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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 27 /* All Rights Reserved */ 28 29 #include <unistd.h> 30 #include <stdlib.h> 31 #include <sys/types.h> 32 #include <ctype.h> 33 #include <string.h> 34 #include <pwd.h> 35 #include <grp.h> 36 #include <signal.h> 37 #include "ttymon.h" 38 #include "tmstruct.h" 39 #include "tmextern.h" 40 41 static int get_flags(char *, long *); 42 static int get_ttyflags(char *, long *); 43 static int same_entry(struct pmtab *, struct pmtab *); 44 static int check_pmtab(struct pmtab *); 45 static void insert_pmtab(struct pmtab *); 46 static void free_pmtab(struct pmtab *); 47 static char *expand(char *, char *); 48 49 int check_identity(struct pmtab *); 50 51 /* 52 * read_pmtab() 53 * - read and parse pmtab 54 * - store table in linked list pointed by global variable "PMtab" 55 * - exit if file does not exist or error detected. 56 */ 57 void 58 read_pmtab(void) 59 { 60 struct pmtab *gptr; 61 char *ptr, *wptr; 62 FILE *fp; 63 int input, state, size, rawc, field, linenum; 64 char oldc; 65 char line[BUFSIZ]; 66 char wbuf[BUFSIZ]; 67 static char *states[] = { 68 "", "tag", "flags", "identity", "reserved1", "reserved2", 69 "reserved3", "device", "ttyflags", "count", "service", "timeout", 70 "ttylabel", "modules", "prompt", "disable msg", "terminal type", 71 "soft-carrier" 72 }; 73 74 #ifdef DEBUG 75 debug("in read_pmtab"); 76 #endif 77 78 if ((fp = fopen(PMTABFILE, "r")) == NULL) { 79 fatal("open pmtab (%s) failed", PMTABFILE); 80 } 81 82 Nentries = 0; 83 if (check_version(PMTAB_VERS, PMTABFILE) != 0) 84 fatal("check pmtab version failed"); 85 86 for (gptr = PMtab; gptr; gptr = gptr->p_next) { 87 if ((gptr->p_status == SESSION) || 88 (gptr->p_status == LOCKED) || 89 (gptr->p_status == UNACCESS)) { 90 if (gptr->p_fd > 0) { 91 (void) close(gptr->p_fd); 92 gptr->p_fd = 0; 93 } 94 gptr->p_inservice = gptr->p_status; 95 } 96 gptr->p_status = NOTVALID; 97 } 98 99 wptr = wbuf; 100 input = ACTIVE; 101 linenum = 0; 102 field = FAILURE; 103 do { 104 linenum++; 105 line[0] = '\0'; 106 for (ptr = line, oldc = '\0'; ptr < &line[sizeof (line) - 1] && 107 (rawc = getc(fp)) != '\n' && rawc != EOF; 108 ptr++, oldc = (char)rawc) { 109 if ((rawc == '#') && (oldc != '\\')) 110 break; 111 *ptr = (char)rawc; 112 } 113 *ptr = '\0'; 114 115 /* skip rest of the line */ 116 if (rawc != EOF && rawc != '\n') { 117 if (rawc != '#') 118 log("Entry too long.\n"); 119 while ((rawc = getc(fp)) != EOF && rawc != '\n') 120 ; 121 } 122 123 if (rawc == EOF) { 124 if (ptr == line) 125 break; 126 else 127 input = FINISHED; 128 } 129 130 /* if empty line, skip */ 131 for (ptr = line; *ptr != '\0' && isspace(*ptr); ptr++) 132 ; 133 if (*ptr == '\0') 134 continue; 135 136 #ifdef DEBUG 137 debug("**** Next Entry ****\n%s", line); 138 #endif 139 log("Processing pmtab line #%d", linenum); 140 141 /* Now we have the complete line */ 142 143 if ((gptr = ALLOC_PMTAB) == NULL) 144 fatal("memory allocation failed"); 145 146 /* set hangup flag, this is the default */ 147 gptr->p_ttyflags |= H_FLAG; 148 149 /* 150 * For compatibility reasons, we cannot rely on these 151 * having values assigned from pmtab. 152 */ 153 gptr->p_termtype = ""; 154 gptr->p_softcar = ""; 155 156 for (state = P_TAG, ptr = line; state != FAILURE && 157 state != SUCCESS;) { 158 switch (state) { 159 case P_TAG: 160 gptr->p_tag = strsave(getword(ptr, &size, 0)); 161 break; 162 case P_FLAGS: 163 (void) strcpy(wptr, getword(ptr, &size, 0)); 164 if ((get_flags(wptr, &gptr->p_flags)) != 0) { 165 field = state; 166 state = FAILURE; 167 } 168 break; 169 case P_IDENTITY: 170 gptr->p_identity = strsave( 171 getword(ptr, &size, 0)); 172 break; 173 case P_RES1: 174 gptr->p_res1 = strsave(getword(ptr, &size, 0)); 175 break; 176 case P_RES2: 177 gptr->p_res2 = strsave(getword(ptr, &size, 0)); 178 break; 179 case P_RES3: 180 gptr->p_res3 = strsave(getword(ptr, &size, 0)); 181 break; 182 case P_DEVICE: 183 gptr->p_device = strsave( 184 getword(ptr, &size, 0)); 185 break; 186 case P_TTYFLAGS: 187 (void) strcpy(wptr, getword(ptr, &size, 0)); 188 if (get_ttyflags(wptr, 189 &gptr->p_ttyflags) != 0) { 190 field = state; 191 state = FAILURE; 192 } 193 break; 194 case P_COUNT: 195 (void) strcpy(wptr, getword(ptr, &size, 0)); 196 if (strcheck(wptr, NUM) != 0) { 197 log("wait_read count must be a " 198 "positive number"); 199 field = state; 200 state = FAILURE; 201 } else { 202 gptr->p_count = atoi(wptr); 203 } 204 break; 205 case P_SERVER: 206 gptr->p_server = 207 strsave(expand(getword(ptr, &size, 1), 208 gptr->p_device)); 209 break; 210 case P_TIMEOUT: 211 (void) strcpy(wptr, getword(ptr, &size, 0)); 212 if (strcheck(wptr, NUM) != 0) { 213 log("timeout value must be a positive " 214 "number"); 215 field = state; 216 state = FAILURE; 217 } else { 218 gptr->p_timeout = atoi(wptr); 219 } 220 break; 221 case P_TTYLABEL: 222 gptr->p_ttylabel = strsave(getword(ptr, 223 &size, 0)); 224 break; 225 case P_MODULES: 226 gptr->p_modules = strsave(getword(ptr, 227 &size, 0)); 228 if (vml(gptr->p_modules) != 0) { 229 field = state; 230 state = FAILURE; 231 } 232 break; 233 case P_PROMPT: 234 gptr->p_prompt = strsave(getword(ptr, &size, 235 TRUE)); 236 break; 237 case P_DMSG: 238 gptr->p_dmsg = strsave(getword(ptr, &size, 239 TRUE)); 240 break; 241 242 case P_TERMTYPE: 243 gptr->p_termtype = strsave(getword(ptr, 244 &size, TRUE)); 245 break; 246 247 case P_SOFTCAR: 248 gptr->p_softcar = strsave(getword(ptr, 249 &size, TRUE)); 250 break; 251 252 } /* end switch */ 253 ptr += size; 254 if (state == FAILURE) 255 break; 256 if (*ptr == ':') { 257 ptr++; /* Skip the ':' */ 258 state++; 259 } else if (*ptr != '\0') { 260 field = state; 261 state = FAILURE; 262 } 263 if (*ptr == '\0') { 264 /* 265 * Maintain compatibility with older ttymon 266 * pmtab files. If Sun-added fields are 267 * missing, this should not be an error. 268 */ 269 if (state > P_DMSG) { 270 state = SUCCESS; 271 } else { 272 field = state; 273 state = FAILURE; 274 } 275 } 276 } /* end for loop */ 277 278 if (state == SUCCESS) { 279 if (check_pmtab(gptr) == 0) { 280 if (Nentries < Maxfds) { 281 insert_pmtab(gptr); 282 } else { 283 log("can't add more entries to " 284 "pmtab, Maxfds = %d", Maxfds); 285 free_pmtab(gptr); 286 (void) fclose(fp); 287 return; 288 } 289 } else { 290 log("Parsing failure for entry: \n%s", line); 291 log("----------------------------------------" 292 "---"); 293 free_pmtab(gptr); 294 } 295 } else { 296 *++ptr = '\0'; 297 log("Parsing failure in the \"%s\" field,\n%s" 298 "<--error detected here", states[field], line); 299 log("-------------------------------------------"); 300 free_pmtab(gptr); 301 } 302 } while (input == ACTIVE); 303 304 (void) fclose(fp); 305 } 306 307 /* 308 * get_flags - scan flags field to set U_FLAG and X_FLAG 309 */ 310 static int 311 get_flags(char *wptr, long *flags) 312 { 313 char *p; 314 for (p = wptr; *p; p++) { 315 switch (*p) { 316 case 'x': 317 *flags |= X_FLAG; 318 break; 319 case 'u': 320 *flags |= U_FLAG; 321 break; 322 default: 323 log("Invalid flag -- %c", *p); 324 return (-1); 325 } 326 } 327 return (0); 328 } 329 330 /* 331 * get_ttyflags - scan ttyflags field to set corresponding flags 332 * char *wptr pointer to the input string 333 * long *ttyflags pointer to the flag to be set 334 */ 335 static int 336 get_ttyflags(char *wptr, long *ttyflags) 337 { 338 char *p; 339 for (p = wptr; *p; p++) { 340 switch (*p) { 341 case 'c': 342 *ttyflags |= C_FLAG; 343 break; 344 case 'h': /* h means don't hangup */ 345 *ttyflags &= ~H_FLAG; 346 break; 347 case 'b': 348 *ttyflags |= B_FLAG; 349 break; 350 case 'r': 351 *ttyflags |= R_FLAG; 352 break; 353 case 'I': 354 *ttyflags |= I_FLAG; 355 break; 356 default: 357 log("Invalid ttyflag -- %c", *p); 358 return (-1); 359 } 360 } 361 return (0); 362 } 363 364 #ifdef DEBUG 365 /* 366 * pflags - put service flags into intelligible form for output 367 * long flags - binary representation of the flags 368 */ 369 370 char * 371 pflags(long flags) 372 { 373 int i; /* scratch counter */ 374 static char buf[BUFSIZ]; /* formatted flags */ 375 376 if (flags == 0) 377 return ("-"); 378 i = 0; 379 if (flags & U_FLAG) { 380 buf[i++] = 'u'; 381 flags &= ~U_FLAG; 382 } 383 if (flags & X_FLAG) { 384 buf[i++] = 'x'; 385 flags &= ~X_FLAG; 386 } 387 if (flags) 388 log("Internal error in pflags"); 389 buf[i] = '\0'; 390 return (buf); 391 } 392 393 /* 394 * pttyflags - put ttyflags into intelligible form for output 395 * long flags - binary representation of ttyflags 396 */ 397 398 char * 399 pttyflags(long flags) 400 { 401 int i; /* scratch counter */ 402 static char buf[BUFSIZ]; /* formatted flags */ 403 404 if (flags == 0) 405 return ("h"); 406 i = 0; 407 if (flags & C_FLAG) { 408 buf[i++] = 'c'; 409 flags &= ~C_FLAG; 410 } 411 if (flags & H_FLAG) 412 flags &= ~H_FLAG; 413 else 414 buf[i++] = 'h'; 415 if (flags & B_FLAG) { 416 buf[i++] = 'b'; 417 flags &= ~B_FLAG; 418 } 419 if (flags & R_FLAG) { 420 buf[i++] = 'r'; 421 flags &= ~B_FLAG; 422 } 423 if (flags & I_FLAG) { 424 buf[i++] = 'I'; 425 flags &= ~I_FLAG; 426 } 427 if (flags) 428 log("Internal error in p_ttyflags"); 429 buf[i] = '\0'; 430 return (buf); 431 } 432 433 void 434 dump_pmtab(void) 435 { 436 struct pmtab *gptr; 437 438 debug("in dump_pmtab"); 439 log("********** dumping pmtab **********"); 440 log(" "); 441 for (gptr = PMtab; gptr != NULL; gptr = gptr->p_next) { 442 log("-------------------------------------------"); 443 log("tag:\t\t%s", gptr->p_tag); 444 log("flags:\t\t%s", pflags(gptr->p_flags)); 445 log("identity:\t%s", gptr->p_identity); 446 log("reserved1:\t%s", gptr->p_res1); 447 log("reserved2:\t%s", gptr->p_res2); 448 log("reserved3:\t%s", gptr->p_res3); 449 log("device:\t%s", gptr->p_device); 450 log("ttyflags:\t%s", pttyflags(gptr->p_ttyflags)); 451 log("count:\t\t%d", gptr->p_count); 452 log("server:\t%s", gptr->p_server); 453 log("timeout:\t%d", gptr->p_timeout); 454 log("ttylabel:\t%s", gptr->p_ttylabel); 455 log("modules:\t%s", gptr->p_modules); 456 log("prompt:\t%s", gptr->p_prompt); 457 log("disable msg:\t%s", gptr->p_dmsg); 458 log("terminal type:\t%s", gptr->p_termtype); 459 log("soft-carrier:\t%s", gptr->p_softcar); 460 log("status:\t\t%d", gptr->p_status); 461 log("inservice:\t%d", gptr->p_inservice); 462 log("fd:\t\t%d", gptr->p_fd); 463 log("pid:\t\t%ld", gptr->p_childpid); 464 log("uid:\t\t%ld", gptr->p_uid); 465 log("gid:\t\t%ld", gptr->p_gid); 466 log("dir:\t%s", gptr->p_dir); 467 log(" "); 468 } 469 log("********** end dumping pmtab **********"); 470 } 471 #endif 472 473 /* 474 * same_entry(e1,e2) - compare 2 entries of pmtab 475 * if the fields are different, copy e2 to e1 476 * return 1 if same, return 0 if different 477 */ 478 static int 479 same_entry(struct pmtab *e1, struct pmtab *e2) 480 { 481 482 if (strcmp(e1->p_identity, e2->p_identity) != 0) 483 return (0); 484 if (strcmp(e1->p_res1, e2->p_res1) != 0) 485 return (0); 486 if (strcmp(e1->p_res2, e2->p_res2) != 0) 487 return (0); 488 if (strcmp(e1->p_res3, e2->p_res3) != 0) 489 return (0); 490 if (strcmp(e1->p_device, e2->p_device) != 0) 491 return (0); 492 if (strcmp(e1->p_server, e2->p_server) != 0) 493 return (0); 494 if (strcmp(e1->p_ttylabel, e2->p_ttylabel) != 0) 495 return (0); 496 if (strcmp(e1->p_modules, e2->p_modules) != 0) 497 return (0); 498 if (strcmp(e1->p_prompt, e2->p_prompt) != 0) 499 return (0); 500 if (strcmp(e1->p_dmsg, e2->p_dmsg) != 0) 501 return (0); 502 if (strcmp(e1->p_termtype, e2->p_termtype) != 0) 503 return (0); 504 if (strcmp(e1->p_softcar, e2->p_softcar) != 0) 505 return (0); 506 if (e1->p_flags != e2->p_flags) 507 return (0); 508 /* 509 * compare lowest 4 bits only, 510 * because A_FLAG is not part of original ttyflags 511 */ 512 if ((e1->p_ttyflags & ~A_FLAG) != (e2->p_ttyflags & ~A_FLAG)) 513 return (0); 514 if (e1->p_count != e2->p_count) 515 return (0); 516 if (e1->p_timeout != e2->p_timeout) 517 return (0); 518 if (e1->p_uid != e2->p_uid) 519 return (0); 520 if (e1->p_gid != e2->p_gid) 521 return (0); 522 if (strcmp(e1->p_dir, e2->p_dir) != 0) 523 return (0); 524 return (1); 525 } 526 527 528 /* 529 * insert_pmtab - insert a pmtab entry into the linked list 530 */ 531 532 static void 533 insert_pmtab(struct pmtab *sp) 534 { 535 struct pmtab *tsp, *savtsp; /* scratch pointers */ 536 int ret; /* strcmp return value */ 537 538 #ifdef DEBUG 539 debug("in insert_pmtab"); 540 #endif 541 savtsp = tsp = PMtab; 542 543 /* 544 * find the correct place to insert this element 545 */ 546 547 while (tsp) { 548 ret = strcmp(sp->p_tag, tsp->p_tag); 549 if (ret > 0) { 550 /* keep on looking */ 551 savtsp = tsp; 552 tsp = tsp->p_next; 553 continue; 554 } else if (ret == 0) { 555 if (tsp->p_status) { 556 /* this is a duplicate entry, ignore it */ 557 log("Ignoring duplicate entry for <%s>", 558 tsp->p_tag); 559 } else { 560 if (same_entry(tsp, sp)) { /* same entry */ 561 tsp->p_status = VALID; 562 } else { /* entry changed */ 563 if ((sp->p_flags & X_FLAG) && 564 ((sp->p_dmsg == NULL) || 565 (*(sp->p_dmsg) == '\0'))) { 566 /* disabled entry */ 567 tsp->p_status = NOTVALID; 568 } else { 569 #ifdef DEBUG 570 debug("replacing <%s>", 571 sp->p_tag); 572 #endif 573 /* replace old entry */ 574 sp->p_next = tsp->p_next; 575 if (tsp == PMtab) { 576 PMtab = sp; 577 } else { 578 savtsp->p_next = sp; 579 } 580 sp->p_status = CHANGED; 581 sp->p_fd = tsp->p_fd; 582 sp->p_childpid = 583 tsp->p_childpid; 584 sp->p_inservice = 585 tsp->p_inservice; 586 sp = tsp; 587 } 588 } 589 Nentries++; 590 } 591 free_pmtab(sp); 592 return; 593 } else { 594 if ((sp->p_flags & X_FLAG) && 595 ((sp->p_dmsg == NULL) || 596 (*(sp->p_dmsg) == '\0'))) { /* disabled entry */ 597 free_pmtab(sp); 598 return; 599 } 600 /* 601 * Set the state of soft-carrier. 602 * Since this is a one-time only operation, 603 * we do it when this service is added to 604 * the enabled list. 605 */ 606 if (*sp->p_softcar != '\0') 607 set_softcar(sp); 608 609 /* insert it here */ 610 if (tsp == PMtab) { 611 sp->p_next = PMtab; 612 PMtab = sp; 613 } else { 614 sp->p_next = savtsp->p_next; 615 savtsp->p_next = sp; 616 } 617 #ifdef DEBUG 618 debug("adding <%s>", sp->p_tag); 619 #endif 620 Nentries++; 621 /* this entry is "current" */ 622 sp->p_status = VALID; 623 return; 624 } 625 } 626 627 /* 628 * either an empty list or should put element at end of list 629 */ 630 631 if ((sp->p_flags & X_FLAG) && 632 ((sp->p_dmsg == NULL) || 633 (*(sp->p_dmsg) == '\0'))) { /* disabled entry */ 634 free_pmtab(sp); /* do not poll this entry */ 635 return; 636 } 637 /* 638 * Set the state of soft-carrier. 639 * Since this is a one-time only operation, 640 * we do it when this service is added to 641 * the enabled list. 642 */ 643 if (*sp->p_softcar != '\0') 644 set_softcar(sp); 645 sp->p_next = NULL; 646 if (PMtab == NULL) 647 PMtab = sp; 648 else 649 savtsp->p_next = sp; 650 #ifdef DEBUG 651 debug("adding <%s>", sp->p_tag); 652 #endif 653 ++Nentries; 654 /* this entry is "current" */ 655 sp->p_status = VALID; 656 } 657 658 659 /* 660 * purge - purge linked list of "old" entries 661 */ 662 void 663 purge(void) 664 { 665 struct pmtab *sp; /* working pointer */ 666 struct pmtab *savesp, *tsp; /* scratch pointers */ 667 668 #ifdef DEBUG 669 debug("in purge"); 670 #endif 671 sp = savesp = PMtab; 672 while (sp) { 673 if (sp->p_status) { 674 #ifdef DEBUG 675 debug("p_status not 0"); 676 #endif 677 savesp = sp; 678 sp = sp->p_next; 679 } else { 680 tsp = sp; 681 if (tsp == PMtab) { 682 PMtab = sp->p_next; 683 savesp = PMtab; 684 } else { 685 savesp->p_next = sp->p_next; 686 } 687 #ifdef DEBUG 688 debug("purging <%s>", sp->p_tag); 689 #endif 690 sp = sp->p_next; 691 free_pmtab(tsp); 692 } 693 } 694 } 695 696 /* 697 * free_pmtab - free one pmtab entry 698 */ 699 static void 700 free_pmtab(struct pmtab *p) 701 { 702 #ifdef DEBUG 703 debug("in free_pmtab"); 704 #endif 705 free(p->p_tag); 706 free(p->p_identity); 707 free(p->p_res1); 708 free(p->p_res2); 709 free(p->p_res3); 710 free(p->p_device); 711 free(p->p_server); 712 free(p->p_ttylabel); 713 free(p->p_modules); 714 free(p->p_prompt); 715 free(p->p_dmsg); 716 free(p->p_termtype); 717 free(p->p_softcar); 718 free(p->p_dir); 719 free(p); 720 } 721 722 /* 723 * check_pmtab - check the fields to make sure things are correct 724 * - return 0 if everything is ok 725 * - return -1 if something is wrong 726 */ 727 static int 728 check_pmtab(struct pmtab *p) 729 { 730 if (p == NULL) { 731 log("pmtab ptr is NULL"); 732 return (-1); 733 } 734 735 /* check service tag */ 736 if ((p->p_tag == NULL) || (*(p->p_tag) == '\0')) { 737 log("port/service tag is missing"); 738 return (-1); 739 } 740 if (strlen(p->p_tag) > (size_t)(MAXID - 1)) { 741 log("port/service tag <%s> is longer than %d", p->p_tag, 742 MAXID-1); 743 return (-1); 744 } 745 if (strcheck(p->p_tag, ALNUM) != 0) { 746 log("port/service tag <%s> is not alphanumeric", p->p_tag); 747 return (-1); 748 } 749 if (check_identity(p) != 0) { 750 return (-1); 751 } 752 753 if (check_device(p->p_device) != 0) 754 return (-1); 755 756 if (check_cmd(p->p_server) != 0) 757 return (-1); 758 return (0); 759 } 760 761 /* 762 * check_identity - check to see if the identity is a valid user 763 * - log name in the passwd file, 764 * - and if its group id is a valid one 765 * - return 0 if everything is ok. Otherwise, return -1 766 */ 767 768 int 769 check_identity(struct pmtab *p) 770 { 771 struct passwd *pwdp; 772 773 if ((p->p_identity == NULL) || (*(p->p_identity) == '\0')) { 774 log("identity field is missing"); 775 return (-1); 776 } 777 if ((pwdp = getpwnam(p->p_identity)) == NULL) { 778 log("missing or bad passwd entry for <%s>", p->p_identity); 779 endpwent(); 780 return (-1); 781 } 782 if (getgrgid(pwdp->pw_gid) == NULL) { 783 log("no group entry for %ld", pwdp->pw_gid); 784 endgrent(); 785 endpwent(); 786 return (-1); 787 } 788 p->p_uid = pwdp->pw_uid; 789 p->p_gid = pwdp->pw_gid; 790 p->p_dir = strsave(pwdp->pw_dir); 791 endgrent(); 792 endpwent(); 793 return (0); 794 } 795 796 /* 797 * expand(cmdp, devp) - expand %d to device name and %% to %, 798 * - any other characters are untouched. 799 * - return the expanded string 800 */ 801 static char * 802 expand(char *cmdp, char *devp) 803 { 804 char *cp, *dp, *np; 805 static char buf[BUFSIZ]; 806 cp = cmdp; 807 np = buf; 808 dp = devp; 809 while (*cp) { 810 if (*cp != '%') { 811 *np++ = *cp++; 812 continue; 813 } 814 switch (*++cp) { 815 case 'd': 816 while (*dp) { 817 *np++ = *dp++; 818 } 819 cp++; 820 break; 821 case '%': 822 *np++ = *cp++; 823 break; 824 default: 825 *np++ = *cp++; 826 break; 827 } 828 } 829 *np = '\0'; 830 return (buf); 831 } 832