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