1 /* $NetBSD: quota.c,v 1.23 2001/03/29 21:54:01 cgd Exp $ */ 2 3 /* 4 * Copyright (c) 1980, 1990, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Robert Elz at The University of Melbourne. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. All advertising materials mentioning features or use of this software 19 * must display the following acknowledgement: 20 * This product includes software developed by the University of 21 * California, Berkeley and its contributors. 22 * 4. Neither the name of the University nor the names of its contributors 23 * may be used to endorse or promote products derived from this software 24 * without specific prior written permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 29 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 36 * SUCH DAMAGE. 37 */ 38 39 #include <sys/cdefs.h> 40 #ifndef lint 41 __COPYRIGHT("@(#) Copyright (c) 1980, 1990, 1993\n\ 42 The Regents of the University of California. All rights reserved.\n"); 43 #endif /* not lint */ 44 45 #ifndef lint 46 #if 0 47 static char sccsid[] = "@(#)quota.c 8.4 (Berkeley) 4/28/95"; 48 #else 49 __RCSID("$NetBSD: quota.c,v 1.23 2001/03/29 21:54:01 cgd Exp $"); 50 #endif 51 #endif /* not lint */ 52 53 /* 54 * Disk quota reporting program. 55 */ 56 #include <sys/param.h> 57 #include <sys/types.h> 58 #include <sys/file.h> 59 #include <sys/stat.h> 60 #include <sys/mount.h> 61 #include <sys/socket.h> 62 #include <sys/queue.h> 63 64 #include <ufs/ufs/quota.h> 65 #include <ctype.h> 66 #include <err.h> 67 #include <errno.h> 68 #include <fstab.h> 69 #include <grp.h> 70 #include <netdb.h> 71 #include <pwd.h> 72 #include <stdio.h> 73 #include <stdlib.h> 74 #include <string.h> 75 #include <time.h> 76 #include <unistd.h> 77 78 #include <rpc/rpc.h> 79 #include <rpc/pmap_prot.h> 80 #include <rpcsvc/rquota.h> 81 82 char *qfname = QUOTAFILENAME; 83 char *qfextension[] = INITQFNAMES; 84 85 struct quotause { 86 struct quotause *next; 87 long flags; 88 struct dqblk dqblk; 89 char fsname[MAXPATHLEN + 1]; 90 }; 91 #define FOUND 0x01 92 93 int alldigits __P((char *)); 94 int callaurpc __P((char *, int, int, int, xdrproc_t, void *, 95 xdrproc_t, void *)); 96 int main __P((int, char **)); 97 int getnfsquota __P((struct statfs *, struct fstab *, struct quotause *, 98 long, int)); 99 struct quotause *getprivs __P((long id, int quotatype)); 100 int getufsquota __P((struct statfs *, struct fstab *, struct quotause *, 101 long, int)); 102 void heading __P((int, u_long, const char *, const char *)); 103 void showgid __P((gid_t)); 104 void showgrpname __P((const char *)); 105 void showquotas __P((int, u_long, const char *)); 106 void showuid __P((uid_t)); 107 void showusrname __P((const char *)); 108 char *timeprt __P((time_t seconds)); 109 int ufshasquota __P((struct fstab *, int, char **)); 110 void usage __P((void)); 111 112 int qflag; 113 int vflag; 114 uid_t myuid; 115 116 int 117 main(argc, argv) 118 int argc; 119 char *argv[]; 120 { 121 int ngroups; 122 gid_t mygid, gidset[NGROUPS]; 123 int i, gflag = 0, uflag = 0; 124 int ch; 125 126 myuid = getuid(); 127 while ((ch = getopt(argc, argv, "ugvq")) != -1) { 128 switch(ch) { 129 case 'g': 130 gflag++; 131 break; 132 case 'u': 133 uflag++; 134 break; 135 case 'v': 136 vflag++; 137 break; 138 case 'q': 139 qflag++; 140 break; 141 default: 142 usage(); 143 } 144 } 145 argc -= optind; 146 argv += optind; 147 if (!uflag && !gflag) 148 uflag++; 149 if (argc == 0) { 150 if (uflag) 151 showuid(myuid); 152 if (gflag) { 153 mygid = getgid(); 154 ngroups = getgroups(NGROUPS, gidset); 155 if (ngroups < 0) 156 err(1, "getgroups"); 157 showgid(mygid); 158 for (i = 0; i < ngroups; i++) 159 if (gidset[i] != mygid) 160 showgid(gidset[i]); 161 } 162 exit(0); 163 } 164 if (uflag && gflag) 165 usage(); 166 if (uflag) { 167 for (; argc > 0; argc--, argv++) { 168 if (alldigits(*argv)) 169 showuid(atoi(*argv)); 170 else 171 showusrname(*argv); 172 } 173 exit(0); 174 } 175 if (gflag) { 176 for (; argc > 0; argc--, argv++) { 177 if (alldigits(*argv)) 178 showgid(atoi(*argv)); 179 else 180 showgrpname(*argv); 181 } 182 exit(0); 183 } 184 /* NOTREACHED */ 185 return (0); 186 } 187 188 void 189 usage() 190 { 191 192 fprintf(stderr, "%s\n%s\n%s\n", 193 "Usage: quota [-guqv]", 194 "\tquota [-qv] -u username ...", 195 "\tquota [-qv] -g groupname ..."); 196 exit(1); 197 } 198 199 /* 200 * Print out quotas for a specified user identifier. 201 */ 202 void 203 showuid(uid) 204 uid_t uid; 205 { 206 struct passwd *pwd = getpwuid(uid); 207 const char *name; 208 209 if (pwd == NULL) 210 name = "(no account)"; 211 else 212 name = pwd->pw_name; 213 if (uid != myuid && myuid != 0) { 214 printf("quota: %s (uid %d): permission denied\n", name, uid); 215 return; 216 } 217 showquotas(USRQUOTA, uid, name); 218 } 219 220 /* 221 * Print out quotas for a specifed user name. 222 */ 223 void 224 showusrname(name) 225 const char *name; 226 { 227 struct passwd *pwd = getpwnam(name); 228 229 if (pwd == NULL) { 230 warnx("%s: unknown user", name); 231 return; 232 } 233 if (pwd->pw_uid != myuid && myuid != 0) { 234 warnx("%s (uid %d): permission denied", name, pwd->pw_uid); 235 return; 236 } 237 showquotas(USRQUOTA, pwd->pw_uid, name); 238 } 239 240 /* 241 * Print out quotas for a specified group identifier. 242 */ 243 void 244 showgid(gid) 245 gid_t gid; 246 { 247 struct group *grp = getgrgid(gid); 248 int ngroups; 249 gid_t mygid, gidset[NGROUPS]; 250 int i; 251 const char *name; 252 253 if (grp == NULL) 254 name = "(no entry)"; 255 else 256 name = grp->gr_name; 257 mygid = getgid(); 258 ngroups = getgroups(NGROUPS, gidset); 259 if (ngroups < 0) { 260 warn("getgroups"); 261 return; 262 } 263 if (gid != mygid) { 264 for (i = 0; i < ngroups; i++) 265 if (gid == gidset[i]) 266 break; 267 if (i >= ngroups && myuid != 0) { 268 warnx("%s (gid %d): permission denied", name, gid); 269 return; 270 } 271 } 272 showquotas(GRPQUOTA, gid, name); 273 } 274 275 /* 276 * Print out quotas for a specifed group name. 277 */ 278 void 279 showgrpname(name) 280 const char *name; 281 { 282 struct group *grp = getgrnam(name); 283 int ngroups; 284 gid_t mygid, gidset[NGROUPS]; 285 int i; 286 287 if (grp == NULL) { 288 warnx("%s: unknown group", name); 289 return; 290 } 291 mygid = getgid(); 292 ngroups = getgroups(NGROUPS, gidset); 293 if (ngroups < 0) { 294 warn("getgroups"); 295 return; 296 } 297 if (grp->gr_gid != mygid) { 298 for (i = 0; i < ngroups; i++) 299 if (grp->gr_gid == gidset[i]) 300 break; 301 if (i >= ngroups && myuid != 0) { 302 warnx("%s (gid %d): permission denied", 303 name, grp->gr_gid); 304 return; 305 } 306 } 307 showquotas(GRPQUOTA, grp->gr_gid, name); 308 } 309 310 void 311 showquotas(type, id, name) 312 int type; 313 u_long id; 314 const char *name; 315 { 316 struct quotause *qup; 317 struct quotause *quplist; 318 char *msgi, *msgb, *nam; 319 int lines = 0; 320 static time_t now; 321 322 if (now == 0) 323 time(&now); 324 quplist = getprivs(id, type); 325 for (qup = quplist; qup; qup = qup->next) { 326 if (!vflag && 327 qup->dqblk.dqb_isoftlimit == 0 && 328 qup->dqblk.dqb_ihardlimit == 0 && 329 qup->dqblk.dqb_bsoftlimit == 0 && 330 qup->dqblk.dqb_bhardlimit == 0) 331 continue; 332 msgi = (char *)0; 333 if (qup->dqblk.dqb_ihardlimit && 334 qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_ihardlimit) 335 msgi = "File limit reached on"; 336 else if (qup->dqblk.dqb_isoftlimit && 337 qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_isoftlimit) { 338 if (qup->dqblk.dqb_itime > now) 339 msgi = "In file grace period on"; 340 else 341 msgi = "Over file quota on"; 342 } 343 msgb = (char *)0; 344 if (qup->dqblk.dqb_bhardlimit && 345 qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bhardlimit) 346 msgb = "Block limit reached on"; 347 else { 348 if (qup->dqblk.dqb_bsoftlimit 349 && qup->dqblk.dqb_curblocks 350 >= qup->dqblk.dqb_bsoftlimit) { 351 if (qup->dqblk.dqb_btime > now) 352 msgb = "In block grace period on"; 353 else 354 msgb = "Over block quota on"; 355 } 356 } 357 if (qflag) { 358 if ((msgi != (char *)0 || msgb != (char *)0) && 359 lines++ == 0) 360 heading(type, id, name, ""); 361 if (msgi != (char *)0) 362 printf("\t%s %s\n", msgi, qup->fsname); 363 if (msgb != (char *)0) 364 printf("\t%s %s\n", msgb, qup->fsname); 365 continue; 366 } 367 if (vflag || 368 qup->dqblk.dqb_curblocks || 369 qup->dqblk.dqb_curinodes) { 370 if (lines++ == 0) 371 heading(type, id, name, ""); 372 nam = qup->fsname; 373 if (strlen(qup->fsname) > 15) { 374 printf("%s\n", qup->fsname); 375 nam = ""; 376 } 377 printf("%12s%9d%c%8d%9d%8s" 378 , nam 379 , (int)(dbtob((u_quad_t)qup->dqblk.dqb_curblocks) 380 / 1024) 381 , (msgb == (char *)0) ? ' ' : '*' 382 , (int)(dbtob((u_quad_t)qup->dqblk.dqb_bsoftlimit) 383 / 1024) 384 , (int)(dbtob((u_quad_t)qup->dqblk.dqb_bhardlimit) 385 / 1024) 386 , (msgb == (char *)0) ? "" 387 : timeprt(qup->dqblk.dqb_btime)); 388 printf("%8d%c%7d%8d%8s\n" 389 , qup->dqblk.dqb_curinodes 390 , (msgi == (char *)0) ? ' ' : '*' 391 , qup->dqblk.dqb_isoftlimit 392 , qup->dqblk.dqb_ihardlimit 393 , (msgi == (char *)0) ? "" 394 : timeprt(qup->dqblk.dqb_itime) 395 ); 396 continue; 397 } 398 } 399 if (!qflag && lines == 0) 400 heading(type, id, name, "none"); 401 } 402 403 void 404 heading(type, id, name, tag) 405 int type; 406 u_long id; 407 const char *name, *tag; 408 { 409 410 printf("Disk quotas for %s %s (%cid %ld): %s\n", qfextension[type], 411 name, *qfextension[type], (u_long)id, tag); 412 if (!qflag && tag[0] == '\0') { 413 printf("%12s%9s %8s%9s%8s%8s %7s%8s%8s\n" 414 , "Filesystem" 415 , "blocks" 416 , "quota" 417 , "limit" 418 , "grace" 419 , "files" 420 , "quota" 421 , "limit" 422 , "grace" 423 ); 424 } 425 } 426 427 /* 428 * Calculate the grace period and return a printable string for it. 429 */ 430 char * 431 timeprt(seconds) 432 time_t seconds; 433 { 434 time_t hours, minutes; 435 static char buf[20]; 436 static time_t now; 437 438 if (now == 0) 439 time(&now); 440 if (now > seconds) 441 return ("none"); 442 seconds -= now; 443 minutes = (seconds + 30) / 60; 444 hours = (minutes + 30) / 60; 445 if (hours >= 36) { 446 (void)snprintf(buf, sizeof buf, "%ddays", 447 (int)((hours + 12) / 24)); 448 return (buf); 449 } 450 if (minutes >= 60) { 451 (void)snprintf(buf, sizeof buf, "%2d:%d", 452 (int)(minutes / 60), (int)(minutes % 60)); 453 return (buf); 454 } 455 (void)snprintf(buf, sizeof buf, "%2d", (int)minutes); 456 return (buf); 457 } 458 459 /* 460 * Collect the requested quota information. 461 */ 462 struct quotause * 463 getprivs(id, quotatype) 464 long id; 465 int quotatype; 466 { 467 struct quotause *qup, *quptail; 468 struct fstab *fs; 469 struct quotause *quphead; 470 struct statfs *fst; 471 int nfst, i; 472 473 qup = quphead = quptail = NULL; 474 475 nfst = getmntinfo(&fst, MNT_WAIT); 476 if (nfst == 0) 477 errx(2, "no filesystems mounted!"); 478 setfsent(); 479 for (i = 0; i < nfst; i++) { 480 if (qup == NULL) { 481 if ((qup = 482 (struct quotause *)malloc(sizeof *qup)) == NULL) 483 errx(2, "out of memory"); 484 } 485 if (strncmp(fst[i].f_fstypename, "nfs", MFSNAMELEN) == 0) { 486 if (getnfsquota(&fst[i], NULL, qup, id, quotatype) == 0) 487 continue; 488 } else if (strncmp(fst[i].f_fstypename, "ffs", 489 MFSNAMELEN) == 0) { 490 /* 491 * XXX 492 * UFS filesystems must be in /etc/fstab, and must 493 * indicate that they have quotas on (?!) This is quite 494 * unlike SunOS where quotas can be enabled/disabled 495 * on a filesystem independent of /etc/fstab, and it 496 * will still print quotas for them. 497 */ 498 if ((fs = getfsspec(fst[i].f_mntfromname)) == NULL) 499 continue; 500 if (getufsquota(&fst[i], fs, qup, id, quotatype) == 0) 501 continue; 502 } else 503 continue; 504 (void)strncpy(qup->fsname, fst[i].f_mntonname, 505 sizeof(qup->fsname) - 1); 506 if (quphead == NULL) 507 quphead = qup; 508 else 509 quptail->next = qup; 510 quptail = qup; 511 quptail->next = 0; 512 qup = NULL; 513 } 514 if (qup) 515 free(qup); 516 endfsent(); 517 return (quphead); 518 } 519 520 /* 521 * Check to see if a particular quota is to be enabled. 522 */ 523 int 524 ufshasquota(fs, type, qfnamep) 525 struct fstab *fs; 526 int type; 527 char **qfnamep; 528 { 529 static char initname, usrname[100], grpname[100]; 530 static char buf[BUFSIZ]; 531 char *opt, *cp; 532 533 cp = NULL; 534 if (!initname) { 535 (void)snprintf(usrname, sizeof usrname, "%s%s", 536 qfextension[USRQUOTA], qfname); 537 (void)snprintf(grpname, sizeof grpname, "%s%s", 538 qfextension[GRPQUOTA], qfname); 539 initname = 1; 540 } 541 (void)strncpy(buf, fs->fs_mntops, sizeof(buf) - 1); 542 buf[sizeof(buf) - 1] = '\0'; 543 for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) { 544 if ((cp = strchr(opt, '=')) != NULL) 545 *cp++ = '\0'; 546 if (type == USRQUOTA && strcmp(opt, usrname) == 0) 547 break; 548 if (type == GRPQUOTA && strcmp(opt, grpname) == 0) 549 break; 550 } 551 if (!opt) 552 return (0); 553 if (cp) { 554 *qfnamep = cp; 555 return (1); 556 } 557 (void)snprintf(buf, sizeof buf, "%s/%s.%s", fs->fs_file, qfname, 558 qfextension[type]); 559 *qfnamep = buf; 560 return (1); 561 } 562 563 int 564 getufsquota(fst, fs, qup, id, quotatype) 565 struct statfs *fst; 566 struct fstab *fs; 567 struct quotause *qup; 568 long id; 569 int quotatype; 570 { 571 char *qfpathname; 572 int fd, qcmd; 573 574 qcmd = QCMD(Q_GETQUOTA, quotatype); 575 if (!ufshasquota(fs, quotatype, &qfpathname)) 576 return (0); 577 578 if (quotactl(fs->fs_file, qcmd, id, &qup->dqblk) != 0) { 579 if ((fd = open(qfpathname, O_RDONLY)) < 0) { 580 warn("%s", qfpathname); 581 return (0); 582 } 583 (void)lseek(fd, (off_t)(id * sizeof(struct dqblk)), SEEK_SET); 584 switch (read(fd, &qup->dqblk, sizeof(struct dqblk))) { 585 case 0: /* EOF */ 586 /* 587 * Convert implicit 0 quota (EOF) 588 * into an explicit one (zero'ed dqblk) 589 */ 590 memset((caddr_t)&qup->dqblk, 0, sizeof(struct dqblk)); 591 break; 592 case sizeof(struct dqblk): /* OK */ 593 break; 594 default: /* ERROR */ 595 warn("read error `%s'", qfpathname); 596 close(fd); 597 return (0); 598 } 599 close(fd); 600 } 601 return (1); 602 } 603 604 int 605 getnfsquota(fst, fs, qup, id, quotatype) 606 struct statfs *fst; 607 struct fstab *fs; 608 struct quotause *qup; 609 long id; 610 int quotatype; 611 { 612 struct getquota_args gq_args; 613 struct getquota_rslt gq_rslt; 614 struct dqblk *dqp = &qup->dqblk; 615 struct timeval tv; 616 char *cp; 617 618 if (fst->f_flags & MNT_LOCAL) 619 return (0); 620 621 /* 622 * rpc.rquotad does not support group quotas 623 */ 624 if (quotatype != USRQUOTA) 625 return (0); 626 627 /* 628 * must be some form of "hostname:/path" 629 */ 630 cp = strchr(fst->f_mntfromname, ':'); 631 if (cp == NULL) { 632 warnx("cannot find hostname for %s", fst->f_mntfromname); 633 return (0); 634 } 635 636 *cp = '\0'; 637 if (*(cp+1) != '/') { 638 *cp = ':'; 639 return (0); 640 } 641 642 gq_args.gqa_pathp = cp + 1; 643 gq_args.gqa_uid = id; 644 if (callaurpc(fst->f_mntfromname, RQUOTAPROG, RQUOTAVERS, 645 RQUOTAPROC_GETQUOTA, xdr_getquota_args, &gq_args, 646 xdr_getquota_rslt, &gq_rslt) != 0) { 647 *cp = ':'; 648 return (0); 649 } 650 651 switch (gq_rslt.status) { 652 case Q_NOQUOTA: 653 break; 654 case Q_EPERM: 655 warnx("quota permission error, host: %s", fst->f_mntfromname); 656 break; 657 case Q_OK: 658 gettimeofday(&tv, NULL); 659 /* blocks*/ 660 dqp->dqb_bhardlimit = 661 gq_rslt.getquota_rslt_u.gqr_rquota.rq_bhardlimit * 662 (gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE); 663 dqp->dqb_bsoftlimit = 664 gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsoftlimit * 665 (gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE); 666 dqp->dqb_curblocks = 667 gq_rslt.getquota_rslt_u.gqr_rquota.rq_curblocks * 668 (gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE); 669 /* inodes */ 670 dqp->dqb_ihardlimit = 671 gq_rslt.getquota_rslt_u.gqr_rquota.rq_fhardlimit; 672 dqp->dqb_isoftlimit = 673 gq_rslt.getquota_rslt_u.gqr_rquota.rq_fsoftlimit; 674 dqp->dqb_curinodes = 675 gq_rslt.getquota_rslt_u.gqr_rquota.rq_curfiles; 676 /* grace times */ 677 dqp->dqb_btime = 678 tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_btimeleft; 679 dqp->dqb_itime = 680 tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_ftimeleft; 681 *cp = ':'; 682 return (1); 683 default: 684 warnx("bad rpc result, host: %s", fst->f_mntfromname); 685 break; 686 } 687 *cp = ':'; 688 return (0); 689 } 690 691 int 692 callaurpc(host, prognum, versnum, procnum, inproc, in, outproc, out) 693 char *host; 694 int prognum, versnum, procnum; 695 xdrproc_t inproc; 696 void *in; 697 xdrproc_t outproc; 698 void *out; 699 { 700 struct sockaddr_in server_addr; 701 enum clnt_stat clnt_stat; 702 struct hostent *hp; 703 struct timeval timeout, tottimeout; 704 705 CLIENT *client = NULL; 706 int socket = RPC_ANYSOCK; 707 708 if ((hp = gethostbyname(host)) == NULL) 709 return ((int) RPC_UNKNOWNHOST); 710 timeout.tv_usec = 0; 711 timeout.tv_sec = 6; 712 memmove(&server_addr.sin_addr, hp->h_addr, hp->h_length); 713 server_addr.sin_family = AF_INET; 714 server_addr.sin_port = 0; 715 716 if ((client = clntudp_create(&server_addr, prognum, 717 versnum, timeout, &socket)) == NULL) 718 return ((int) rpc_createerr.cf_stat); 719 720 client->cl_auth = authunix_create_default(); 721 tottimeout.tv_sec = 25; 722 tottimeout.tv_usec = 0; 723 clnt_stat = clnt_call(client, procnum, inproc, in, 724 outproc, out, tottimeout); 725 726 return ((int) clnt_stat); 727 } 728 729 int 730 alldigits(s) 731 char *s; 732 { 733 int c; 734 735 c = *s++; 736 do { 737 if (!isdigit(c)) 738 return (0); 739 } while ((c = *s++) != 0); 740 return (1); 741 } 742