1 /* $OpenBSD: ncheck_ffs.c,v 1.50 2015/02/07 02:09:13 deraadt Exp $ */ 2 3 /*- 4 * Copyright (c) 1995, 1996 SigmaSoft, Th. Lockert <tholo@sigmasoft.com> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 18 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 19 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 /*- 29 * Copyright (c) 1980, 1988, 1991, 1993 30 * The Regents of the University of California. All rights reserved. 31 * 32 * Redistribution and use in source and binary forms, with or without 33 * modification, are permitted provided that the following conditions 34 * are met: 35 * 1. Redistributions of source code must retain the above copyright 36 * notice, this list of conditions and the following disclaimer. 37 * 2. Redistributions in binary form must reproduce the above copyright 38 * notice, this list of conditions and the following disclaimer in the 39 * documentation and/or other materials provided with the distribution. 40 * 3. Neither the name of the University nor the names of its contributors 41 * may be used to endorse or promote products derived from this software 42 * without specific prior written permission. 43 * 44 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 45 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 46 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 47 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 48 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 49 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 50 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 51 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 52 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 53 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 54 * SUCH DAMAGE. 55 */ 56 57 #include <sys/param.h> /* MAXBSIZE DEV_BSIZE */ 58 #include <sys/time.h> 59 #include <sys/stat.h> 60 #include <sys/ioctl.h> 61 #include <sys/disklabel.h> 62 #include <sys/dkio.h> 63 #include <ufs/ffs/fs.h> 64 #include <ufs/ufs/dir.h> 65 #include <ufs/ufs/dinode.h> 66 67 #include <stdio.h> 68 #include <stdlib.h> 69 #include <fcntl.h> 70 #include <string.h> 71 #include <ctype.h> 72 #include <unistd.h> 73 #include <limits.h> 74 #include <fstab.h> 75 #include <errno.h> 76 #include <err.h> 77 #include <util.h> 78 79 #define DIP(dp, field) \ 80 ((sblock->fs_magic == FS_UFS1_MAGIC) ? \ 81 ((struct ufs1_dinode *)(dp))->field : \ 82 ((struct ufs2_dinode *)(dp))->field) 83 84 char *disk; /* name of the disk file */ 85 char rdisk[PATH_MAX];/* resolved name of the disk file */ 86 int diskfd; /* disk file descriptor */ 87 struct fs *sblock; /* the file system super block */ 88 char sblock_buf[MAXBSIZE]; 89 int sblock_try[] = SBLOCKSEARCH; /* possible superblock locations */ 90 ufsino_t *ilist; /* list of inodes to check */ 91 int ninodes; /* number of inodes in list */ 92 int sflag; /* only suid and special files */ 93 int aflag; /* print the . and .. entries too */ 94 int mflag; /* verbose output */ 95 int iflag; /* specific inode */ 96 char *format; /* output format */ 97 98 struct disklabel lab; 99 100 struct icache_s { 101 ufsino_t ino; 102 union { 103 struct ufs1_dinode dp1; 104 struct ufs2_dinode dp2; 105 } di; 106 } *icache; 107 int nicache; 108 int maxicache; 109 110 void addinode(ufsino_t inum); 111 void *getino(ufsino_t inum); 112 void findinodes(ufsino_t); 113 void bread(daddr_t, char *, int); 114 __dead void usage(void); 115 void scanonedir(ufsino_t, const char *); 116 void dirindir(ufsino_t, daddr_t, int, off_t *, const char *); 117 void searchdir(ufsino_t, daddr_t, long, off_t, const char *); 118 int matchino(const void *, const void *); 119 int matchcache(const void *, const void *); 120 void cacheino(ufsino_t, void *); 121 void *cached(ufsino_t); 122 int main(int, char *[]); 123 char *rawname(char *); 124 void format_entry(const char *, struct direct *); 125 126 /* 127 * Check to see if the indicated inodes are the same 128 */ 129 int 130 matchino(const void *key, const void *val) 131 { 132 ufsino_t k = *(ufsino_t *)key; 133 ufsino_t v = *(ufsino_t *)val; 134 135 if (k < v) 136 return -1; 137 else if (k > v) 138 return 1; 139 return 0; 140 } 141 142 /* 143 * Check if the indicated inode match the entry in the cache 144 */ 145 int 146 matchcache(const void *key, const void *val) 147 { 148 ufsino_t ino = *(ufsino_t *)key; 149 struct icache_s *ic = (struct icache_s *)val; 150 151 if (ino < ic->ino) 152 return -1; 153 else if (ino > ic->ino) 154 return 1; 155 return 0; 156 } 157 158 /* 159 * Add an inode to the cached entries 160 */ 161 void 162 cacheino(ufsino_t ino, void *dp) 163 { 164 if (nicache == maxicache) { 165 struct icache_s *newicache; 166 167 /* grow exponentially */ 168 maxicache += 10 + maxicache/2; 169 newicache = reallocarray(icache, maxicache, sizeof(*icache)); 170 if (newicache == NULL) 171 errx(1, "malloc"); 172 icache = newicache; 173 174 } 175 icache[nicache].ino = ino; 176 if (sblock->fs_magic == FS_UFS1_MAGIC) 177 icache[nicache++].di.dp1 = *(struct ufs1_dinode *)dp; 178 else 179 icache[nicache++].di.dp2 = *(struct ufs2_dinode *)dp; 180 } 181 182 /* 183 * Get a cached inode 184 */ 185 void * 186 cached(ufsino_t ino) 187 { 188 struct icache_s *ic; 189 void *dp = NULL; 190 191 ic = bsearch(&ino, icache, nicache, sizeof(*icache), matchcache); 192 if (ic != NULL) { 193 if (sblock->fs_magic == FS_UFS1_MAGIC) 194 dp = &ic->di.dp1; 195 else 196 dp = &ic->di.dp2; 197 } 198 return (dp); 199 } 200 201 /* 202 * Walk the inode list for a filesystem to find all allocated inodes 203 * Remember inodes we want to give information about and cache all 204 * inodes pointing to directories 205 */ 206 void 207 findinodes(ufsino_t maxino) 208 { 209 ufsino_t ino; 210 void *dp; 211 mode_t mode; 212 213 for (ino = ROOTINO; ino < maxino; ino++) { 214 dp = getino(ino); 215 mode = DIP(dp, di_mode) & IFMT; 216 if (!mode) 217 continue; 218 if (mode == IFDIR) 219 cacheino(ino, dp); 220 if (iflag || 221 (sflag && (mode == IFDIR || 222 ((DIP(dp, di_mode) & (ISGID | ISUID)) == 0 && 223 (mode == IFREG || mode == IFLNK))))) 224 continue; 225 addinode(ino); 226 } 227 } 228 229 /* 230 * Get a specified inode from disk. Attempt to minimize reads to once 231 * per cylinder group 232 */ 233 void * 234 getino(ufsino_t inum) 235 { 236 static char *itab = NULL; 237 static daddr_t iblk = -1; 238 void *dp; 239 size_t dsize; 240 241 if (inum < ROOTINO || inum >= sblock->fs_ncg * sblock->fs_ipg) 242 return NULL; 243 if ((dp = cached(inum)) != NULL) 244 return dp; 245 if (sblock->fs_magic == FS_UFS1_MAGIC) 246 dsize = sizeof(struct ufs1_dinode); 247 else 248 dsize = sizeof(struct ufs2_dinode); 249 if ((inum / sblock->fs_ipg) != iblk || itab == NULL) { 250 iblk = inum / sblock->fs_ipg; 251 if (itab == NULL && 252 (itab = reallocarray(NULL, sblock->fs_ipg, dsize)) == NULL) 253 errx(1, "no memory for inodes"); 254 bread(fsbtodb(sblock, cgimin(sblock, iblk)), itab, 255 sblock->fs_ipg * dsize); 256 } 257 return itab + (inum % sblock->fs_ipg) * dsize; 258 } 259 260 /* 261 * Read a chunk of data from the disk. 262 * Try to recover from hard errors by reading in sector sized pieces. 263 * Error recovery is attempted at most BREADEMAX times before seeking 264 * consent from the operator to continue. 265 */ 266 int breaderrors = 0; 267 #define BREADEMAX 32 268 269 void 270 bread(daddr_t blkno, char *buf, int size) 271 { 272 off_t offset; 273 int cnt, i; 274 u_int32_t secsize = lab.d_secsize; 275 276 offset = blkno * DEV_BSIZE; 277 278 loop: 279 if ((cnt = pread(diskfd, buf, size, offset)) == size) 280 return; 281 if (blkno + (size / DEV_BSIZE) > 282 fsbtodb(sblock, sblock->fs_ffs1_size)) { 283 /* 284 * Trying to read the final fragment. 285 * 286 * NB - dump only works in TP_BSIZE blocks, hence 287 * rounds `DEV_BSIZE' fragments up to TP_BSIZE pieces. 288 * It should be smarter about not actually trying to 289 * read more than it can get, but for the time being 290 * we punt and scale back the read only when it gets 291 * us into trouble. (mkm 9/25/83) 292 */ 293 size -= secsize; 294 goto loop; 295 } 296 if (cnt == -1) 297 warnx("read error from %s: %s: [block %lld]: count=%d", 298 disk, strerror(errno), (long long)blkno, size); 299 else 300 warnx("short read error from %s: [block %lld]: count=%d, " 301 "got=%d", disk, (long long)blkno, size, cnt); 302 if (++breaderrors > BREADEMAX) 303 errx(1, "More than %d block read errors from %s", BREADEMAX, 304 disk); 305 /* 306 * Zero buffer, then try to read each sector of buffer separately. 307 */ 308 memset(buf, 0, size); 309 for (i = 0; i < size; i += secsize, buf += secsize) { 310 if ((cnt = pread(diskfd, buf, secsize, offset + i)) == 311 secsize) 312 continue; 313 if (cnt == -1) { 314 warnx("read error from %s: %s: [sector %lld]: " 315 "count=%u", disk, strerror(errno), 316 (long long)(offset + i) / DEV_BSIZE, secsize); 317 continue; 318 } 319 warnx("short read error from %s: [sector %lld]: count=%u, " 320 "got=%d", disk, (long long)(offset + i) / DEV_BSIZE, 321 secsize, cnt); 322 } 323 } 324 325 /* 326 * Add an inode to the in-memory list of inodes to dump 327 */ 328 void 329 addinode(ufsino_t ino) 330 { 331 ufsino_t *newilist; 332 333 newilist = reallocarray(ilist, ninodes + 1, sizeof(*ilist)); 334 if (newilist == NULL) 335 errx(4, "not enough memory to allocate tables"); 336 ilist = newilist; 337 ilist[ninodes] = ino; 338 ninodes++; 339 } 340 341 /* 342 * Scan the directory pointer at by ino 343 */ 344 void 345 scanonedir(ufsino_t ino, const char *path) 346 { 347 void *dp; 348 off_t filesize; 349 int i; 350 351 if ((dp = cached(ino)) == NULL) 352 return; 353 filesize = (off_t)DIP(dp, di_size); 354 for (i = 0; filesize > 0 && i < NDADDR; i++) { 355 if (DIP(dp, di_db[i]) != 0) { 356 searchdir(ino, DIP(dp, di_db[i]), 357 sblksize(sblock, DIP(dp, di_size), i), 358 filesize, path); 359 } 360 filesize -= sblock->fs_bsize; 361 } 362 for (i = 0; filesize > 0 && i < NIADDR; i++) { 363 if (DIP(dp, di_ib[i])) 364 dirindir(ino, DIP(dp, di_ib[i]), i, &filesize, path); 365 } 366 } 367 368 /* 369 * Read indirect blocks, and pass the data blocks to be searched 370 * as directories. 371 */ 372 void 373 dirindir(ufsino_t ino, daddr_t blkno, int ind_level, off_t *filesizep, 374 const char *path) 375 { 376 int i; 377 void *idblk; 378 379 if ((idblk = malloc(sblock->fs_bsize)) == NULL) 380 errx(1, "dirindir: cannot allocate indirect memory.\n"); 381 bread(fsbtodb(sblock, blkno), idblk, (int)sblock->fs_bsize); 382 if (ind_level <= 0) { 383 for (i = 0; *filesizep > 0 && i < NINDIR(sblock); i++) { 384 if (sblock->fs_magic == FS_UFS1_MAGIC) 385 blkno = ((int32_t *)idblk)[i]; 386 else 387 blkno = ((int64_t *)idblk)[i]; 388 if (blkno != 0) 389 searchdir(ino, blkno, sblock->fs_bsize, 390 *filesizep, path); 391 *filesizep -= sblock->fs_bsize; 392 } 393 } else { 394 ind_level--; 395 for (i = 0; *filesizep > 0 && i < NINDIR(sblock); i++) { 396 if (sblock->fs_magic == FS_UFS1_MAGIC) 397 blkno = ((int32_t *)idblk)[i]; 398 else 399 blkno = ((int64_t *)idblk)[i]; 400 if (blkno != 0) 401 dirindir(ino, blkno, ind_level, filesizep, 402 path); 403 } 404 } 405 free(idblk); 406 } 407 408 /* 409 * Scan a disk block containing directory information looking to see if 410 * any of the entries are on the dump list and to see if the directory 411 * contains any subdirectories. 412 */ 413 void 414 searchdir(ufsino_t ino, daddr_t blkno, long size, off_t filesize, 415 const char *path) 416 { 417 char *dblk; 418 struct direct *dp; 419 void *di; 420 mode_t mode; 421 char *npath; 422 ufsino_t subino; 423 long loc; 424 425 if ((dblk = malloc(sblock->fs_bsize)) == NULL) 426 errx(1, "searchdir: cannot allocate directory memory."); 427 bread(fsbtodb(sblock, blkno), dblk, (int)size); 428 if (filesize < size) 429 size = filesize; 430 for (loc = 0; loc < size; ) { 431 dp = (struct direct *)(dblk + loc); 432 if (dp->d_reclen == 0) { 433 warnx("corrupted directory, inode %llu", 434 (unsigned long long)ino); 435 break; 436 } 437 loc += dp->d_reclen; 438 if (dp->d_ino == 0) 439 continue; 440 if (dp->d_name[0] == '.') { 441 if (!aflag && (dp->d_name[1] == '\0' || 442 (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) 443 continue; 444 } 445 di = getino(dp->d_ino); 446 mode = DIP(di, di_mode) & IFMT; 447 subino = dp->d_ino; 448 if (bsearch(&subino, ilist, ninodes, sizeof(*ilist), matchino)) { 449 if (format) { 450 format_entry(path, dp); 451 } else { 452 if (mflag) 453 printf("mode %-6o uid %-5u gid %-5u ino ", 454 DIP(di, di_mode), DIP(di, di_uid), 455 DIP(di, di_gid)); 456 printf("%-7llu %s/%s%s\n", 457 (unsigned long long)dp->d_ino, path, 458 dp->d_name, mode == IFDIR ? "/." : ""); 459 } 460 } 461 if (mode == IFDIR) { 462 if (dp->d_name[0] == '.') { 463 if (dp->d_name[1] == '\0' || 464 (dp->d_name[1] == '.' && dp->d_name[2] == '\0')) 465 continue; 466 } 467 if (asprintf(&npath, "%s/%s", path, dp->d_name) == -1) 468 errx(1, "malloc"); 469 scanonedir(dp->d_ino, npath); 470 free(npath); 471 } 472 } 473 free(dblk); 474 } 475 476 char * 477 rawname(char *name) 478 { 479 static char newname[PATH_MAX]; 480 char *p; 481 482 if ((p = strrchr(name, '/')) == NULL) 483 return name; 484 *p = '\0'; 485 strlcpy(newname, name, sizeof newname - 2); 486 *p++ = '/'; 487 strlcat(newname, "/r", sizeof newname); 488 strlcat(newname, p, sizeof newname); 489 return(newname); 490 } 491 492 __dead void 493 usage(void) 494 { 495 extern char *__progname; 496 497 fprintf(stderr, 498 "usage: %s [-ams] [-f format] [-i number ...] filesystem\n", 499 __progname); 500 exit(3); 501 } 502 503 int 504 main(int argc, char *argv[]) 505 { 506 struct stat stblock; 507 struct fstab *fsp; 508 unsigned long long ullval; 509 ssize_t n; 510 char *ep; 511 int c, i; 512 513 while ((c = getopt(argc, argv, "af:i:ms")) != -1) 514 switch (c) { 515 case 'a': 516 aflag = 1; 517 break; 518 case 'i': 519 iflag = 1; 520 521 errno = 0; 522 ullval = strtoull(optarg, &ep, 10); 523 if (optarg[0] == '\0' || *ep != '\0') 524 errx(1, "%s is not a number", 525 optarg); 526 if ((errno == ERANGE && ullval == ULLONG_MAX) || 527 (ufsino_t)ullval != ullval) 528 errx(1, "%s is out of range", 529 optarg); 530 addinode((ufsino_t)ullval); 531 532 while (optind < argc) { 533 errno = 0; 534 ullval = strtoull(argv[optind], &ep, 10); 535 if (argv[optind][0] == '\0' || *ep != '\0') 536 break; 537 if ((errno == ERANGE && ullval == ULLONG_MAX) 538 || (ufsino_t)ullval != ullval) 539 errx(1, "%s is out of range", 540 argv[optind]); 541 addinode((ufsino_t)ullval); 542 optind++; 543 } 544 break; 545 case 'f': 546 format = optarg; 547 break; 548 case 'm': 549 mflag = 1; 550 break; 551 case 's': 552 sflag = 1; 553 break; 554 default: 555 usage(); 556 exit(2); 557 } 558 if (optind != argc - 1 || (mflag && format)) 559 usage(); 560 561 disk = argv[optind]; 562 if ((diskfd = opendev(disk, O_RDONLY, 0, NULL)) >= 0) { 563 if (fstat(diskfd, &stblock)) 564 err(1, "cannot stat %s", disk); 565 if (S_ISCHR(stblock.st_mode)) 566 goto gotdev; 567 close(diskfd); 568 } 569 570 if (realpath(disk, rdisk) == NULL) 571 err(1, "cannot find real path for %s", disk); 572 disk = rdisk; 573 574 if (stat(disk, &stblock) < 0) 575 err(1, "cannot stat %s", disk); 576 577 if (S_ISBLK(stblock.st_mode)) { 578 disk = rawname(disk); 579 } else if (!S_ISCHR(stblock.st_mode)) { 580 if ((fsp = getfsfile(disk)) == NULL) 581 err(1, "could not find file system %s", disk); 582 disk = rawname(fsp->fs_spec); 583 } 584 585 if ((diskfd = opendev(disk, O_RDONLY, 0, NULL)) < 0) 586 err(1, "cannot open %s", disk); 587 588 gotdev: 589 if (ioctl(diskfd, DIOCGDINFO, (char *)&lab) < 0) 590 err(1, "ioctl (DIOCGDINFO)"); 591 if (ioctl(diskfd, DIOCGPDINFO, (char *)&lab) < 0) 592 err(1, "ioctl (DIOCGPDINFO)"); 593 sblock = (struct fs *)sblock_buf; 594 for (i = 0; sblock_try[i] != -1; i++) { 595 n = pread(diskfd, sblock, SBLOCKSIZE, (off_t)sblock_try[i]); 596 if (n == SBLOCKSIZE && (sblock->fs_magic == FS_UFS1_MAGIC || 597 (sblock->fs_magic == FS_UFS2_MAGIC && 598 sblock->fs_sblockloc == sblock_try[i])) && 599 sblock->fs_bsize <= MAXBSIZE && 600 sblock->fs_bsize >= sizeof(struct fs)) 601 break; 602 } 603 if (sblock_try[i] == -1) 604 errx(1, "cannot find filesystem superblock"); 605 606 findinodes(sblock->fs_ipg * sblock->fs_ncg); 607 if (!format) 608 printf("%s:\n", disk); 609 scanonedir(ROOTINO, ""); 610 close(diskfd); 611 exit (0); 612 } 613 614 void 615 format_entry(const char *path, struct direct *dp) 616 { 617 static size_t size; 618 static char *buf; 619 char *src, *dst, *newbuf; 620 int len; 621 622 if (buf == NULL) { 623 if ((buf = malloc(LINE_MAX)) == NULL) 624 err(1, "malloc"); 625 size = LINE_MAX; 626 } 627 628 for (src = format, dst = buf; *src; src++) { 629 /* Need room for at least one character in buf. */ 630 if (size <= dst - buf) { 631 expand_buf: 632 if ((newbuf = reallocarray(buf, size, 2)) == NULL) 633 err(1, "realloc"); 634 buf = newbuf; 635 size = size * 2; 636 } 637 if (src[0] =='\\') { 638 switch (src[1]) { 639 case 'I': 640 len = snprintf(dst, size - (dst - buf), "%llu", 641 (unsigned long long)dp->d_ino); 642 if (len == -1 || len >= size - (dst - buf)) 643 goto expand_buf; 644 dst += len; 645 break; 646 case 'P': 647 len = snprintf(dst, size - (dst - buf), "%s/%s", 648 path, dp->d_name); 649 if (len == -1 || len >= size - (dst - buf)) 650 goto expand_buf; 651 dst += len; 652 break; 653 case '\\': 654 *dst++ = '\\'; 655 break; 656 case '0': 657 /* XXX - support other octal numbers? */ 658 *dst++ = '\0'; 659 break; 660 case 'a': 661 *dst++ = '\a'; 662 break; 663 case 'b': 664 *dst++ = '\b'; 665 break; 666 case 'e': 667 *dst++ = '\e'; 668 break; 669 case 'f': 670 *dst++ = '\f'; 671 break; 672 case 'n': 673 *dst++ = '\n'; 674 break; 675 case 'r': 676 *dst++ = '\r'; 677 break; 678 case 't': 679 *dst++ = '\t'; 680 break; 681 case 'v': 682 *dst++ = '\v'; 683 break; 684 default: 685 *dst++ = src[1]; 686 break; 687 } 688 src++; 689 } else 690 *dst++ = *src; 691 } 692 fwrite(buf, dst - buf, 1, stdout); 693 } 694