1 /* 2 * Copyright (C) 1986-2005 The Free Software Foundation, Inc. 3 * 4 * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>, 5 * and others. 6 * 7 * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk 8 * Portions Copyright (C) 1989-1992, Brian Berliner 9 * 10 * You may distribute under the terms of the GNU General Public License as 11 * specified in the README file that comes with the CVS source distribution. 12 * 13 * Find Names 14 * 15 * Finds all the pertinent file names, both from the administration and from the 16 * repository 17 * 18 * Find Dirs 19 * 20 * Finds all pertinent sub-directories of the checked out instantiation and the 21 * repository (and optionally the attic) 22 */ 23 #include <sys/cdefs.h> 24 __RCSID("$NetBSD: find_names.c,v 1.4 2017/09/15 21:03:26 christos Exp $"); 25 26 #include "cvs.h" 27 #include <glob.h> 28 #include <assert.h> 29 30 static int find_dirs (char *dir, List * list, int checkadm, 31 List *entries); 32 static int find_rcs (const char *dir, List * list); 33 static int add_subdir_proc (Node *, void *); 34 static int register_subdir_proc (Node *, void *); 35 36 /* 37 * add the key from entry on entries list to the files list 38 */ 39 static int add_entries_proc (Node *, void *); 40 static int 41 add_entries_proc (Node *node, void *closure) 42 { 43 Node *fnode; 44 List *filelist = closure; 45 Entnode *entnode = node->data; 46 47 if (entnode->type != ENT_FILE) 48 return (0); 49 50 fnode = getnode (); 51 fnode->type = FILES; 52 fnode->key = xstrdup (node->key); 53 if (addnode (filelist, fnode) != 0) 54 freenode (fnode); 55 return (0); 56 } 57 58 /* Find files in the repository and/or working directory. On error, 59 may either print a nonfatal error and return NULL, or just give 60 a fatal error. On success, return non-NULL (even if it is an empty 61 list). */ 62 63 List * 64 Find_Names (char *repository, int which, int aflag, List **optentries) 65 { 66 List *entries; 67 List *files; 68 69 /* make a list for the files */ 70 files = getlist (); 71 72 /* look at entries (if necessary) */ 73 if (which & W_LOCAL) 74 { 75 /* parse the entries file (if it exists) */ 76 entries = Entries_Open (aflag, NULL); 77 if (entries != NULL) 78 { 79 /* walk the entries file adding elements to the files list */ 80 (void) walklist (entries, add_entries_proc, files); 81 82 /* if our caller wanted the entries list, return it; else free it */ 83 if (optentries != NULL) 84 *optentries = entries; 85 else 86 Entries_Close (entries); 87 } 88 } 89 90 if ((which & W_REPOS) && repository && !isreadable (CVSADM_ENTSTAT)) 91 { 92 /* search the repository */ 93 if (find_rcs (repository, files) != 0) 94 { 95 error (0, errno, "cannot open directory %s", 96 primary_root_inverse_translate (repository)); 97 goto error_exit; 98 } 99 100 /* search the attic too */ 101 if (which & W_ATTIC) 102 { 103 char *dir = Xasprintf ("%s/%s", repository, CVSATTIC); 104 if (find_rcs (dir, files) != 0 105 && !existence_error (errno)) 106 /* For now keep this a fatal error, seems less useful 107 for access control than the case above. */ 108 error (1, errno, "cannot open directory %s", 109 primary_root_inverse_translate (dir)); 110 free (dir); 111 } 112 } 113 114 /* sort the list into alphabetical order and return it */ 115 sortlist (files, fsortcmp); 116 return files; 117 error_exit: 118 dellist (&files); 119 return NULL; 120 } 121 122 /* 123 * Add an entry from the subdirs list to the directories list. This 124 * is called via walklist. 125 */ 126 127 static int 128 add_subdir_proc (Node *p, void *closure) 129 { 130 List *dirlist = closure; 131 Entnode *entnode = p->data; 132 Node *dnode; 133 134 if (entnode->type != ENT_SUBDIR) 135 return 0; 136 137 dnode = getnode (); 138 dnode->type = DIRS; 139 dnode->key = xstrdup (entnode->user); 140 if (addnode (dirlist, dnode) != 0) 141 freenode (dnode); 142 return 0; 143 } 144 145 /* 146 * Register a subdirectory. This is called via walklist. 147 */ 148 149 /*ARGSUSED*/ 150 static int 151 register_subdir_proc (Node *p, void *closure) 152 { 153 List *entries = (List *) closure; 154 155 Subdir_Register (entries, NULL, p->key); 156 return 0; 157 } 158 159 /* 160 * create a list of directories to traverse from the current directory 161 */ 162 List * 163 Find_Directories (char *repository, int which, List *entries) 164 { 165 List *dirlist; 166 167 /* make a list for the directories */ 168 dirlist = getlist (); 169 170 /* find the local ones */ 171 if (which & W_LOCAL) 172 { 173 List *tmpentries; 174 struct stickydirtag *sdtp; 175 176 /* Look through the Entries file. */ 177 178 if (entries != NULL) 179 tmpentries = entries; 180 else if (isfile (CVSADM_ENT)) 181 tmpentries = Entries_Open (0, NULL); 182 else 183 tmpentries = NULL; 184 185 if (tmpentries != NULL) 186 sdtp = tmpentries->list->data; 187 188 /* If we do have an entries list, then if sdtp is NULL, or if 189 sdtp->subdirs is nonzero, all subdirectory information is 190 recorded in the entries list. */ 191 if (tmpentries != NULL && (sdtp == NULL || sdtp->subdirs)) 192 walklist (tmpentries, add_subdir_proc, (void *) dirlist); 193 else 194 { 195 /* This is an old working directory, in which subdirectory 196 information is not recorded in the Entries file. Find 197 the subdirectories the hard way, and, if possible, add 198 it to the Entries file for next time. */ 199 200 /* FIXME-maybe: find_dirs is bogus for this usage because 201 it skips CVSATTIC and CVSLCK directories--those names 202 should be special only in the repository. However, in 203 the interests of not perturbing this code, we probably 204 should leave well enough alone unless we want to write 205 a sanity.sh test case (which would operate by manually 206 hacking on the CVS/Entries file). */ 207 208 if (find_dirs (".", dirlist, 1, tmpentries) != 0) 209 error (1, errno, "cannot open current directory"); 210 if (tmpentries != NULL) 211 { 212 if (! list_isempty (dirlist)) 213 walklist (dirlist, register_subdir_proc, 214 (void *) tmpentries); 215 else 216 Subdirs_Known (tmpentries); 217 } 218 } 219 220 if (entries == NULL && tmpentries != NULL) 221 Entries_Close (tmpentries); 222 } 223 224 /* look for sub-dirs in the repository */ 225 if ((which & W_REPOS) && repository) 226 { 227 /* search the repository */ 228 if (find_dirs (repository, dirlist, 0, entries) != 0) 229 error (1, errno, "cannot open directory %s", repository); 230 231 /* We don't need to look in the attic because directories 232 never go in the attic. In the future, there hopefully will 233 be a better mechanism for detecting whether a directory in 234 the repository is alive or dead; it may or may not involve 235 moving directories to the attic. */ 236 } 237 238 /* sort the list into alphabetical order and return it */ 239 sortlist (dirlist, fsortcmp); 240 return (dirlist); 241 } 242 243 244 245 /* Finds all the files matching PAT. If DIR is NULL, PAT will be interpreted 246 * as either absolute or relative to the PWD and read errors, e.g. failure to 247 * open a directory, will be ignored. If DIR is not NULL, PAT is 248 * always interpreted as relative to DIR. Adds all matching files and 249 * directories to a new List. Returns the new List for success and NULL in 250 * case of error, in which case ERRNO will also be set. 251 * 252 * NOTES 253 * When DIR is NULL, this is really just a thinly veiled wrapper for glob(). 254 * 255 * Much of the cruft in this function could be avoided if DIR was eliminated. 256 * 257 * INPUTS 258 * dir The directory to match relative to. 259 * pat The pattern to match against, via glob(). 260 * 261 * GLOBALS 262 * errno Set on error. 263 * really_quiet Used to decide whether to print warnings. 264 * 265 * RETURNS 266 * A pointer to a List of matching file and directory names, on success. 267 * NULL, on error. 268 * 269 * ERRORS 270 * Error returns can be caused if glob() returns an error. ERRNO will be 271 * set. When !REALLY_QUIET and the failure was not a read error, a warning 272 * message will be printed via error (0, errno, ...). 273 */ 274 List * 275 find_files (const char *dir, const char *pat) 276 { 277 List *retval; 278 glob_t glist; 279 int err, i; 280 char *catpat = NULL; 281 bool dirslash = false; 282 283 if (dir && *dir) 284 { 285 size_t catpatlen = 0; 286 const char *p; 287 if (glob_pattern_p (dir, false)) 288 { 289 /* Escape special characters in DIR. */ 290 size_t len = 0; 291 p = dir; 292 while (*p) 293 { 294 switch (*p) 295 { 296 case '\\': 297 case '*': 298 case '[': 299 case ']': 300 case '?': 301 expand_string (&catpat, &catpatlen, len + 1); 302 catpat[len++] = '\\'; 303 default: 304 expand_string (&catpat, &catpatlen, len + 1); 305 catpat[len++] = *p++; 306 break; 307 } 308 } 309 catpat[len] = '\0'; 310 } 311 else 312 { 313 xrealloc_and_strcat (&catpat, &catpatlen, dir); 314 p = dir + strlen (dir); 315 } 316 317 dirslash = *p - 1 == '/'; 318 if (!dirslash) 319 xrealloc_and_strcat (&catpat, &catpatlen, "/"); 320 321 xrealloc_and_strcat (&catpat, &catpatlen, pat); 322 pat = catpat; 323 } 324 325 err = glob (pat, GLOB_PERIOD | (dir ? GLOB_ERR : 0), NULL, &glist); 326 if (err && err != GLOB_NOMATCH) 327 { 328 if (err == GLOB_ABORTED) 329 /* Let our caller handle the problem. */ 330 return NULL; 331 if (err == GLOB_NOSPACE) errno = ENOMEM; 332 if (!really_quiet) 333 error (0, errno, "glob failed"); 334 if (catpat) free (catpat); 335 return NULL; 336 } 337 338 /* Copy what glob() returned into a List for our caller. */ 339 retval = getlist (); 340 for (i = 0; i < glist.gl_pathc; i++) 341 { 342 Node *p; 343 const char *tmp; 344 345 /* Ignore `.' && `..'. */ 346 tmp = last_component (glist.gl_pathv[i]); 347 if (!strcmp (tmp, ".") || !strcmp (tmp, "..")) 348 continue; 349 350 p = getnode (); 351 p->type = FILES; 352 p->key = xstrdup (glist.gl_pathv[i] 353 + (dir ? strlen (dir) + !dirslash : 0)); 354 if (addnode (retval, p)) freenode (p); 355 } 356 357 if (catpat) free (catpat); 358 globfree (&glist); 359 return retval; 360 } 361 362 363 364 /* walklist() proc which strips a trailing RCSEXT from node keys. 365 */ 366 static int 367 strip_rcsext (Node *p, void *closure) 368 { 369 char *s = p->key + strlen (p->key) - strlen (RCSEXT); 370 assert (!strcmp (s, RCSEXT)); 371 *s = '\0'; /* strip the ,v */ 372 return 0; 373 } 374 375 376 377 /* 378 * Finds all the ,v files in the directory DIR, and adds them to the LIST. 379 * Returns 0 for success and non-zero if DIR cannot be opened, in which case 380 * ERRNO is set to indicate the error. In the error case, LIST is left in some 381 * reasonable state (unchanged, or containing the files which were found before 382 * the error occurred). 383 * 384 * INPUTS 385 * dir The directory to open for read. 386 * 387 * OUTPUTS 388 * list Where to store matching file entries. 389 * 390 * GLOBALS 391 * errno Set on error. 392 * 393 * RETURNS 394 * 0, for success. 395 * <> 0, on error. 396 */ 397 static int 398 find_rcs (dir, list) 399 const char *dir; 400 List *list; 401 { 402 List *newlist; 403 if (!(newlist = find_files (dir, RCSPAT))) 404 return 1; 405 walklist (newlist, strip_rcsext, NULL); 406 mergelists (list, &newlist); 407 return 0; 408 } 409 410 411 412 /* 413 * Finds all the subdirectories of the argument dir and adds them to 414 * the specified list. Sub-directories without a CVS administration 415 * directory are optionally ignored. If ENTRIES is not NULL, all 416 * files on the list are ignored. Returns 0 for success or 1 on 417 * error, in which case errno is set to indicate the error. 418 */ 419 static int 420 find_dirs (char *dir, List *list, int checkadm, List *entries) 421 { 422 Node *p; 423 char *tmp = NULL; 424 size_t tmp_size = 0; 425 struct dirent *dp; 426 DIR *dirp; 427 int skip_emptydir = 0; 428 429 /* First figure out whether we need to skip directories named 430 Emptydir. Except in the CVSNULLREPOS case, Emptydir is just 431 a normal directory name. */ 432 if (ISABSOLUTE (dir) 433 && strncmp (dir, current_parsed_root->directory, strlen (current_parsed_root->directory)) == 0 434 && ISSLASH (dir[strlen (current_parsed_root->directory)]) 435 && strcmp (dir + strlen (current_parsed_root->directory) + 1, CVSROOTADM) == 0) 436 skip_emptydir = 1; 437 438 /* set up to read the dir */ 439 if ((dirp = CVS_OPENDIR (dir)) == NULL) 440 return (1); 441 442 /* read the dir, grabbing sub-dirs */ 443 errno = 0; 444 while ((dp = CVS_READDIR (dirp)) != NULL) 445 { 446 if (strcmp (dp->d_name, ".") == 0 || 447 strcmp (dp->d_name, "..") == 0 || 448 strcmp (dp->d_name, CVSATTIC) == 0 || 449 strcmp (dp->d_name, CVSLCK) == 0 || 450 strcmp (dp->d_name, CVSREP) == 0) 451 goto do_it_again; 452 453 /* findnode() is going to be significantly faster than stat() 454 because it involves no system calls. That is why we bother 455 with the entries argument, and why we check this first. */ 456 if (entries != NULL && findnode (entries, dp->d_name) != NULL) 457 goto do_it_again; 458 459 if (skip_emptydir 460 && strcmp (dp->d_name, CVSNULLREPOS) == 0) 461 goto do_it_again; 462 463 #ifdef DT_DIR 464 if (dp->d_type != DT_DIR) 465 { 466 if (dp->d_type != DT_UNKNOWN && dp->d_type != DT_LNK) 467 goto do_it_again; 468 #endif 469 /* don't bother stating ,v files */ 470 if (CVS_FNMATCH (RCSPAT, dp->d_name, 0) == 0) 471 goto do_it_again; 472 473 expand_string (&tmp, 474 &tmp_size, 475 strlen (dir) + strlen (dp->d_name) + 10); 476 snprintf (tmp, tmp_size, "%s/%s", dir, dp->d_name); 477 if (!isdir (tmp)) 478 goto do_it_again; 479 480 #ifdef DT_DIR 481 } 482 #endif 483 484 /* check for administration directories (if needed) */ 485 if (checkadm) 486 { 487 /* blow off symbolic links to dirs in local dir */ 488 #ifdef DT_DIR 489 if (dp->d_type != DT_DIR) 490 { 491 /* we're either unknown or a symlink at this point */ 492 if (dp->d_type == DT_LNK) 493 goto do_it_again; 494 #endif 495 /* Note that we only get here if we already set tmp 496 above. */ 497 if (islink (tmp, NULL)) 498 goto do_it_again; 499 #ifdef DT_DIR 500 } 501 #endif 502 503 /* check for new style */ 504 expand_string (&tmp, 505 &tmp_size, 506 (strlen (dir) + strlen (dp->d_name) 507 + sizeof (CVSADM) + 10)); 508 (void)snprintf (tmp, tmp_size, "%s/%s/%s", dir, dp->d_name, CVSADM); 509 if (!isdir (tmp)) 510 goto do_it_again; 511 } 512 513 /* put it in the list */ 514 p = getnode (); 515 p->type = DIRS; 516 p->key = xstrdup (dp->d_name); 517 if (addnode (list, p) != 0) 518 freenode (p); 519 520 do_it_again: 521 errno = 0; 522 } 523 if (errno != 0) 524 { 525 int save_errno = errno; 526 (void) CVS_CLOSEDIR (dirp); 527 errno = save_errno; 528 return 1; 529 } 530 (void) CVS_CLOSEDIR (dirp); 531 if (tmp != NULL) 532 free (tmp); 533 return (0); 534 } 535