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                         target->stat.time = MAX(buf.st_mtim, file_min_time);
 152                 }
 153         }
 154         if ((target->colon_splits > 0) &&
 155             (get_prop(target->prop, time_prop) == NULL)) {
 156                 append_prop(target, time_prop)->body.time.time =
 157                   target->stat.time;
 158         }
 159         return target->stat.time;
 160 }
 161 
 162 /*
 163  *      set_target_stat( target, buf)
 164  *
 165  *      Called by exists() to set some stat fields in the Name structure
 166  *      to those read by the stat_vroot() call (from disk).
 167  *
 168  *      Parameters:
 169  *              target          The target whose stat field is set
 170  *              buf             stat values (on disk) of the file
 171  *                              represented by target.
 172  */
 173 void
 174 set_target_stat(register Name target, struct stat buf)
 175 {
 176         target->stat.stat_errno = 0;
 177         target->stat.is_file = true;
 178         target->stat.mode = buf.st_mode & 0777;
 179         target->stat.size = buf.st_size;
 180         target->stat.is_dir =
 181           BOOLEAN((buf.st_mode & S_IFMT) == S_IFDIR);
 182         if (target->stat.is_dir) {
 183                 target->stat.time = file_is_dir;
 184         } else {
 185                 /* target->stat.time = buf.st_mtime; */
 186 /* BID_1129806 */
 187 /* vis@nbsp.nsk.su */
 188                 target->stat.time = MAX(buf.st_mtim, file_min_time);
 189         }
 190 }
 191 
 192 
 193 /*
 194  *      vpath_exists(target)
 195  *
 196  *      Called if exists() discovers that there is a VPATH defined.
 197  *      This function stats the VPATH translation of the target.
 198  *
 199  *      Return value:
 200  *                              The time the target was created
 201  *
 202  *      Parameters:
 203  *              target          The target to check
 204  *
 205  *      Global variables used:
 206  *              vpath_name      The Name "VPATH", used to get macro value
 207  */
 208 static timestruc_t&
 209 vpath_exists(register Name target)
 210 {
 211         wchar_t                 *vpath;
 212         wchar_t                 file_name[MAXPATHLEN];
 213         wchar_t                 *name_p;
 214         Name                    alias;
 215 
 216         /*
 217          * To avoid recursive search through VPATH when exists(alias) is called
 218          */
 219         vpath_defined = false;
 220 
 221         Wstring wcb(getvar(vpath_name));
 222         Wstring wcb1(target);
 223 
 224         vpath = wcb.get_string();
 225 
 226         while (*vpath != (int) nul_char) {
 227                 name_p = file_name;
 228                 while ((*vpath != (int) colon_char) &&
 229                        (*vpath != (int) nul_char)) {
 230                         *name_p++ = *vpath++;
 231                 }
 232                 *name_p++ = (int) slash_char;
 233                 (void) wscpy(name_p, wcb1.get_string());
 234                 alias = GETNAME(file_name, FIND_LENGTH);
 235                 if (exists(alias) != file_doesnt_exist) {
 236                         target->stat.is_file = true;
 237                         target->stat.mode = alias->stat.mode;
 238                         target->stat.size = alias->stat.size;
 239                         target->stat.is_dir = alias->stat.is_dir;
 240                         target->stat.time = alias->stat.time;
 241                         maybe_append_prop(target, vpath_alias_prop)->
 242                                                 body.vpath_alias.alias = alias;
 243                         target->has_vpath_alias_prop = true;
 244                         vpath_defined = true;
 245                         return alias->stat.time;
 246                 }
 247                 while ((*vpath != (int) nul_char) &&
 248                        ((*vpath == (int) colon_char) || iswspace(*vpath))) {
 249                         vpath++;
 250                 }
 251         }
 252         /*
 253          * Restore vpath_defined
 254          */
 255         vpath_defined = true;
 256         return target->stat.time;
 257 }
 258 
 259 /*
 260  *      read_dir(dir, pattern, line, library)
 261  *
 262  *      Used to enter the contents of directories into makes namespace.
 263  *      Presence of a file is important when scanning for implicit rules.
 264  *      read_dir() is also used to expand wildcards in dependency lists.
 265  *
 266  *      Return value:
 267  *                              Non-0 if we found files to match the pattern
 268  *
 269  *      Parameters:
 270  *              dir             Path to the directory to read
 271  *              pattern         Pattern for that files should match or NULL
 272  *              line            When we scan using a pattern we enter files
 273  *                              we find as dependencies for this line
 274  *              library         If we scan for "lib.a(<wildcard-member>)"
 275  *
 276  *      Global variables used:
 277  *              debug_level     Should we trace the dir reading?
 278  *              dot             The Name ".", compared against
 279  *              sccs_dir_path   The path to the SCCS dir (from PROJECTDIR)
 280  *              vpath_defined   Was the variable VPATH defined in environment?
 281  *              vpath_name      The Name "VPATH", use to get macro value
 282  */
 283 int
 284 read_dir(Name dir, wchar_t *pattern, Property line, wchar_t *library)
 285 {
 286         wchar_t                 file_name[MAXPATHLEN];
 287         wchar_t                 *file_name_p = file_name;
 288         Name                    file;
 289         wchar_t                 plain_file_name[MAXPATHLEN];
 290         wchar_t                 *plain_file_name_p;
 291         Name                    plain_file;
 292         wchar_t                 tmp_wcs_buffer[MAXPATHLEN];
 293         DIR                     *dir_fd;
 294         int                     m_local_dependency=0;
 295 #define d_fileno d_ino
 296         register struct dirent  *dp;
 297         wchar_t                 *vpath = NULL;
 298         wchar_t                 *p;
 299         int                     result = 0;
 300 
 301         if(dir->hash.length >= MAXPATHLEN) {
 302                 return 0;
 303         }
 304 
 305         Wstring wcb(dir);
 306         Wstring vps;
 307 
 308         /* A directory is only read once unless we need to expand wildcards. */
 309         if (pattern == NULL) {
 310                 if (dir->has_read_dir) {
 311                         return 0;
 312                 }
 313                 dir->has_read_dir = true;
 314         }
 315         /* Check if VPATH is active and setup list if it is. */
 316         if (vpath_defined && (dir == dot)) {
 317                 vps.init(getvar(vpath_name));
 318                 vpath = vps.get_string();
 319         }
 320 
 321         /*
 322          * Prepare the string where we build the full name of the
 323          * files in the directory.
 324          */
 325         if ((dir->hash.length > 1) || (wcb.get_string()[0] != (int) period_char)) {
 326                 (void) wscpy(file_name, wcb.get_string());
 327                 MBSTOWCS(wcs_buffer, "/");
 328                 (void) wscat(file_name, wcs_buffer);
 329                 file_name_p = file_name + wslen(file_name);
 330         }
 331 
 332         /* Open the directory. */
 333 vpath_loop:
 334         dir_fd = opendir(dir->string_mb);
 335         if (dir_fd == NULL) {
 336                 return 0;
 337         }
 338 
 339         /* Read all the directory entries. */
 340         while ((dp = readdir(dir_fd)) != NULL) {
 341                 /* We ignore "." and ".." */
 342                 if ((dp->d_fileno == 0) ||
 343                     ((dp->d_name[0] == (int) period_char) &&
 344                      ((dp->d_name[1] == 0) ||
 345                       ((dp->d_name[1] == (int) period_char) &&
 346                        (dp->d_name[2] == 0))))) {
 347                         continue;
 348                 }
 349                 /*
 350                  * Build the full name of the file using whatever
 351                  * path supplied to the function.
 352                  */
 353                 MBSTOWCS(tmp_wcs_buffer, dp->d_name);
 354                 (void) wscpy(file_name_p, tmp_wcs_buffer);
 355                 file = enter_file_name(file_name, library);
 356                 if ((pattern != NULL) && amatch(tmp_wcs_buffer, pattern)) {
 357                         /*
 358                          * If we are expanding a wildcard pattern, we
 359                          * enter the file as a dependency for the target.
 360                          */
 361                         if (debug_level > 0){
 362                                 WCSTOMBS(mbs_buffer, pattern);
 363                                 (void) printf(catgets(catd, 1, 231, "'%s: %s' due to %s expansion\n"),
 364                                               line->body.line.target->string_mb,
 365                                               file->string_mb,
 366                                               mbs_buffer);
 367                         }
 368                         enter_dependency(line, file, false);
 369                         result++;
 370                 } else {
 371                         /*
 372                          * If the file has an SCCS/s. file,
 373                          * we will detect that later on.
 374                          */
 375                         file->stat.has_sccs = NO_SCCS;
 376                 /*
 377                  * If this is an s. file, we also enter it as if it
 378                  * existed in the plain directory.
 379                  */
 380                 if ((dp->d_name[0] == 's') &&
 381                     (dp->d_name[1] == (int) period_char)) {
 382         
 383                         MBSTOWCS(tmp_wcs_buffer, dp->d_name + 2);
 384                         plain_file_name_p = plain_file_name;
 385                         (void) wscpy(plain_file_name_p, tmp_wcs_buffer);
 386                         plain_file = GETNAME(plain_file_name, FIND_LENGTH);
 387                         plain_file->stat.is_file = true;
 388                         plain_file->stat.has_sccs = HAS_SCCS;
 389                         /*
 390                          * Enter the s. file as a dependency for the
 391                          * plain file.
 392                          */
 393                         maybe_append_prop(plain_file, sccs_prop)->
 394                           body.sccs.file = file;
 395                         MBSTOWCS(tmp_wcs_buffer, dp->d_name + 2);
 396                         if ((pattern != NULL) &&
 397                             amatch(tmp_wcs_buffer, pattern)) {
 398                                 if (debug_level > 0) {
 399                                         WCSTOMBS(mbs_buffer, pattern);
 400                                         (void) printf(catgets(catd, 1, 232, "'%s: %s' due to %s expansion\n"),
 401                                                       line->body.line.target->
 402                                                       string_mb,
 403                                                       plain_file->string_mb,
 404                                                       mbs_buffer);
 405                                 }
 406                                 enter_dependency(line, plain_file, false);
 407                                 result++;
 408                         }
 409                 }
 410               }
 411         }
 412         (void) closedir(dir_fd);
 413         if ((vpath != NULL) && (*vpath != (int) nul_char)) {
 414                 while ((*vpath != (int) nul_char) &&
 415                        (iswspace(*vpath) || (*vpath == (int) colon_char))) {
 416                         vpath++;
 417                 }
 418                 p = vpath;
 419                 while ((*vpath != (int) colon_char) &&
 420                        (*vpath != (int) nul_char)) {
 421                         vpath++;
 422                 }
 423                 if (vpath > p) {
 424                         dir = GETNAME(p, vpath - p);
 425                         goto vpath_loop;
 426                 }
 427         }
 428 /*
 429  * look into SCCS directory only if it's not svr4. For svr4 dont do that.
 430  */
 431 
 432 /*
 433  * Now read the SCCS directory.
 434  * Files in the SCSC directory are considered to be part of the set of
 435  * files in the plain directory. They are also entered in their own right.
 436  * Prepare the string where we build the true name of the SCCS files.
 437  */
 438         (void) wsncpy(plain_file_name,
 439                       file_name,
 440                       file_name_p - file_name);
 441         plain_file_name[file_name_p - file_name] = 0;
 442         plain_file_name_p = plain_file_name + wslen(plain_file_name);
 443 
 444         if(!svr4) {
 445 
 446           if (sccs_dir_path != NULL) {
 447                 wchar_t         tmp_wchar;
 448                 wchar_t         path[MAXPATHLEN];
 449                 char            mb_path[MAXPATHLEN];
 450 
 451                 if (file_name_p - file_name > 0) {
 452                         tmp_wchar = *file_name_p;
 453                         *file_name_p = 0;
 454                         WCSTOMBS(mbs_buffer, file_name);
 455                         (void) sprintf(mb_path, NOCATGETS("%s/%s/SCCS"),
 456                                         sccs_dir_path,
 457                                         mbs_buffer);
 458                         *file_name_p = tmp_wchar;
 459                 } else {
 460                         (void) sprintf(mb_path, NOCATGETS("%s/SCCS"), sccs_dir_path);
 461                 }
 462                 MBSTOWCS(path, mb_path);
 463                 (void) wscpy(file_name, path);
 464           } else {
 465                 MBSTOWCS(wcs_buffer, NOCATGETS("SCCS"));
 466                 (void) wscpy(file_name_p, wcs_buffer);
 467           }
 468         } else {
 469                 MBSTOWCS(wcs_buffer, NOCATGETS("."));
 470                 (void) wscpy(file_name_p, wcs_buffer);
 471         }
 472         /* Internalize the constructed SCCS dir name. */
 473         (void) exists(dir = GETNAME(file_name, FIND_LENGTH));
 474         /* Just give up if the directory file doesnt exist. */
 475         if (!dir->stat.is_file) {
 476                 return result;
 477         }
 478         /* Open the directory. */
 479         dir_fd = opendir(dir->string_mb);
 480         if (dir_fd == NULL) {
 481                 return result;
 482         }
 483         MBSTOWCS(wcs_buffer, "/");
 484         (void) wscat(file_name, wcs_buffer);
 485         file_name_p = file_name + wslen(file_name);
 486 
 487         while ((dp = readdir(dir_fd)) != NULL) {
 488                 if ((dp->d_fileno == 0) ||
 489                     ((dp->d_name[0] == (int) period_char) &&
 490                      ((dp->d_name[1] == 0) ||
 491                       ((dp->d_name[1] == (int) period_char) &&
 492                        (dp->d_name[2] == 0))))) {
 493                         continue;
 494                 }
 495                 /* Construct and internalize the true name of the SCCS file. */
 496                 MBSTOWCS(wcs_buffer, dp->d_name);
 497                 (void) wscpy(file_name_p, wcs_buffer);
 498                 file = GETNAME(file_name, FIND_LENGTH);
 499                 file->stat.is_file = true;
 500                 file->stat.has_sccs = NO_SCCS;
 501                 /*
 502                  * If this is an s. file, we also enter it as if it
 503                  * existed in the plain directory.
 504                  */
 505                 if ((dp->d_name[0] == 's') &&
 506                     (dp->d_name[1] == (int) period_char)) {
 507         
 508                         MBSTOWCS(wcs_buffer, dp->d_name + 2);
 509                         (void) wscpy(plain_file_name_p, wcs_buffer);
 510                         plain_file = GETNAME(plain_file_name, FIND_LENGTH);
 511                         plain_file->stat.is_file = true;
 512                         plain_file->stat.has_sccs = HAS_SCCS;
 513                                 /* if sccs dependency is already set,skip */
 514                         if(plain_file->prop) {
 515                                 Property sprop = get_prop(plain_file->prop,sccs_prop);
 516                                 if(sprop != NULL) {
 517                                         if (sprop->body.sccs.file) {
 518                                                 goto try_pattern;
 519                                         }
 520                                 }
 521                         }
 522 
 523                         /*
 524                          * Enter the s. file as a dependency for the
 525                          * plain file.
 526                          */
 527                         maybe_append_prop(plain_file, sccs_prop)->
 528                           body.sccs.file = file;
 529 try_pattern:
 530                         MBSTOWCS(tmp_wcs_buffer, dp->d_name + 2);
 531                         if ((pattern != NULL) &&
 532                             amatch(tmp_wcs_buffer, pattern)) {
 533                                 if (debug_level > 0) {
 534                                         WCSTOMBS(mbs_buffer, pattern);
 535                                         (void) printf(catgets(catd, 1, 233, "'%s: %s' due to %s expansion\n"),
 536                                                       line->body.line.target->
 537                                                       string_mb,
 538                                                       plain_file->string_mb,
 539                                                       mbs_buffer);
 540                                 }
 541                                 enter_dependency(line, plain_file, false);
 542                                 result++;
 543                         }
 544                 }
 545         }
 546         (void) closedir(dir_fd);
 547 
 548         return result;
 549 }
 550 
 551 /*
 552  *      enter_file_name(name_string, library)
 553  *
 554  *      Helper function for read_dir().
 555  *
 556  *      Return value:
 557  *                              The Name that was entered
 558  *
 559  *      Parameters:
 560  *              name_string     Name of the file we want to enter
 561  *              library         The library it is a member of, if any
 562  *
 563  *      Global variables used:
 564  */
 565 static Name
 566 enter_file_name(wchar_t *name_string, wchar_t *library)
 567 {
 568         wchar_t         buffer[STRING_BUFFER_LENGTH];
 569         String_rec      lib_name;
 570         Name            name;
 571         Property        prop;
 572 
 573         if (library == NULL) {
 574                 name = GETNAME(name_string, FIND_LENGTH);
 575                 name->stat.is_file = true;
 576                 return name;
 577         }
 578 
 579         INIT_STRING_FROM_STACK(lib_name, buffer);
 580         append_string(library, &lib_name, FIND_LENGTH);
 581         append_char((int) parenleft_char, &lib_name);
 582         append_string(name_string, &lib_name, FIND_LENGTH);
 583         append_char((int) parenright_char, &lib_name);
 584 
 585         name = GETNAME(lib_name.buffer.start, FIND_LENGTH);
 586         name->stat.is_file = true;
 587         name->is_member = true;
 588         prop = maybe_append_prop(name, member_prop);
 589         prop->body.member.library = GETNAME(library, FIND_LENGTH);
 590         prop->body.member.library->stat.is_file = true;
 591         prop->body.member.entry = NULL;
 592         prop->body.member.member = GETNAME(name_string, FIND_LENGTH);
 593         prop->body.member.member->stat.is_file = true;
 594         return name;
 595 }
 596 
 597 /*
 598  *      star_match(string, pattern)
 599  *
 600  *      This is a regular shell type wildcard pattern matcher
 601  *      It is used when xpanding wildcards in dependency lists
 602  *
 603  *      Return value:
 604  *                              Indication if the string matched the pattern
 605  *
 606  *      Parameters:
 607  *              string          String to match
 608  *              pattern         Pattern to match it against
 609  *
 610  *      Global variables used:
 611  */
 612 static Boolean
 613 star_match(register wchar_t *string, register wchar_t *pattern)
 614 {
 615         register int            pattern_ch;
 616 
 617         switch (*pattern) {
 618         case 0:
 619                 return succeeded;
 620         case bracketleft_char:
 621         case question_char:
 622         case asterisk_char:
 623                 while (*string) {
 624                         if (amatch(string++, pattern)) {
 625                                 return succeeded;
 626                         }
 627                 }
 628                 break;
 629         default:
 630                 pattern_ch = (int) *pattern++;
 631                 while (*string) {
 632                         if ((*string++ == pattern_ch) &&
 633                             amatch(string, pattern)) {
 634                                 return succeeded;
 635                         }
 636                 }
 637                 break;
 638         }
 639         return failed;
 640 }
 641 
 642 /*
 643  *      amatch(string, pattern)
 644  *
 645  *      Helper function for shell pattern matching
 646  *
 647  *      Return value:
 648  *                              Indication if the string matched the pattern
 649  *
 650  *      Parameters:
 651  *              string          String to match
 652  *              pattern         Pattern to match it against
 653  *
 654  *      Global variables used:
 655  */
 656 static Boolean
 657 amatch(register wchar_t *string, register wchar_t *pattern)
 658 {
 659         register long           lower_bound;
 660         register long           string_ch;
 661         register long           pattern_ch;
 662         register int            k;
 663 
 664 top:
 665         for (; 1; pattern++, string++) {
 666                 lower_bound = 017777777777;
 667                 string_ch = *string;
 668                 switch (pattern_ch = *pattern) {
 669                 case bracketleft_char:
 670                         k = 0;
 671                         while ((pattern_ch = *++pattern) != 0) {
 672                                 switch (pattern_ch) {
 673                                 case bracketright_char:
 674                                         if (!k) {
 675                                                 return failed;
 676                                         }
 677                                         string++;
 678                                         pattern++;
 679                                         goto top;
 680                                 case hyphen_char:
 681                                         k |= (lower_bound <= string_ch) &&
 682                                              (string_ch <=
 683                                               (pattern_ch = pattern[1]));
 684                                 default:
 685                                         if (string_ch ==
 686                                             (lower_bound = pattern_ch)) {
 687                                                 k++;
 688                                         }
 689                                 }
 690                         }
 691                         return failed;
 692                 case asterisk_char:
 693                         return star_match(string, ++pattern);
 694                 case 0:
 695                         return BOOLEAN(!string_ch);
 696                 case question_char:
 697                         if (string_ch == 0) {
 698                                 return failed;
 699                         }
 700                         break;
 701                 default:
 702                         if (pattern_ch != string_ch) {
 703                                 return failed;
 704                         }
 705                         break;
 706                 }
 707         }
 708         /* NOTREACHED */
 709 }
 710