1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 /*
  27  *      files.c
  28  *
  29  *      Various file related routines:
  30  *              Figure out if file exists
  31  *              Wildcard resolution for directory reader
  32  *              Directory reader
  33  */
  34 
  35 
  36 /*
  37  * Included files
  38  */
  39 #include <dirent.h>               /* opendir() */
  40 #include <errno.h>                /* errno */
  41 #include <mk/defs.h>
  42 #include <mksh/macro.h>           /* getvar() */
  43 #include <mksh/misc.h>            /* get_prop(), append_prop() */
  44 #include <sys/stat.h>             /* lstat() */
  45 
  46 /*
  47  * Defined macros
  48  */
  49 
  50 /*
  51  * typedefs & structs
  52  */
  53 
  54 /*
  55  * Static variables
  56  */
  57 
  58 /*
  59  * File table of contents
  60  */
  61 extern  timestruc_t&        exists(register Name target);
  62 extern  void            set_target_stat(register Name target, struct stat buf);
  63 static  timestruc_t&        vpath_exists(register Name target);
  64 static  Name            enter_file_name(wchar_t *name_string, wchar_t *library);
  65 static  Boolean         star_match(register char *string, register char *pattern);
  66 static  Boolean         amatch(register wchar_t *string, register wchar_t *pattern);
  67   
  68 /*
  69  *      exists(target)
  70  *
  71  *      Figure out the timestamp for one target.
  72  *
  73  *      Return value:
  74  *                              The time the target was created
  75  *
  76  *      Parameters:
  77  *              target          The target to check
  78  *
  79  *      Global variables used:
  80  *              debug_level     Should we trace the stat call?
  81  *              recursion_level Used for tracing
  82  *              vpath_defined   Was the variable VPATH defined in environment?
  83  */
  84 timestruc_t&
  85 exists(register Name target)
  86 {
  87         struct stat             buf;
  88         register int            result;
  89 
  90         /* We cache stat information. */
  91         if (target->stat.time != file_no_time) {
  92                 return target->stat.time;
  93         }
  94 
  95         /*
  96          * If the target is a member, we have to extract the time
  97          * from the archive.
  98          */
  99         if (target->is_member &&
 100             (get_prop(target->prop, member_prop) != NULL)) {
 101                 return read_archive(target);
 102         }
 103 
 104         if (debug_level > 1) {
 105                 (void) printf(NOCATGETS("%*sstat(%s)\n"),
 106                               recursion_level,
 107                               "",
 108                               target->string_mb);
 109         }
 110 
 111         result = lstat_vroot(target->string_mb, &buf, NULL, VROOT_DEFAULT);
 112         if ((result != -1) && ((buf.st_mode & S_IFMT) == S_IFLNK)) {
 113                 /*
 114                  * If the file is a symbolic link, we remember that
 115                  * and then we get the status for the refd file.
 116                  */
 117                 target->stat.is_sym_link = true;
 118                 result = stat_vroot(target->string_mb, &buf, NULL, VROOT_DEFAULT);
 119         } else {
 120                 target->stat.is_sym_link = false;
 121         }
 122 
 123         if (result < 0) {
 124                 target->stat.time = file_doesnt_exist;
 125                 target->stat.stat_errno = errno;
 126                 if ((errno == ENOENT) &&
 127                     vpath_defined &&
 128 /* azv, fixing bug 1262942, VPATH works with a leaf name
 129  * but not a directory name.
 130  */
 131                     (target->string_mb[0] != (int) slash_char) ) {
 132 /* BID_1214655 */
 133 /* azv */
 134                         vpath_exists(target);
 135                         // return vpath_exists(target);
 136                 }
 137         } else {
 138                 /* Save all the information we need about the file */
 139                 target->stat.stat_errno = 0;
 140                 target->stat.is_file = true;
 141                 target->stat.mode = buf.st_mode & 0777;
 142                 target->stat.size = buf.st_size;
 143                 target->stat.is_dir =
 144                   BOOLEAN((buf.st_mode & S_IFMT) == S_IFDIR);
 145                 if (target->stat.is_dir) {
 146                         target->stat.time = file_is_dir;
 147                 } else {
 148                         /* target->stat.time = buf.st_mtime; */
 149 /* BID_1129806 */
 150 /* vis@nbsp.nsk.su */
 151 #if defined(linux)
 152                         timestruc_t ttime = { buf.st_mtime, 0 };
 153                         target->stat.time = MAX(ttime, file_min_time);
 154 #else
 155                         target->stat.time = MAX(buf.st_mtim, file_min_time);
 156 #endif
 157                 }
 158         }
 159         if ((target->colon_splits > 0) &&
 160             (get_prop(target->prop, time_prop) == NULL)) {
 161                 append_prop(target, time_prop)->body.time.time =
 162                   target->stat.time;
 163         }
 164         return target->stat.time;
 165 }
 166 
 167 /*
 168  *      set_target_stat( target, buf)
 169  *
 170  *      Called by exists() to set some stat fields in the Name structure
 171  *      to those read by the stat_vroot() call (from disk).
 172  *
 173  *      Parameters:
 174  *              target          The target whose stat field is set
 175  *              buf             stat values (on disk) of the file
 176  *                              represented by target.
 177  */
 178 void
 179 set_target_stat(register Name target, struct stat buf)
 180 {
 181         target->stat.stat_errno = 0;
 182         target->stat.is_file = true;
 183         target->stat.mode = buf.st_mode & 0777;
 184         target->stat.size = buf.st_size;
 185         target->stat.is_dir =
 186           BOOLEAN((buf.st_mode & S_IFMT) == S_IFDIR);
 187         if (target->stat.is_dir) {
 188                 target->stat.time = file_is_dir;
 189         } else {
 190                 /* target->stat.time = buf.st_mtime; */
 191 /* BID_1129806 */
 192 /* vis@nbsp.nsk.su */
 193 #if defined(linux)
 194                 timestruc_t ttime = { buf.st_mtime, 0 };
 195                 target->stat.time = ttime;
 196 #else
 197                 target->stat.time = MAX(buf.st_mtim, file_min_time);
 198 #endif
 199         }
 200 }
 201 
 202 
 203 /*
 204  *      vpath_exists(target)
 205  *
 206  *      Called if exists() discovers that there is a VPATH defined.
 207  *      This function stats the VPATH translation of the target.
 208  *
 209  *      Return value:
 210  *                              The time the target was created
 211  *
 212  *      Parameters:
 213  *              target          The target to check
 214  *
 215  *      Global variables used:
 216  *              vpath_name      The Name "VPATH", used to get macro value
 217  */
 218 static timestruc_t&
 219 vpath_exists(register Name target)
 220 {
 221         wchar_t                 *vpath;
 222         wchar_t                 file_name[MAXPATHLEN];
 223         wchar_t                 *name_p;
 224         Name                    alias;
 225 
 226         /*
 227          * To avoid recursive search through VPATH when exists(alias) is called
 228          */
 229         vpath_defined = false;
 230 
 231         Wstring wcb(getvar(vpath_name));
 232         Wstring wcb1(target);
 233 
 234         vpath = wcb.get_string();
 235 
 236         while (*vpath != (int) nul_char) {
 237                 name_p = file_name;
 238                 while ((*vpath != (int) colon_char) &&
 239                        (*vpath != (int) nul_char)) {
 240                         *name_p++ = *vpath++;
 241                 }
 242                 *name_p++ = (int) slash_char;
 243                 (void) wscpy(name_p, wcb1.get_string());
 244                 alias = GETNAME(file_name, FIND_LENGTH);
 245                 if (exists(alias) != file_doesnt_exist) {
 246                         target->stat.is_file = true;
 247                         target->stat.mode = alias->stat.mode;
 248                         target->stat.size = alias->stat.size;
 249                         target->stat.is_dir = alias->stat.is_dir;
 250                         target->stat.time = alias->stat.time;
 251                         maybe_append_prop(target, vpath_alias_prop)->
 252                                                 body.vpath_alias.alias = alias;
 253                         target->has_vpath_alias_prop = true;
 254                         vpath_defined = true;
 255                         return alias->stat.time;
 256                 }
 257                 while ((*vpath != (int) nul_char) &&
 258                        ((*vpath == (int) colon_char) || iswspace(*vpath))) {
 259                         vpath++;
 260                 }
 261         }
 262         /*
 263          * Restore vpath_defined
 264          */
 265         vpath_defined = true;
 266         return target->stat.time;
 267 }
 268 
 269 /*
 270  *      read_dir(dir, pattern, line, library)
 271  *
 272  *      Used to enter the contents of directories into makes namespace.
 273  *      Presence of a file is important when scanning for implicit rules.
 274  *      read_dir() is also used to expand wildcards in dependency lists.
 275  *
 276  *      Return value:
 277  *                              Non-0 if we found files to match the pattern
 278  *
 279  *      Parameters:
 280  *              dir             Path to the directory to read
 281  *              pattern         Pattern for that files should match or NULL
 282  *              line            When we scan using a pattern we enter files
 283  *                              we find as dependencies for this line
 284  *              library         If we scan for "lib.a(<wildcard-member>)"
 285  *
 286  *      Global variables used:
 287  *              debug_level     Should we trace the dir reading?
 288  *              dot             The Name ".", compared against
 289  *              sccs_dir_path   The path to the SCCS dir (from PROJECTDIR)
 290  *              vpath_defined   Was the variable VPATH defined in environment?
 291  *              vpath_name      The Name "VPATH", use to get macro value
 292  */
 293 int
 294 read_dir(Name dir, wchar_t *pattern, Property line, wchar_t *library)
 295 {
 296         wchar_t                 file_name[MAXPATHLEN];
 297         wchar_t                 *file_name_p = file_name;
 298         Name                    file;
 299         wchar_t                 plain_file_name[MAXPATHLEN];
 300         wchar_t                 *plain_file_name_p;
 301         Name                    plain_file;
 302         wchar_t                 tmp_wcs_buffer[MAXPATHLEN];
 303         DIR                     *dir_fd;
 304         int                     m_local_dependency=0;
 305 #define d_fileno d_ino
 306         register struct dirent  *dp;
 307         wchar_t                 *vpath = NULL;
 308         wchar_t                 *p;
 309         int                     result = 0;
 310 
 311         if(dir->hash.length >= MAXPATHLEN) {
 312                 return 0;
 313         }
 314 
 315         Wstring wcb(dir);
 316         Wstring vps;
 317 
 318         /* A directory is only read once unless we need to expand wildcards. */
 319         if (pattern == NULL) {
 320                 if (dir->has_read_dir) {
 321                         return 0;
 322                 }
 323                 dir->has_read_dir = true;
 324         }
 325         /* Check if VPATH is active and setup list if it is. */
 326         if (vpath_defined && (dir == dot)) {
 327                 vps.init(getvar(vpath_name));
 328                 vpath = vps.get_string();
 329         }
 330 
 331         /*
 332          * Prepare the string where we build the full name of the
 333          * files in the directory.
 334          */
 335         if ((dir->hash.length > 1) || (wcb.get_string()[0] != (int) period_char)) {
 336                 (void) wscpy(file_name, wcb.get_string());
 337                 MBSTOWCS(wcs_buffer, "/");
 338                 (void) wscat(file_name, wcs_buffer);
 339                 file_name_p = file_name + wslen(file_name);
 340         }
 341 
 342         /* Open the directory. */
 343 vpath_loop:
 344         dir_fd = opendir(dir->string_mb);
 345         if (dir_fd == NULL) {
 346                 return 0;
 347         }
 348 
 349         /* Read all the directory entries. */
 350         while ((dp = readdir(dir_fd)) != NULL) {
 351                 /* We ignore "." and ".." */
 352                 if ((dp->d_fileno == 0) ||
 353                     ((dp->d_name[0] == (int) period_char) &&
 354                      ((dp->d_name[1] == 0) ||
 355                       ((dp->d_name[1] == (int) period_char) &&
 356                        (dp->d_name[2] == 0))))) {
 357                         continue;
 358                 }
 359                 /*
 360                  * Build the full name of the file using whatever
 361                  * path supplied to the function.
 362                  */
 363                 MBSTOWCS(tmp_wcs_buffer, dp->d_name);
 364                 (void) wscpy(file_name_p, tmp_wcs_buffer);
 365                 file = enter_file_name(file_name, library);
 366                 if ((pattern != NULL) && amatch(tmp_wcs_buffer, pattern)) {
 367                         /*
 368                          * If we are expanding a wildcard pattern, we
 369                          * enter the file as a dependency for the target.
 370                          */
 371                         if (debug_level > 0){
 372                                 WCSTOMBS(mbs_buffer, pattern);
 373                                 (void) printf(catgets(catd, 1, 231, "'%s: %s' due to %s expansion\n"),
 374                                               line->body.line.target->string_mb,
 375                                               file->string_mb,
 376                                               mbs_buffer);
 377                         }
 378                         enter_dependency(line, file, false);
 379                         result++;
 380                 } else {
 381                         /*
 382                          * If the file has an SCCS/s. file,
 383                          * we will detect that later on.
 384                          */
 385                         file->stat.has_sccs = NO_SCCS;
 386                 /*
 387                  * If this is an s. file, we also enter it as if it
 388                  * existed in the plain directory.
 389                  */
 390                 if ((dp->d_name[0] == 's') &&
 391                     (dp->d_name[1] == (int) period_char)) {
 392         
 393                         MBSTOWCS(tmp_wcs_buffer, dp->d_name + 2);
 394                         plain_file_name_p = plain_file_name;
 395                         (void) wscpy(plain_file_name_p, tmp_wcs_buffer);
 396                         plain_file = GETNAME(plain_file_name, FIND_LENGTH);
 397                         plain_file->stat.is_file = true;
 398                         plain_file->stat.has_sccs = HAS_SCCS;
 399                         /*
 400                          * Enter the s. file as a dependency for the
 401                          * plain file.
 402                          */
 403                         maybe_append_prop(plain_file, sccs_prop)->
 404                           body.sccs.file = file;
 405                         MBSTOWCS(tmp_wcs_buffer, dp->d_name + 2);
 406                         if ((pattern != NULL) &&
 407                             amatch(tmp_wcs_buffer, pattern)) {
 408                                 if (debug_level > 0) {
 409                                         WCSTOMBS(mbs_buffer, pattern);
 410                                         (void) printf(catgets(catd, 1, 232, "'%s: %s' due to %s expansion\n"),
 411                                                       line->body.line.target->
 412                                                       string_mb,
 413                                                       plain_file->string_mb,
 414                                                       mbs_buffer);
 415                                 }
 416                                 enter_dependency(line, plain_file, false);
 417                                 result++;
 418                         }
 419                 }
 420               }
 421         }
 422         (void) closedir(dir_fd);
 423         if ((vpath != NULL) && (*vpath != (int) nul_char)) {
 424                 while ((*vpath != (int) nul_char) &&
 425                        (iswspace(*vpath) || (*vpath == (int) colon_char))) {
 426                         vpath++;
 427                 }
 428                 p = vpath;
 429                 while ((*vpath != (int) colon_char) &&
 430                        (*vpath != (int) nul_char)) {
 431                         vpath++;
 432                 }
 433                 if (vpath > p) {
 434                         dir = GETNAME(p, vpath - p);
 435                         goto vpath_loop;
 436                 }
 437         }
 438 /*
 439  * look into SCCS directory only if it's not svr4. For svr4 dont do that.
 440  */
 441 
 442 /*
 443  * Now read the SCCS directory.
 444  * Files in the SCSC directory are considered to be part of the set of
 445  * files in the plain directory. They are also entered in their own right.
 446  * Prepare the string where we build the true name of the SCCS files.
 447  */
 448         (void) wsncpy(plain_file_name,
 449                       file_name,
 450                       file_name_p - file_name);
 451         plain_file_name[file_name_p - file_name] = 0;
 452         plain_file_name_p = plain_file_name + wslen(plain_file_name);
 453 
 454         if(!svr4) {
 455 
 456           if (sccs_dir_path != NULL) {
 457                 wchar_t         tmp_wchar;
 458                 wchar_t         path[MAXPATHLEN];
 459                 char            mb_path[MAXPATHLEN];
 460 
 461                 if (file_name_p - file_name > 0) {
 462                         tmp_wchar = *file_name_p;
 463                         *file_name_p = 0;
 464                         WCSTOMBS(mbs_buffer, file_name);
 465                         (void) sprintf(mb_path, NOCATGETS("%s/%s/SCCS"),
 466                                         sccs_dir_path,
 467                                         mbs_buffer);
 468                         *file_name_p = tmp_wchar;
 469                 } else {
 470                         (void) sprintf(mb_path, NOCATGETS("%s/SCCS"), sccs_dir_path);
 471                 }
 472                 MBSTOWCS(path, mb_path);
 473                 (void) wscpy(file_name, path);
 474           } else {
 475                 MBSTOWCS(wcs_buffer, NOCATGETS("SCCS"));
 476                 (void) wscpy(file_name_p, wcs_buffer);
 477           }
 478         } else {
 479                 MBSTOWCS(wcs_buffer, NOCATGETS("."));
 480                 (void) wscpy(file_name_p, wcs_buffer);
 481         }
 482         /* Internalize the constructed SCCS dir name. */
 483         (void) exists(dir = GETNAME(file_name, FIND_LENGTH));
 484         /* Just give up if the directory file doesnt exist. */
 485         if (!dir->stat.is_file) {
 486                 return result;
 487         }
 488         /* Open the directory. */
 489         dir_fd = opendir(dir->string_mb);
 490         if (dir_fd == NULL) {
 491                 return result;
 492         }
 493         MBSTOWCS(wcs_buffer, "/");
 494         (void) wscat(file_name, wcs_buffer);
 495         file_name_p = file_name + wslen(file_name);
 496 
 497         while ((dp = readdir(dir_fd)) != NULL) {
 498                 if ((dp->d_fileno == 0) ||
 499                     ((dp->d_name[0] == (int) period_char) &&
 500                      ((dp->d_name[1] == 0) ||
 501                       ((dp->d_name[1] == (int) period_char) &&
 502                        (dp->d_name[2] == 0))))) {
 503                         continue;
 504                 }
 505                 /* Construct and internalize the true name of the SCCS file. */
 506                 MBSTOWCS(wcs_buffer, dp->d_name);
 507                 (void) wscpy(file_name_p, wcs_buffer);
 508                 file = GETNAME(file_name, FIND_LENGTH);
 509                 file->stat.is_file = true;
 510                 file->stat.has_sccs = NO_SCCS;
 511                 /*
 512                  * If this is an s. file, we also enter it as if it
 513                  * existed in the plain directory.
 514                  */
 515                 if ((dp->d_name[0] == 's') &&
 516                     (dp->d_name[1] == (int) period_char)) {
 517         
 518                         MBSTOWCS(wcs_buffer, dp->d_name + 2);
 519                         (void) wscpy(plain_file_name_p, wcs_buffer);
 520                         plain_file = GETNAME(plain_file_name, FIND_LENGTH);
 521                         plain_file->stat.is_file = true;
 522                         plain_file->stat.has_sccs = HAS_SCCS;
 523                                 /* if sccs dependency is already set,skip */
 524                         if(plain_file->prop) {
 525                                 Property sprop = get_prop(plain_file->prop,sccs_prop);
 526                                 if(sprop != NULL) {
 527                                         if (sprop->body.sccs.file) {
 528                                                 goto try_pattern;
 529                                         }
 530                                 }
 531                         }
 532 
 533                         /*
 534                          * Enter the s. file as a dependency for the
 535                          * plain file.
 536                          */
 537                         maybe_append_prop(plain_file, sccs_prop)->
 538                           body.sccs.file = file;
 539 try_pattern:
 540                         MBSTOWCS(tmp_wcs_buffer, dp->d_name + 2);
 541                         if ((pattern != NULL) &&
 542                             amatch(tmp_wcs_buffer, pattern)) {
 543                                 if (debug_level > 0) {
 544                                         WCSTOMBS(mbs_buffer, pattern);
 545                                         (void) printf(catgets(catd, 1, 233, "'%s: %s' due to %s expansion\n"),
 546                                                       line->body.line.target->
 547                                                       string_mb,
 548                                                       plain_file->string_mb,
 549                                                       mbs_buffer);
 550                                 }
 551                                 enter_dependency(line, plain_file, false);
 552                                 result++;
 553                         }
 554                 }
 555         }
 556         (void) closedir(dir_fd);
 557 
 558         return result;
 559 }
 560 
 561 /*
 562  *      enter_file_name(name_string, library)
 563  *
 564  *      Helper function for read_dir().
 565  *
 566  *      Return value:
 567  *                              The Name that was entered
 568  *
 569  *      Parameters:
 570  *              name_string     Name of the file we want to enter
 571  *              library         The library it is a member of, if any
 572  *
 573  *      Global variables used:
 574  */
 575 static Name
 576 enter_file_name(wchar_t *name_string, wchar_t *library)
 577 {
 578         wchar_t         buffer[STRING_BUFFER_LENGTH];
 579         String_rec      lib_name;
 580         Name            name;
 581         Property        prop;
 582 
 583         if (library == NULL) {
 584                 name = GETNAME(name_string, FIND_LENGTH);
 585                 name->stat.is_file = true;
 586                 return name;
 587         }
 588 
 589         INIT_STRING_FROM_STACK(lib_name, buffer);
 590         append_string(library, &lib_name, FIND_LENGTH);
 591         append_char((int) parenleft_char, &lib_name);
 592         append_string(name_string, &lib_name, FIND_LENGTH);
 593         append_char((int) parenright_char, &lib_name);
 594 
 595         name = GETNAME(lib_name.buffer.start, FIND_LENGTH);
 596         name->stat.is_file = true;
 597         name->is_member = true;
 598         prop = maybe_append_prop(name, member_prop);
 599         prop->body.member.library = GETNAME(library, FIND_LENGTH);
 600         prop->body.member.library->stat.is_file = true;
 601         prop->body.member.entry = NULL;
 602         prop->body.member.member = GETNAME(name_string, FIND_LENGTH);
 603         prop->body.member.member->stat.is_file = true;
 604         return name;
 605 }
 606 
 607 /*
 608  *      star_match(string, pattern)
 609  *
 610  *      This is a regular shell type wildcard pattern matcher
 611  *      It is used when xpanding wildcards in dependency lists
 612  *
 613  *      Return value:
 614  *                              Indication if the string matched the pattern
 615  *
 616  *      Parameters:
 617  *              string          String to match
 618  *              pattern         Pattern to match it against
 619  *
 620  *      Global variables used:
 621  */
 622 static Boolean
 623 star_match(register wchar_t *string, register wchar_t *pattern)
 624 {
 625         register int            pattern_ch;
 626 
 627         switch (*pattern) {
 628         case 0:
 629                 return succeeded;
 630         case bracketleft_char:
 631         case question_char:
 632         case asterisk_char:
 633                 while (*string) {
 634                         if (amatch(string++, pattern)) {
 635                                 return succeeded;
 636                         }
 637                 }
 638                 break;
 639         default:
 640                 pattern_ch = (int) *pattern++;
 641                 while (*string) {
 642                         if ((*string++ == pattern_ch) &&
 643                             amatch(string, pattern)) {
 644                                 return succeeded;
 645                         }
 646                 }
 647                 break;
 648         }
 649         return failed;
 650 }
 651 
 652 /*
 653  *      amatch(string, pattern)
 654  *
 655  *      Helper function for shell pattern matching
 656  *
 657  *      Return value:
 658  *                              Indication if the string matched the pattern
 659  *
 660  *      Parameters:
 661  *              string          String to match
 662  *              pattern         Pattern to match it against
 663  *
 664  *      Global variables used:
 665  */
 666 static Boolean
 667 amatch(register wchar_t *string, register wchar_t *pattern)
 668 {
 669         register long           lower_bound;
 670         register long           string_ch;
 671         register long           pattern_ch;
 672         register int            k;
 673 
 674 top:
 675         for (; 1; pattern++, string++) {
 676                 lower_bound = 017777777777;
 677                 string_ch = *string;
 678                 switch (pattern_ch = *pattern) {
 679                 case bracketleft_char:
 680                         k = 0;
 681                         while ((pattern_ch = *++pattern) != 0) {
 682                                 switch (pattern_ch) {
 683                                 case bracketright_char:
 684                                         if (!k) {
 685                                                 return failed;
 686                                         }
 687                                         string++;
 688                                         pattern++;
 689                                         goto top;
 690                                 case hyphen_char:
 691                                         k |= (lower_bound <= string_ch) &&
 692                                              (string_ch <=
 693                                               (pattern_ch = pattern[1]));
 694                                 default:
 695                                         if (string_ch ==
 696                                             (lower_bound = pattern_ch)) {
 697                                                 k++;
 698                                         }
 699                                 }
 700                         }
 701                         return failed;
 702                 case asterisk_char:
 703                         return star_match(string, ++pattern);
 704                 case 0:
 705                         return BOOLEAN(!string_ch);
 706                 case question_char:
 707                         if (string_ch == 0) {
 708                                 return failed;
 709                         }
 710                         break;
 711                 default:
 712                         if (pattern_ch != string_ch) {
 713                                 return failed;
 714                         }
 715                         break;
 716                 }
 717         }
 718         /* NOTREACHED */
 719 }
 720