1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 2007-2009 Google Inc. and Amit Singh 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 are 9 * met: 10 * 11 * * Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * * Redistributions in binary form must reproduce the above 14 * copyright notice, this list of conditions and the following disclaimer 15 * in the documentation and/or other materials provided with the 16 * distribution. 17 * * Neither the name of Google Inc. nor the names of its 18 * contributors may be used to endorse or promote products derived from 19 * this software without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 * 33 * Copyright (C) 2005 Csaba Henk. 34 * All rights reserved. 35 * 36 * Redistribution and use in source and binary forms, with or without 37 * modification, are permitted provided that the following conditions 38 * are met: 39 * 1. Redistributions of source code must retain the above copyright 40 * notice, this list of conditions and the following disclaimer. 41 * 2. Redistributions in binary form must reproduce the above copyright 42 * notice, this list of conditions and the following disclaimer in the 43 * documentation and/or other materials provided with the distribution. 44 * 45 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 46 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 47 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 48 * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 49 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 50 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 51 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 52 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 53 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 54 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 55 * SUCH DAMAGE. 56 */ 57 58 #include <sys/cdefs.h> 59 __FBSDID("$FreeBSD$"); 60 61 #include <sys/param.h> 62 #include <sys/module.h> 63 #include <sys/systm.h> 64 #include <sys/errno.h> 65 #include <sys/kernel.h> 66 #include <sys/conf.h> 67 #include <sys/uio.h> 68 #include <sys/malloc.h> 69 #include <sys/queue.h> 70 #include <sys/lock.h> 71 #include <sys/mutex.h> 72 #include <sys/sdt.h> 73 #include <sys/sx.h> 74 #include <sys/proc.h> 75 #include <sys/mount.h> 76 #include <sys/vnode.h> 77 #include <sys/namei.h> 78 #include <sys/stat.h> 79 #include <sys/unistd.h> 80 #include <sys/filedesc.h> 81 #include <sys/file.h> 82 #include <sys/fcntl.h> 83 #include <sys/dirent.h> 84 #include <sys/bio.h> 85 #include <sys/buf.h> 86 #include <sys/sysctl.h> 87 #include <sys/priv.h> 88 89 #include "fuse.h" 90 #include "fuse_file.h" 91 #include "fuse_internal.h" 92 #include "fuse_ipc.h" 93 #include "fuse_node.h" 94 #include "fuse_file.h" 95 #include "fuse_param.h" 96 97 SDT_PROVIDER_DECLARE(fuse); 98 /* 99 * Fuse trace probe: 100 * arg0: verbosity. Higher numbers give more verbose messages 101 * arg1: Textual message 102 */ 103 SDT_PROBE_DEFINE2(fuse, , internal, trace, "int", "char*"); 104 105 #ifdef ZERO_PAD_INCOMPLETE_BUFS 106 static int isbzero(void *buf, size_t len); 107 108 #endif 109 110 /* access */ 111 112 int 113 fuse_internal_access(struct vnode *vp, 114 mode_t mode, 115 struct fuse_access_param *facp, 116 struct thread *td, 117 struct ucred *cred) 118 { 119 int err = 0; 120 uint32_t mask = 0; 121 int dataflags; 122 int vtype; 123 struct mount *mp; 124 struct fuse_dispatcher fdi; 125 struct fuse_access_in *fai; 126 struct fuse_data *data; 127 128 /* NOT YET DONE */ 129 /* 130 * If this vnop gives you trouble, just return 0 here for a lazy 131 * kludge. 132 */ 133 /* return 0;*/ 134 135 mp = vnode_mount(vp); 136 vtype = vnode_vtype(vp); 137 138 data = fuse_get_mpdata(mp); 139 dataflags = data->dataflags; 140 141 if ((mode & VWRITE) && vfs_isrdonly(mp)) { 142 return EACCES; 143 } 144 /* Unless explicitly permitted, deny everyone except the fs owner. */ 145 if (vnode_isvroot(vp) && !(facp->facc_flags & FACCESS_NOCHECKSPY)) { 146 if (!(dataflags & FSESS_DAEMON_CAN_SPY)) { 147 int denied = fuse_match_cred(data->daemoncred, 148 cred); 149 150 if (denied) { 151 return EPERM; 152 } 153 } 154 facp->facc_flags |= FACCESS_NOCHECKSPY; 155 } 156 if (!(facp->facc_flags & FACCESS_DO_ACCESS)) { 157 return 0; 158 } 159 if (((vtype == VREG) && (mode & VEXEC))) { 160 #ifdef NEED_MOUNT_ARGUMENT_FOR_THIS 161 /* Let the kernel handle this through open / close heuristics.*/ 162 return ENOTSUP; 163 #else 164 /* Let the kernel handle this. */ 165 return 0; 166 #endif 167 } 168 if (!fsess_isimpl(mp, FUSE_ACCESS)) { 169 /* Let the kernel handle this. */ 170 return 0; 171 } 172 if (dataflags & FSESS_DEFAULT_PERMISSIONS) { 173 /* Let the kernel handle this. */ 174 return 0; 175 } 176 if ((mode & VADMIN) != 0) { 177 err = priv_check_cred(cred, PRIV_VFS_ADMIN); 178 if (err) { 179 return err; 180 } 181 } 182 if ((mode & (VWRITE | VAPPEND | VADMIN)) != 0) { 183 mask |= W_OK; 184 } 185 if ((mode & VREAD) != 0) { 186 mask |= R_OK; 187 } 188 if ((mode & VEXEC) != 0) { 189 mask |= X_OK; 190 } 191 bzero(&fdi, sizeof(fdi)); 192 193 fdisp_init(&fdi, sizeof(*fai)); 194 fdisp_make_vp(&fdi, FUSE_ACCESS, vp, td, cred); 195 196 fai = fdi.indata; 197 fai->mask = F_OK; 198 fai->mask |= mask; 199 200 err = fdisp_wait_answ(&fdi); 201 fdisp_destroy(&fdi); 202 203 if (err == ENOSYS) { 204 fsess_set_notimpl(mp, FUSE_ACCESS); 205 err = 0; 206 } 207 return err; 208 } 209 210 /* 211 * Cache FUSE attributes from feo, in attr cache associated with vnode 'vp'. 212 * Optionally, if argument 'vap' is not NULL, store a copy of the converted 213 * attributes there as well. 214 * 215 * If the nominal attribute cache TTL is zero, do not cache on the 'vp' (but do 216 * return the result to the caller). 217 */ 218 void 219 fuse_internal_cache_attrs(struct vnode *vp, struct fuse_attr *attr, 220 uint64_t attr_valid, uint32_t attr_valid_nsec, struct vattr *vap) 221 { 222 struct mount *mp; 223 struct fuse_vnode_data *fvdat; 224 struct vattr *vp_cache_at; 225 226 mp = vnode_mount(vp); 227 fvdat = VTOFUD(vp); 228 229 /* Honor explicit do-not-cache requests from user filesystems. */ 230 if (attr_valid == 0 && attr_valid_nsec == 0) 231 fvdat->valid_attr_cache = false; 232 else 233 fvdat->valid_attr_cache = true; 234 235 vp_cache_at = VTOVA(vp); 236 237 if (vap == NULL && vp_cache_at == NULL) 238 return; 239 240 if (vap == NULL) 241 vap = vp_cache_at; 242 243 vattr_null(vap); 244 245 vap->va_fsid = mp->mnt_stat.f_fsid.val[0]; 246 vap->va_fileid = attr->ino; 247 vap->va_mode = attr->mode & ~S_IFMT; 248 vap->va_nlink = attr->nlink; 249 vap->va_uid = attr->uid; 250 vap->va_gid = attr->gid; 251 vap->va_rdev = attr->rdev; 252 vap->va_size = attr->size; 253 /* XXX on i386, seconds are truncated to 32 bits */ 254 vap->va_atime.tv_sec = attr->atime; 255 vap->va_atime.tv_nsec = attr->atimensec; 256 vap->va_mtime.tv_sec = attr->mtime; 257 vap->va_mtime.tv_nsec = attr->mtimensec; 258 vap->va_ctime.tv_sec = attr->ctime; 259 vap->va_ctime.tv_nsec = attr->ctimensec; 260 vap->va_blocksize = PAGE_SIZE; 261 vap->va_type = IFTOVT(attr->mode); 262 vap->va_bytes = attr->blocks * S_BLKSIZE; 263 vap->va_flags = 0; 264 265 if (vap != vp_cache_at && vp_cache_at != NULL) 266 memcpy(vp_cache_at, vap, sizeof(*vap)); 267 } 268 269 270 /* fsync */ 271 272 int 273 fuse_internal_fsync_callback(struct fuse_ticket *tick, struct uio *uio) 274 { 275 if (tick->tk_aw_ohead.error == ENOSYS) { 276 fsess_set_notimpl(tick->tk_data->mp, fticket_opcode(tick)); 277 } 278 return 0; 279 } 280 281 int 282 fuse_internal_fsync(struct vnode *vp, 283 struct thread *td, 284 struct ucred *cred, 285 struct fuse_filehandle *fufh) 286 { 287 int op = FUSE_FSYNC; 288 struct fuse_fsync_in *ffsi; 289 struct fuse_dispatcher fdi; 290 291 if (vnode_isdir(vp)) { 292 op = FUSE_FSYNCDIR; 293 } 294 fdisp_init(&fdi, sizeof(*ffsi)); 295 fdisp_make_vp(&fdi, op, vp, td, cred); 296 ffsi = fdi.indata; 297 ffsi->fh = fufh->fh_id; 298 299 ffsi->fsync_flags = 1; /* datasync */ 300 301 fuse_insert_callback(fdi.tick, fuse_internal_fsync_callback); 302 fuse_insert_message(fdi.tick); 303 304 fdisp_destroy(&fdi); 305 306 return 0; 307 308 } 309 310 /* readdir */ 311 312 int 313 fuse_internal_readdir(struct vnode *vp, 314 struct uio *uio, 315 struct fuse_filehandle *fufh, 316 struct fuse_iov *cookediov) 317 { 318 int err = 0; 319 struct fuse_dispatcher fdi; 320 struct fuse_read_in *fri; 321 322 if (uio_resid(uio) == 0) { 323 return 0; 324 } 325 fdisp_init(&fdi, 0); 326 327 /* 328 * Note that we DO NOT have a UIO_SYSSPACE here (so no need for p2p 329 * I/O). 330 */ 331 332 while (uio_resid(uio) > 0) { 333 334 fdi.iosize = sizeof(*fri); 335 fdisp_make_vp(&fdi, FUSE_READDIR, vp, NULL, NULL); 336 337 fri = fdi.indata; 338 fri->fh = fufh->fh_id; 339 fri->offset = uio_offset(uio); 340 fri->size = min(uio_resid(uio), FUSE_DEFAULT_IOSIZE); 341 /* mp->max_read */ 342 343 if ((err = fdisp_wait_answ(&fdi))) { 344 break; 345 } 346 if ((err = fuse_internal_readdir_processdata(uio, fri->size, fdi.answ, 347 fdi.iosize, cookediov))) { 348 break; 349 } 350 } 351 352 fdisp_destroy(&fdi); 353 return ((err == -1) ? 0 : err); 354 } 355 356 int 357 fuse_internal_readdir_processdata(struct uio *uio, 358 size_t reqsize, 359 void *buf, 360 size_t bufsize, 361 void *param) 362 { 363 int err = 0; 364 int cou = 0; 365 int bytesavail; 366 size_t freclen; 367 368 struct dirent *de; 369 struct fuse_dirent *fudge; 370 struct fuse_iov *cookediov = param; 371 372 if (bufsize < FUSE_NAME_OFFSET) { 373 return -1; 374 } 375 for (;;) { 376 377 if (bufsize < FUSE_NAME_OFFSET) { 378 err = -1; 379 break; 380 } 381 fudge = (struct fuse_dirent *)buf; 382 freclen = FUSE_DIRENT_SIZE(fudge); 383 384 cou++; 385 386 if (bufsize < freclen) { 387 err = ((cou == 1) ? -1 : 0); 388 break; 389 } 390 #ifdef ZERO_PAD_INCOMPLETE_BUFS 391 if (isbzero(buf, FUSE_NAME_OFFSET)) { 392 err = -1; 393 break; 394 } 395 #endif 396 397 if (!fudge->namelen || fudge->namelen > MAXNAMLEN) { 398 err = EINVAL; 399 break; 400 } 401 bytesavail = GENERIC_DIRSIZ((struct pseudo_dirent *) 402 &fudge->namelen); 403 404 if (bytesavail > uio_resid(uio)) { 405 err = -1; 406 break; 407 } 408 fiov_refresh(cookediov); 409 fiov_adjust(cookediov, bytesavail); 410 411 de = (struct dirent *)cookediov->base; 412 de->d_fileno = fudge->ino; 413 de->d_reclen = bytesavail; 414 de->d_type = fudge->type; 415 de->d_namlen = fudge->namelen; 416 memcpy((char *)cookediov->base + sizeof(struct dirent) - 417 MAXNAMLEN - 1, 418 (char *)buf + FUSE_NAME_OFFSET, fudge->namelen); 419 dirent_terminate(de); 420 421 err = uiomove(cookediov->base, cookediov->len, uio); 422 if (err) { 423 break; 424 } 425 buf = (char *)buf + freclen; 426 bufsize -= freclen; 427 uio_setoffset(uio, fudge->off); 428 } 429 430 return err; 431 } 432 433 /* remove */ 434 435 int 436 fuse_internal_remove(struct vnode *dvp, 437 struct vnode *vp, 438 struct componentname *cnp, 439 enum fuse_opcode op) 440 { 441 struct fuse_dispatcher fdi; 442 struct fuse_vnode_data *fvdat; 443 int err; 444 445 err = 0; 446 fvdat = VTOFUD(vp); 447 448 fdisp_init(&fdi, cnp->cn_namelen + 1); 449 fdisp_make_vp(&fdi, op, dvp, cnp->cn_thread, cnp->cn_cred); 450 451 memcpy(fdi.indata, cnp->cn_nameptr, cnp->cn_namelen); 452 ((char *)fdi.indata)[cnp->cn_namelen] = '\0'; 453 454 err = fdisp_wait_answ(&fdi); 455 fdisp_destroy(&fdi); 456 return err; 457 } 458 459 /* rename */ 460 461 int 462 fuse_internal_rename(struct vnode *fdvp, 463 struct componentname *fcnp, 464 struct vnode *tdvp, 465 struct componentname *tcnp) 466 { 467 struct fuse_dispatcher fdi; 468 struct fuse_rename_in *fri; 469 int err = 0; 470 471 fdisp_init(&fdi, sizeof(*fri) + fcnp->cn_namelen + tcnp->cn_namelen + 2); 472 fdisp_make_vp(&fdi, FUSE_RENAME, fdvp, tcnp->cn_thread, tcnp->cn_cred); 473 474 fri = fdi.indata; 475 fri->newdir = VTOI(tdvp); 476 memcpy((char *)fdi.indata + sizeof(*fri), fcnp->cn_nameptr, 477 fcnp->cn_namelen); 478 ((char *)fdi.indata)[sizeof(*fri) + fcnp->cn_namelen] = '\0'; 479 memcpy((char *)fdi.indata + sizeof(*fri) + fcnp->cn_namelen + 1, 480 tcnp->cn_nameptr, tcnp->cn_namelen); 481 ((char *)fdi.indata)[sizeof(*fri) + fcnp->cn_namelen + 482 tcnp->cn_namelen + 1] = '\0'; 483 484 err = fdisp_wait_answ(&fdi); 485 fdisp_destroy(&fdi); 486 return err; 487 } 488 489 /* strategy */ 490 491 /* entity creation */ 492 493 void 494 fuse_internal_newentry_makerequest(struct mount *mp, 495 uint64_t dnid, 496 struct componentname *cnp, 497 enum fuse_opcode op, 498 void *buf, 499 size_t bufsize, 500 struct fuse_dispatcher *fdip) 501 { 502 fdip->iosize = bufsize + cnp->cn_namelen + 1; 503 504 fdisp_make(fdip, op, mp, dnid, cnp->cn_thread, cnp->cn_cred); 505 memcpy(fdip->indata, buf, bufsize); 506 memcpy((char *)fdip->indata + bufsize, cnp->cn_nameptr, cnp->cn_namelen); 507 ((char *)fdip->indata)[bufsize + cnp->cn_namelen] = '\0'; 508 } 509 510 int 511 fuse_internal_newentry_core(struct vnode *dvp, 512 struct vnode **vpp, 513 struct componentname *cnp, 514 enum vtype vtyp, 515 struct fuse_dispatcher *fdip) 516 { 517 int err = 0; 518 struct fuse_entry_out *feo; 519 struct mount *mp = vnode_mount(dvp); 520 521 if ((err = fdisp_wait_answ(fdip))) { 522 return err; 523 } 524 feo = fdip->answ; 525 526 if ((err = fuse_internal_checkentry(feo, vtyp))) { 527 return err; 528 } 529 err = fuse_vnode_get(mp, feo, feo->nodeid, dvp, vpp, cnp, vtyp); 530 if (err) { 531 fuse_internal_forget_send(mp, cnp->cn_thread, cnp->cn_cred, 532 feo->nodeid, 1); 533 return err; 534 } 535 fuse_internal_cache_attrs(*vpp, &feo->attr, feo->attr_valid, 536 feo->attr_valid_nsec, NULL); 537 538 return err; 539 } 540 541 int 542 fuse_internal_newentry(struct vnode *dvp, 543 struct vnode **vpp, 544 struct componentname *cnp, 545 enum fuse_opcode op, 546 void *buf, 547 size_t bufsize, 548 enum vtype vtype) 549 { 550 int err; 551 struct fuse_dispatcher fdi; 552 struct mount *mp = vnode_mount(dvp); 553 554 fdisp_init(&fdi, 0); 555 fuse_internal_newentry_makerequest(mp, VTOI(dvp), cnp, op, buf, 556 bufsize, &fdi); 557 err = fuse_internal_newentry_core(dvp, vpp, cnp, vtype, &fdi); 558 fdisp_destroy(&fdi); 559 560 return err; 561 } 562 563 /* entity destruction */ 564 565 int 566 fuse_internal_forget_callback(struct fuse_ticket *ftick, struct uio *uio) 567 { 568 fuse_internal_forget_send(ftick->tk_data->mp, curthread, NULL, 569 ((struct fuse_in_header *)ftick->tk_ms_fiov.base)->nodeid, 1); 570 571 return 0; 572 } 573 574 void 575 fuse_internal_forget_send(struct mount *mp, 576 struct thread *td, 577 struct ucred *cred, 578 uint64_t nodeid, 579 uint64_t nlookup) 580 { 581 582 struct fuse_dispatcher fdi; 583 struct fuse_forget_in *ffi; 584 585 /* 586 * KASSERT(nlookup > 0, ("zero-times forget for vp #%llu", 587 * (long long unsigned) nodeid)); 588 */ 589 590 fdisp_init(&fdi, sizeof(*ffi)); 591 fdisp_make(&fdi, FUSE_FORGET, mp, nodeid, td, cred); 592 593 ffi = fdi.indata; 594 ffi->nlookup = nlookup; 595 596 fuse_insert_message(fdi.tick); 597 fdisp_destroy(&fdi); 598 } 599 600 void 601 fuse_internal_vnode_disappear(struct vnode *vp) 602 { 603 struct fuse_vnode_data *fvdat = VTOFUD(vp); 604 605 ASSERT_VOP_ELOCKED(vp, "fuse_internal_vnode_disappear"); 606 fvdat->flag |= FN_REVOKED; 607 fvdat->valid_attr_cache = false; 608 cache_purge(vp); 609 } 610 611 /* fuse start/stop */ 612 613 int 614 fuse_internal_init_callback(struct fuse_ticket *tick, struct uio *uio) 615 { 616 int err = 0; 617 struct fuse_data *data = tick->tk_data; 618 struct fuse_init_out *fiio; 619 620 if ((err = tick->tk_aw_ohead.error)) { 621 goto out; 622 } 623 if ((err = fticket_pull(tick, uio))) { 624 goto out; 625 } 626 fiio = fticket_resp(tick)->base; 627 628 /* XXX: Do we want to check anything further besides this? */ 629 if (fiio->major < 7) { 630 SDT_PROBE2(fuse, , internal, trace, 1, 631 "userpace version too low"); 632 err = EPROTONOSUPPORT; 633 goto out; 634 } 635 data->fuse_libabi_major = fiio->major; 636 data->fuse_libabi_minor = fiio->minor; 637 638 if (fuse_libabi_geq(data, 7, 5)) { 639 if (fticket_resp(tick)->len == sizeof(struct fuse_init_out)) { 640 data->max_write = fiio->max_write; 641 } else { 642 err = EINVAL; 643 } 644 } else { 645 /* Old fix values */ 646 data->max_write = 4096; 647 } 648 649 out: 650 if (err) { 651 fdata_set_dead(data); 652 } 653 FUSE_LOCK(); 654 data->dataflags |= FSESS_INITED; 655 wakeup(&data->ticketer); 656 FUSE_UNLOCK(); 657 658 return 0; 659 } 660 661 void 662 fuse_internal_send_init(struct fuse_data *data, struct thread *td) 663 { 664 struct fuse_init_in *fiii; 665 struct fuse_dispatcher fdi; 666 667 fdisp_init(&fdi, sizeof(*fiii)); 668 fdisp_make(&fdi, FUSE_INIT, data->mp, 0, td, NULL); 669 fiii = fdi.indata; 670 fiii->major = FUSE_KERNEL_VERSION; 671 fiii->minor = FUSE_KERNEL_MINOR_VERSION; 672 fiii->max_readahead = FUSE_DEFAULT_IOSIZE * 16; 673 fiii->flags = 0; 674 675 fuse_insert_callback(fdi.tick, fuse_internal_init_callback); 676 fuse_insert_message(fdi.tick); 677 fdisp_destroy(&fdi); 678 } 679 680 #ifdef ZERO_PAD_INCOMPLETE_BUFS 681 static int 682 isbzero(void *buf, size_t len) 683 { 684 int i; 685 686 for (i = 0; i < len; i++) { 687 if (((char *)buf)[i]) 688 return (0); 689 } 690 691 return (1); 692 } 693 694 #endif 695