1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 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. Neither the name of the University nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 */ 34 35 /* 36 * Fix up / report on disk quotas & usage 37 */ 38 #include <sys/param.h> 39 #include <sys/disklabel.h> 40 #include <sys/mount.h> 41 #include <sys/stat.h> 42 43 #include <ufs/ufs/dinode.h> 44 #include <ufs/ufs/quota.h> 45 #include <ufs/ffs/fs.h> 46 47 #include <err.h> 48 #include <errno.h> 49 #include <fcntl.h> 50 #include <fstab.h> 51 #include <grp.h> 52 #include <libufs.h> 53 #include <libutil.h> 54 #include <pwd.h> 55 #include <stdint.h> 56 #include <stdio.h> 57 #include <stdlib.h> 58 #include <string.h> 59 #include <unistd.h> 60 61 #include "quotacheck.h" 62 63 const char *qfname = QUOTAFILENAME; 64 const char *qfextension[] = INITQFNAMES; 65 const char *quotagroup = QUOTAGROUP; 66 67 union { 68 struct fs sblk; 69 char dummy[MAXBSIZE]; 70 } sb_un; 71 #define sblock sb_un.sblk 72 union { 73 struct cg cgblk; 74 char dummy[MAXBSIZE]; 75 } cg_un; 76 #define cgblk cg_un.cgblk 77 long dev_bsize = 1; 78 ino_t maxino; 79 80 #define DIP(dp, field) \ 81 ((sblock.fs_magic == FS_UFS1_MAGIC) ? \ 82 (dp)->dp1.field : (dp)->dp2.field) 83 84 #define HASUSR 1 85 #define HASGRP 2 86 87 struct fileusage { 88 struct fileusage *fu_next; 89 u_long fu_curinodes; 90 u_long fu_curblocks; 91 u_long fu_id; 92 char fu_name[1]; 93 /* actually bigger */ 94 }; 95 #define FUHASH 1024 /* must be power of two */ 96 struct fileusage *fuhead[MAXQUOTAS][FUHASH]; 97 98 int aflag; /* all file systems */ 99 int cflag; /* convert format to 32 or 64 bit size */ 100 int gflag; /* check group quotas */ 101 int uflag; /* check user quotas */ 102 int vflag; /* verbose */ 103 int fi; /* open disk file descriptor */ 104 105 struct fileusage * 106 addid(u_long, int, char *, const char *); 107 void blkread(ufs2_daddr_t, char *, long); 108 void freeinodebuf(void); 109 union dinode * 110 getnextinode(ino_t); 111 int getquotagid(void); 112 struct fileusage * 113 lookup(u_long, int); 114 int oneof(char *, char*[], int); 115 void printchanges(const char *, int, struct dqblk *, struct fileusage *, 116 u_long); 117 void setinodebuf(ino_t); 118 int update(const char *, struct quotafile *, int); 119 void usage(void); 120 121 int 122 main(int argc, char *argv[]) 123 { 124 struct fstab *fs; 125 struct passwd *pw; 126 struct group *gr; 127 struct quotafile *qfu, *qfg; 128 int i, argnum, maxrun, errs, ch; 129 long done = 0; 130 char *name; 131 132 errs = maxrun = 0; 133 while ((ch = getopt(argc, argv, "ac:guvl:")) != -1) { 134 switch(ch) { 135 case 'a': 136 aflag++; 137 break; 138 case 'c': 139 if (cflag) 140 usage(); 141 cflag = atoi(optarg); 142 break; 143 case 'g': 144 gflag++; 145 break; 146 case 'u': 147 uflag++; 148 break; 149 case 'v': 150 vflag++; 151 break; 152 case 'l': 153 maxrun = atoi(optarg); 154 break; 155 default: 156 usage(); 157 } 158 } 159 argc -= optind; 160 argv += optind; 161 if ((argc == 0 && !aflag) || (argc > 0 && aflag)) 162 usage(); 163 if (cflag && cflag != 32 && cflag != 64) 164 usage(); 165 if (!gflag && !uflag) { 166 gflag++; 167 uflag++; 168 } 169 if (gflag) { 170 setgrent(); 171 while ((gr = getgrent()) != NULL) 172 (void) addid((u_long)gr->gr_gid, GRPQUOTA, gr->gr_name, 173 NULL); 174 endgrent(); 175 } 176 if (uflag) { 177 setpwent(); 178 while ((pw = getpwent()) != NULL) 179 (void) addid((u_long)pw->pw_uid, USRQUOTA, pw->pw_name, 180 NULL); 181 endpwent(); 182 } 183 /* 184 * The maxrun (-l) option is now deprecated. 185 */ 186 if (maxrun > 0) 187 warnx("the -l option is now deprecated"); 188 if (aflag) 189 exit(checkfstab(uflag, gflag)); 190 if (setfsent() == 0) 191 errx(1, "%s: can't open", FSTAB); 192 while ((fs = getfsent()) != NULL) { 193 if (((argnum = oneof(fs->fs_file, argv, argc)) >= 0 || 194 (argnum = oneof(fs->fs_spec, argv, argc)) >= 0) && 195 (name = blockcheck(fs->fs_spec))) { 196 done |= 1 << argnum; 197 qfu = NULL; 198 if (uflag) 199 qfu = quota_open(fs, USRQUOTA, O_CREAT|O_RDWR); 200 qfg = NULL; 201 if (gflag) 202 qfg = quota_open(fs, GRPQUOTA, O_CREAT|O_RDWR); 203 if (qfu == NULL && qfg == NULL) 204 continue; 205 errs += chkquota(name, qfu, qfg); 206 if (qfu) 207 quota_close(qfu); 208 if (qfg) 209 quota_close(qfg); 210 } 211 } 212 endfsent(); 213 for (i = 0; i < argc; i++) 214 if ((done & (1 << i)) == 0) 215 fprintf(stderr, "%s not found in %s\n", 216 argv[i], FSTAB); 217 exit(errs); 218 } 219 220 void 221 usage(void) 222 { 223 (void)fprintf(stderr, "%s\n%s\n", 224 "usage: quotacheck [-guv] [-c 32 | 64] [-l maxrun] -a", 225 " quotacheck [-guv] [-c 32 | 64] filesystem ..."); 226 exit(1); 227 } 228 229 /* 230 * Scan the specified file system to check quota(s) present on it. 231 */ 232 int 233 chkquota(char *specname, struct quotafile *qfu, struct quotafile *qfg) 234 { 235 struct fileusage *fup; 236 union dinode *dp; 237 struct fs *fs; 238 int i, ret, mode, errs = 0; 239 u_int32_t cg; 240 ino_t curino, ino, inosused, userino = 0, groupino = 0; 241 dev_t dev, userdev = 0, groupdev = 0; 242 struct stat sb; 243 const char *mntpt; 244 char *cp; 245 246 if (qfu != NULL) 247 mntpt = quota_fsname(qfu); 248 else if (qfg != NULL) 249 mntpt = quota_fsname(qfg); 250 else 251 errx(1, "null quotafile information passed to chkquota()\n"); 252 if (cflag) { 253 if (vflag && qfu != NULL) 254 printf("%s: convert user quota to %d bits\n", 255 mntpt, cflag); 256 if (qfu != NULL && quota_convert(qfu, cflag) < 0) { 257 if (errno == EBADF) 258 errx(1, 259 "%s: cannot convert an active quota file", 260 mntpt); 261 err(1, "user quota conversion to size %d failed", 262 cflag); 263 } 264 if (vflag && qfg != NULL) 265 printf("%s: convert group quota to %d bits\n", 266 mntpt, cflag); 267 if (qfg != NULL && quota_convert(qfg, cflag) < 0) { 268 if (errno == EBADF) 269 errx(1, 270 "%s: cannot convert an active quota file", 271 mntpt); 272 err(1, "group quota conversion to size %d failed", 273 cflag); 274 } 275 } 276 if ((fi = open(specname, O_RDONLY, 0)) < 0) { 277 warn("%s", specname); 278 return (1); 279 } 280 if ((stat(mntpt, &sb)) < 0) { 281 warn("%s", mntpt); 282 return (1); 283 } 284 dev = sb.st_dev; 285 if (vflag) { 286 (void)printf("*** Checking "); 287 if (qfu) 288 (void)printf("user%s", qfg ? " and " : ""); 289 if (qfg) 290 (void)printf("group"); 291 (void)printf(" quotas for %s (%s)\n", specname, mntpt); 292 } 293 if (qfu) { 294 if (stat(quota_qfname(qfu), &sb) == 0) { 295 userino = sb.st_ino; 296 userdev = sb.st_dev; 297 } 298 } 299 if (qfg) { 300 if (stat(quota_qfname(qfg), &sb) == 0) { 301 groupino = sb.st_ino; 302 groupdev = sb.st_dev; 303 } 304 } 305 sync(); 306 if ((ret = sbget(fi, &fs, UFS_STDSB, UFS_NOCSUM)) != 0) { 307 switch (ret) { 308 case ENOENT: 309 warn("Cannot find file system superblock"); 310 return (1); 311 default: 312 warn("Unable to read file system superblock"); 313 return (1); 314 } 315 } 316 bcopy(fs, &sblock, fs->fs_sbsize); 317 free(fs); 318 dev_bsize = sblock.fs_fsize / fsbtodb(&sblock, 1); 319 maxino = sblock.fs_ncg * sblock.fs_ipg; 320 for (cg = 0; cg < sblock.fs_ncg; cg++) { 321 ino = cg * sblock.fs_ipg; 322 setinodebuf(ino); 323 blkread(fsbtodb(&sblock, cgtod(&sblock, cg)), (char *)(&cgblk), 324 sblock.fs_cgsize); 325 if (sblock.fs_magic == FS_UFS2_MAGIC) 326 inosused = cgblk.cg_initediblk; 327 else 328 inosused = sblock.fs_ipg; 329 /* 330 * If we are using soft updates, then we can trust the 331 * cylinder group inode allocation maps to tell us which 332 * inodes are allocated. We will scan the used inode map 333 * to find the inodes that are really in use, and then 334 * read only those inodes in from disk. 335 */ 336 if (sblock.fs_flags & FS_DOSOFTDEP) { 337 if (!cg_chkmagic(&cgblk)) 338 errx(1, "CG %d: BAD MAGIC NUMBER\n", cg); 339 cp = &cg_inosused(&cgblk)[(inosused - 1) / CHAR_BIT]; 340 for ( ; inosused > 0; inosused -= CHAR_BIT, cp--) { 341 if (*cp == 0) 342 continue; 343 for (i = 1 << (CHAR_BIT - 1); i > 0; i >>= 1) { 344 if (*cp & i) 345 break; 346 inosused--; 347 } 348 break; 349 } 350 if (inosused <= 0) 351 continue; 352 } 353 for (curino = 0; curino < inosused; curino++, ino++) { 354 if ((dp = getnextinode(ino)) == NULL || 355 ino < UFS_ROOTINO || 356 (mode = DIP(dp, di_mode) & IFMT) == 0) 357 continue; 358 /* 359 * XXX: Do not account for UIDs or GIDs that appear 360 * to be negative to prevent generating 100GB+ 361 * quota files. 362 */ 363 if ((int)DIP(dp, di_uid) < 0 || 364 (int)DIP(dp, di_gid) < 0) { 365 if (vflag) { 366 if (aflag) 367 (void)printf("%s: ", mntpt); 368 (void)printf("out of range UID/GID (%u/%u) ino=%ju\n", 369 DIP(dp, di_uid), DIP(dp,di_gid), 370 (uintmax_t)ino); 371 } 372 continue; 373 } 374 375 /* 376 * Do not account for file system snapshot files 377 * or the actual quota data files to be consistent 378 * with how they are handled inside the kernel. 379 */ 380 #ifdef SF_SNAPSHOT 381 if (DIP(dp, di_flags) & SF_SNAPSHOT) 382 continue; 383 #endif 384 if ((ino == userino && dev == userdev) || 385 (ino == groupino && dev == groupdev)) 386 continue; 387 if (qfg) { 388 fup = addid((u_long)DIP(dp, di_gid), GRPQUOTA, 389 NULL, mntpt); 390 fup->fu_curinodes++; 391 if (mode == IFREG || mode == IFDIR || 392 mode == IFLNK) 393 fup->fu_curblocks += DIP(dp, di_blocks); 394 } 395 if (qfu) { 396 fup = addid((u_long)DIP(dp, di_uid), USRQUOTA, 397 NULL, mntpt); 398 fup->fu_curinodes++; 399 if (mode == IFREG || mode == IFDIR || 400 mode == IFLNK) 401 fup->fu_curblocks += DIP(dp, di_blocks); 402 } 403 } 404 } 405 freeinodebuf(); 406 if (qfu) 407 errs += update(mntpt, qfu, USRQUOTA); 408 if (qfg) 409 errs += update(mntpt, qfg, GRPQUOTA); 410 close(fi); 411 (void)fflush(stdout); 412 return (errs); 413 } 414 415 /* 416 * Update a specified quota file. 417 */ 418 int 419 update(const char *fsname, struct quotafile *qf, int type) 420 { 421 struct fileusage *fup; 422 u_long id, lastid, highid = 0; 423 struct dqblk dqbuf; 424 struct stat sb; 425 static struct dqblk zerodqbuf; 426 static struct fileusage zerofileusage; 427 428 /* 429 * Scan the on-disk quota file and record any usage changes. 430 */ 431 lastid = quota_maxid(qf); 432 for (id = 0; id <= lastid; id++) { 433 if (quota_read(qf, &dqbuf, id) < 0) 434 dqbuf = zerodqbuf; 435 if ((fup = lookup(id, type)) == NULL) 436 fup = &zerofileusage; 437 if (fup->fu_curinodes || fup->fu_curblocks || 438 dqbuf.dqb_bsoftlimit || dqbuf.dqb_bhardlimit || 439 dqbuf.dqb_isoftlimit || dqbuf.dqb_ihardlimit) 440 highid = id; 441 if (dqbuf.dqb_curinodes == fup->fu_curinodes && 442 dqbuf.dqb_curblocks == fup->fu_curblocks) { 443 fup->fu_curinodes = 0; 444 fup->fu_curblocks = 0; 445 continue; 446 } 447 printchanges(fsname, type, &dqbuf, fup, id); 448 dqbuf.dqb_curinodes = fup->fu_curinodes; 449 dqbuf.dqb_curblocks = fup->fu_curblocks; 450 (void) quota_write_usage(qf, &dqbuf, id); 451 fup->fu_curinodes = 0; 452 fup->fu_curblocks = 0; 453 } 454 455 /* 456 * Walk the hash table looking for ids with non-zero usage 457 * that are not currently recorded in the quota file. E.g. 458 * ids that are past the end of the current file. 459 */ 460 for (id = 0; id < FUHASH; id++) { 461 for (fup = fuhead[type][id]; fup != NULL; fup = fup->fu_next) { 462 if (fup->fu_id <= lastid) 463 continue; 464 if (fup->fu_curinodes == 0 && fup->fu_curblocks == 0) 465 continue; 466 bzero(&dqbuf, sizeof(struct dqblk)); 467 if (fup->fu_id > highid) 468 highid = fup->fu_id; 469 printchanges(fsname, type, &dqbuf, fup, fup->fu_id); 470 dqbuf.dqb_curinodes = fup->fu_curinodes; 471 dqbuf.dqb_curblocks = fup->fu_curblocks; 472 (void) quota_write_usage(qf, &dqbuf, fup->fu_id); 473 fup->fu_curinodes = 0; 474 fup->fu_curblocks = 0; 475 } 476 } 477 /* 478 * If this is old format file, then size may be smaller, 479 * so ensure that we only truncate when it will make things 480 * smaller, and not if it will grow an old format file. 481 */ 482 if (highid < lastid && 483 stat(quota_qfname(qf), &sb) == 0 && 484 sb.st_size > (off_t)((highid + 2) * sizeof(struct dqblk))) 485 truncate(quota_qfname(qf), 486 (((off_t)highid + 2) * sizeof(struct dqblk))); 487 return (0); 488 } 489 490 /* 491 * Check to see if target appears in list of size cnt. 492 */ 493 int 494 oneof(char *target, char *list[], int cnt) 495 { 496 int i; 497 498 for (i = 0; i < cnt; i++) 499 if (strcmp(target, list[i]) == 0) 500 return (i); 501 return (-1); 502 } 503 504 /* 505 * Determine the group identifier for quota files. 506 */ 507 int 508 getquotagid(void) 509 { 510 struct group *gr; 511 512 if ((gr = getgrnam(quotagroup)) != NULL) 513 return (gr->gr_gid); 514 return (-1); 515 } 516 517 /* 518 * Routines to manage the file usage table. 519 * 520 * Lookup an id of a specific type. 521 */ 522 struct fileusage * 523 lookup(u_long id, int type) 524 { 525 struct fileusage *fup; 526 527 for (fup = fuhead[type][id & (FUHASH-1)]; fup != NULL; fup = fup->fu_next) 528 if (fup->fu_id == id) 529 return (fup); 530 return (NULL); 531 } 532 533 /* 534 * Add a new file usage id if it does not already exist. 535 */ 536 struct fileusage * 537 addid(u_long id, int type, char *name, const char *fsname) 538 { 539 struct fileusage *fup, **fhp; 540 int len; 541 542 if ((fup = lookup(id, type)) != NULL) 543 return (fup); 544 if (name) 545 len = strlen(name); 546 else 547 len = 0; 548 if ((fup = calloc(1, sizeof(*fup) + len)) == NULL) 549 errx(1, "calloc failed"); 550 fhp = &fuhead[type][id & (FUHASH - 1)]; 551 fup->fu_next = *fhp; 552 *fhp = fup; 553 fup->fu_id = id; 554 if (name) 555 bcopy(name, fup->fu_name, len + 1); 556 else { 557 (void)sprintf(fup->fu_name, "%lu", id); 558 if (vflag) { 559 if (aflag && fsname != NULL) 560 (void)printf("%s: ", fsname); 561 printf("unknown %cid: %lu\n", 562 type == USRQUOTA ? 'u' : 'g', id); 563 } 564 } 565 return (fup); 566 } 567 568 /* 569 * Special purpose version of ginode used to optimize pass 570 * over all the inodes in numerical order. 571 */ 572 static ino_t nextino, lastinum, lastvalidinum; 573 static long readcnt, readpercg, fullcnt, inobufsize, partialcnt, partialsize; 574 static caddr_t inodebuf; 575 #define INOBUFSIZE 56*1024 /* size of buffer to read inodes */ 576 577 union dinode * 578 getnextinode(ino_t inumber) 579 { 580 long size; 581 ufs2_daddr_t dblk; 582 union dinode *dp; 583 static caddr_t nextinop; 584 585 if (inumber != nextino++ || inumber > lastvalidinum) 586 errx(1, "bad inode number %ju to nextinode", 587 (uintmax_t)inumber); 588 if (inumber >= lastinum) { 589 readcnt++; 590 dblk = fsbtodb(&sblock, ino_to_fsba(&sblock, lastinum)); 591 if (readcnt % readpercg == 0) { 592 size = partialsize; 593 lastinum += partialcnt; 594 } else { 595 size = inobufsize; 596 lastinum += fullcnt; 597 } 598 /* 599 * If blkread returns an error, it will already have zeroed 600 * out the buffer, so we do not need to do so here. 601 */ 602 blkread(dblk, inodebuf, size); 603 nextinop = inodebuf; 604 } 605 dp = (union dinode *)nextinop; 606 if (sblock.fs_magic == FS_UFS1_MAGIC) 607 nextinop += sizeof(struct ufs1_dinode); 608 else 609 nextinop += sizeof(struct ufs2_dinode); 610 return (dp); 611 } 612 613 /* 614 * Prepare to scan a set of inodes. 615 */ 616 void 617 setinodebuf(ino_t inum) 618 { 619 620 if (inum % sblock.fs_ipg != 0) 621 errx(1, "bad inode number %ju to setinodebuf", (uintmax_t)inum); 622 lastvalidinum = inum + sblock.fs_ipg - 1; 623 nextino = inum; 624 lastinum = inum; 625 readcnt = 0; 626 if (inodebuf != NULL) 627 return; 628 inobufsize = blkroundup(&sblock, INOBUFSIZE); 629 fullcnt = inobufsize / ((sblock.fs_magic == FS_UFS1_MAGIC) ? 630 sizeof(struct ufs1_dinode) : sizeof(struct ufs2_dinode)); 631 readpercg = sblock.fs_ipg / fullcnt; 632 partialcnt = sblock.fs_ipg % fullcnt; 633 partialsize = partialcnt * ((sblock.fs_magic == FS_UFS1_MAGIC) ? 634 sizeof(struct ufs1_dinode) : sizeof(struct ufs2_dinode)); 635 if (partialcnt != 0) { 636 readpercg++; 637 } else { 638 partialcnt = fullcnt; 639 partialsize = inobufsize; 640 } 641 if ((inodebuf = malloc((unsigned)inobufsize)) == NULL) 642 errx(1, "cannot allocate space for inode buffer"); 643 } 644 645 /* 646 * Free up data structures used to scan inodes. 647 */ 648 void 649 freeinodebuf(void) 650 { 651 652 if (inodebuf != NULL) 653 free(inodebuf); 654 inodebuf = NULL; 655 } 656 657 /* 658 * Read specified disk blocks. 659 */ 660 void 661 blkread(ufs2_daddr_t bno, char *buf, long cnt) 662 { 663 664 if (lseek(fi, (off_t)bno * dev_bsize, SEEK_SET) < 0 || 665 read(fi, buf, cnt) != cnt) 666 errx(1, "blkread failed on block %ld", (long)bno); 667 } 668 669 /* 670 * Display updated block and i-node counts. 671 */ 672 void 673 printchanges(const char *fsname, int type, struct dqblk *dp, 674 struct fileusage *fup, u_long id) 675 { 676 if (!vflag) 677 return; 678 if (aflag) 679 (void)printf("%s: ", fsname); 680 if (fup->fu_name[0] == '\0') 681 (void)printf("%-8lu fixed ", id); 682 else 683 (void)printf("%-8s fixed ", fup->fu_name); 684 switch (type) { 685 686 case GRPQUOTA: 687 (void)printf("(group):"); 688 break; 689 690 case USRQUOTA: 691 (void)printf("(user): "); 692 break; 693 694 default: 695 (void)printf("(unknown quota type %d)", type); 696 break; 697 } 698 if (dp->dqb_curinodes != fup->fu_curinodes) 699 (void)printf("\tinodes %lu -> %lu", (u_long)dp->dqb_curinodes, 700 (u_long)fup->fu_curinodes); 701 if (dp->dqb_curblocks != fup->fu_curblocks) 702 (void)printf("\tblocks %lu -> %lu", 703 (u_long)dp->dqb_curblocks, 704 (u_long)fup->fu_curblocks); 705 (void)printf("\n"); 706 } 707