]> git.saurik.com Git - apple/xnu.git/blob - bsd/ufs/ufs/ufs_bmap.c
4acc2dd91a46d2d2412df7d76df828cfb447f833
[apple/xnu.git] / bsd / ufs / ufs / ufs_bmap.c
1 /*
2 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * The contents of this file constitute Original Code as defined in and
7 * are subject to the Apple Public Source License Version 1.1 (the
8 * "License"). You may not use this file except in compliance with the
9 * License. Please obtain a copy of the License at
10 * http://www.apple.com/publicsource and read it before using this file.
11 *
12 * This Original Code and all software distributed under the License are
13 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
17 * License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * @APPLE_LICENSE_HEADER_END@
21 */
22 /* Copyright (c) 1995 NeXT Computer, Inc. All Rights Reserved */
23 /*
24 * Copyright (c) 1989, 1991, 1993
25 * The Regents of the University of California. All rights reserved.
26 * (c) UNIX System Laboratories, Inc.
27 * All or some portions of this file are derived from material licensed
28 * to the University of California by American Telephone and Telegraph
29 * Co. or Unix System Laboratories, Inc. and are reproduced herein with
30 * the permission of UNIX System Laboratories, Inc.
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. All advertising materials mentioning features or use of this software
41 * must display the following acknowledgement:
42 * This product includes software developed by the University of
43 * California, Berkeley and its contributors.
44 * 4. Neither the name of the University nor the names of its contributors
45 * may be used to endorse or promote products derived from this software
46 * without specific prior written permission.
47 *
48 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
49 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
50 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
51 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
52 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
53 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
54 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
55 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
56 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
57 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
58 * SUCH DAMAGE.
59 *
60 * @(#)ufs_bmap.c 8.7 (Berkeley) 3/21/95
61 */
62 /*
63 * HISTORY
64 * 11-July-97 Umesh Vaishampayan (umeshv@apple.com)
65 * Cleanup. Fixed compilation error when tracing is turned on.
66 */
67 #include <rev_endian_fs.h>
68 #include <sys/param.h>
69 #include <sys/buf.h>
70 #include <sys/proc.h>
71 #include <sys/vnode.h>
72 #include <sys/mount.h>
73 #include <sys/resourcevar.h>
74 #include <sys/trace.h>
75
76 #include <miscfs/specfs/specdev.h>
77
78 #include <ufs/ufs/quota.h>
79 #include <ufs/ufs/inode.h>
80 #include <ufs/ufs/ufsmount.h>
81 #include <ufs/ufs/ufs_extern.h>
82 #if REV_ENDIAN_FS
83 #include <ufs/ufs/ufs_byte_order.h>
84 #include <architecture/byte_order.h>
85 #endif /* REV_ENDIAN_FS */
86
87 /*
88 * Bmap converts a the logical block number of a file to its physical block
89 * number on the disk. The conversion is done by using the logical block
90 * number to index into the array of block pointers described by the dinode.
91 */
92 int
93 ufs_bmap(ap)
94 struct vop_bmap_args /* {
95 struct vnode *a_vp;
96 ufs_daddr_t a_bn;
97 struct vnode **a_vpp;
98 ufs_daddr_t *a_bnp;
99 int *a_runp;
100 } */ *ap;
101 {
102 /*
103 * Check for underlying vnode requests and ensure that logical
104 * to physical mapping is requested.
105 */
106 if (ap->a_vpp != NULL)
107 *ap->a_vpp = VTOI(ap->a_vp)->i_devvp;
108 if (ap->a_bnp == NULL)
109 return (0);
110
111 return (ufs_bmaparray(ap->a_vp, ap->a_bn, ap->a_bnp, NULL, NULL,
112 ap->a_runp));
113 }
114
115 /*
116 * Indirect blocks are now on the vnode for the file. They are given negative
117 * logical block numbers. Indirect blocks are addressed by the negative
118 * address of the first data block to which they point. Double indirect blocks
119 * are addressed by one less than the address of the first indirect block to
120 * which they point. Triple indirect blocks are addressed by one less than
121 * the address of the first double indirect block to which they point.
122 *
123 * ufs_bmaparray does the bmap conversion, and if requested returns the
124 * array of logical blocks which must be traversed to get to a block.
125 * Each entry contains the offset into that block that gets you to the
126 * next block and the disk address of the block (if it is assigned).
127 */
128
129 int
130 ufs_bmaparray(vp, bn, bnp, ap, nump, runp)
131 struct vnode *vp;
132 ufs_daddr_t bn;
133 ufs_daddr_t *bnp;
134 struct indir *ap;
135 int *nump;
136 int *runp;
137 {
138 register struct inode *ip;
139 struct buf *bp;
140 struct ufsmount *ump;
141 struct mount *mp;
142 struct vnode *devvp;
143 struct indir a[NIADDR], *xap;
144 ufs_daddr_t daddr;
145 long metalbn;
146 int error, maxrun, num;
147 #if REV_ENDIAN_FS
148 int rev_endian=0;
149 #endif /* REV_ENDIAN_FS */
150
151 ip = VTOI(vp);
152 mp = vp->v_mount;
153 ump = VFSTOUFS(mp);
154
155 #if REV_ENDIAN_FS
156 rev_endian=(mp->mnt_flag & MNT_REVEND);
157 #endif /* REV_ENDIAN_FS */
158
159 #if DIAGNOSTIC
160 if (ap != NULL && nump == NULL || ap == NULL && nump != NULL)
161 panic("ufs_bmaparray: invalid arguments");
162 #endif
163
164 if (runp) {
165 /*
166 * XXX
167 * If MAXPHYSIO is the largest transfer the disks can handle,
168 * we probably want maxrun to be 1 block less so that we
169 * don't create a block larger than the device can handle.
170 */
171 *runp = 0;
172 maxrun = MAXPHYSIO / mp->mnt_stat.f_iosize - 1;
173 }
174
175 xap = ap == NULL ? a : ap;
176 if (!nump)
177 nump = &num;
178 if (error = ufs_getlbns(vp, bn, xap, nump))
179 return (error);
180
181 num = *nump;
182 if (num == 0) {
183 *bnp = blkptrtodb(ump, ip->i_db[bn]);
184 if (*bnp == 0)
185 *bnp = -1;
186 else if (runp)
187 for (++bn; bn < NDADDR && *runp < maxrun &&
188 is_sequential(ump, ip->i_db[bn - 1], ip->i_db[bn]);
189 ++bn, ++*runp);
190 return (0);
191 }
192
193
194 /* Get disk address out of indirect block array */
195 daddr = ip->i_ib[xap->in_off];
196
197 devvp = VFSTOUFS(vp->v_mount)->um_devvp;
198 for (bp = NULL, ++xap; --num; ++xap) {
199 /*
200 * Exit the loop if there is no disk address assigned yet and
201 * the indirect block isn't in the cache, or if we were
202 * looking for an indirect block and we've found it.
203 */
204
205 metalbn = xap->in_lbn;
206 if (daddr == 0 && !incore(vp, metalbn) || metalbn == bn)
207 break;
208 /*
209 * If we get here, we've either got the block in the cache
210 * or we have a disk address for it, go fetch it.
211 */
212 if (bp)
213 brelse(bp);
214
215 xap->in_exists = 1;
216 bp = getblk(vp, metalbn, mp->mnt_stat.f_iosize, 0, 0, BLK_META);
217 if (bp->b_flags & (B_DONE | B_DELWRI)) {
218 trace(TR_BREADHIT, pack(vp, mp->mnt_stat.f_iosize), metalbn);
219 }
220 #if DIAGNOSTIC
221 else if (!daddr)
222 panic("ufs_bmaparry: indirect block not in cache");
223 #endif
224 else {
225 trace(TR_BREADMISS, pack(vp, mp->mnt_stat.f_iosize), metalbn);
226 bp->b_blkno = blkptrtodb(ump, daddr);
227 bp->b_flags |= B_READ;
228 VOP_STRATEGY(bp);
229 current_proc()->p_stats->p_ru.ru_inblock++; /* XXX */
230 if (error = biowait(bp)) {
231 brelse(bp);
232 return (error);
233 }
234 }
235
236 daddr = ((ufs_daddr_t *)bp->b_data)[xap->in_off];
237 #if REV_ENDIAN_FS
238 if (rev_endian)
239 daddr = NXSwapLong(daddr);
240 #endif /* REV_ENDIAN_FS */
241 if (num == 1 && daddr && runp) {
242 #if REV_ENDIAN_FS
243 if (rev_endian) {
244 for (bn = xap->in_off + 1;
245 bn < MNINDIR(ump) && *runp < maxrun &&
246 is_sequential(ump,
247 NXSwapLong(((ufs_daddr_t *)bp->b_data)[bn - 1]),
248 NXSwapLong(((ufs_daddr_t *)bp->b_data)[bn]));
249 ++bn, ++*runp);
250 } else {
251 #endif /* REV_ENDIAN_FS */
252 for (bn = xap->in_off + 1;
253 bn < MNINDIR(ump) && *runp < maxrun &&
254 is_sequential(ump,
255 ((ufs_daddr_t *)bp->b_data)[bn - 1],
256 ((ufs_daddr_t *)bp->b_data)[bn]);
257 ++bn, ++*runp);
258 #if REV_ENDIAN_FS
259 }
260 #endif /* REV_ENDIAN_FS */
261 }
262 }
263 if (bp)
264 brelse(bp);
265
266 daddr = blkptrtodb(ump, daddr);
267 *bnp = daddr == 0 ? -1 : daddr;
268 return (0);
269 }
270
271 /*
272 * Create an array of logical block number/offset pairs which represent the
273 * path of indirect blocks required to access a data block. The first "pair"
274 * contains the logical block number of the appropriate single, double or
275 * triple indirect block and the offset into the inode indirect block array.
276 * Note, the logical block number of the inode single/double/triple indirect
277 * block appears twice in the array, once with the offset into the i_ib and
278 * once with the offset into the page itself.
279 */
280 int
281 ufs_getlbns(vp, bn, ap, nump)
282 struct vnode *vp;
283 ufs_daddr_t bn;
284 struct indir *ap;
285 int *nump;
286 {
287 long metalbn, realbn;
288 struct ufsmount *ump;
289 int blockcnt, i, numlevels, off;
290
291 ump = VFSTOUFS(vp->v_mount);
292 if (nump)
293 *nump = 0;
294 numlevels = 0;
295 realbn = bn;
296 if ((long)bn < 0)
297 bn = -(long)bn;
298
299 /* The first NDADDR blocks are direct blocks. */
300 if (bn < NDADDR)
301 return (0);
302
303 /*
304 * Determine the number of levels of indirection. After this loop
305 * is done, blockcnt indicates the number of data blocks possible
306 * at the given level of indirection, and NIADDR - i is the number
307 * of levels of indirection needed to locate the requested block.
308 */
309 for (blockcnt = 1, i = NIADDR, bn -= NDADDR;; i--, bn -= blockcnt) {
310 if (i == 0)
311 return (EFBIG);
312 blockcnt *= MNINDIR(ump);
313 if (bn < blockcnt)
314 break;
315 }
316
317 /* Calculate the address of the first meta-block. */
318 if (realbn >= 0)
319 metalbn = -(realbn - bn + NIADDR - i);
320 else
321 metalbn = -(-realbn - bn + NIADDR - i);
322
323 /*
324 * At each iteration, off is the offset into the bap array which is
325 * an array of disk addresses at the current level of indirection.
326 * The logical block number and the offset in that block are stored
327 * into the argument array.
328 */
329 ap->in_lbn = metalbn;
330 ap->in_off = off = NIADDR - i;
331 ap->in_exists = 0;
332 ap++;
333 for (++numlevels; i <= NIADDR; i++) {
334 /* If searching for a meta-data block, quit when found. */
335 if (metalbn == realbn)
336 break;
337
338 blockcnt /= MNINDIR(ump);
339 off = (bn / blockcnt) % MNINDIR(ump);
340
341 ++numlevels;
342 ap->in_lbn = metalbn;
343 ap->in_off = off;
344 ap->in_exists = 0;
345 ++ap;
346
347 metalbn -= -1 + off * blockcnt;
348 }
349 if (nump)
350 *nump = numlevels;
351 return (0);
352 }
353 /*
354 * Cmap converts a the file offset of a file to its physical block
355 * number on the disk And returns contiguous size for transfer.
356 */
357 int
358 ufs_cmap(ap)
359 struct vop_cmap_args /* {
360 struct vnode *a_vp;
361 off_t a_foffset;
362 size_t a_size;
363 daddr_t *a_bpn;
364 size_t *a_run;
365 void *a_poff;
366 } */ *ap;
367 {
368 struct vnode * vp = ap->a_vp;
369 ufs_daddr_t *bnp = ap->a_bpn;
370 size_t *runp = ap->a_run;
371 int size = ap->a_size;
372 daddr_t bn;
373 int nblks;
374 register struct inode *ip;
375 ufs_daddr_t daddr = 0;
376 int devBlockSize=0;
377 struct fs *fs;
378 int retsize=0;
379 int error=0;
380
381 ip = VTOI(vp);
382 fs = ip->i_fs;
383
384
385 if (blkoff(fs, ap->a_foffset)) {
386 panic("ufs_cmap; allocation requested inside a block");
387 }
388
389 bn = (daddr_t)lblkno(fs, ap->a_foffset);
390 VOP_DEVBLOCKSIZE(ip->i_devvp, &devBlockSize);
391
392 if (size % devBlockSize) {
393 panic("ufs_cmap: size is not multiple of device block size\n");
394 }
395
396 if (error = VOP_BMAP(vp, bn, (struct vnode **) 0, &daddr, &nblks)) {
397 return(error);
398 }
399
400 retsize = nblks * fs->fs_bsize;
401
402 if (bnp)
403 *bnp = daddr;
404
405 if (ap->a_poff)
406 *(int *)ap->a_poff = 0;
407
408 if (daddr == -1) {
409 if (size < fs->fs_bsize) {
410 retsize = fragroundup(fs, size);
411 if(size >= retsize)
412 *runp = retsize;
413 else
414 *runp = size;
415 } else {
416 *runp = fs->fs_bsize;
417 }
418 return(0);
419 }
420
421 if (runp) {
422 if ((size < fs->fs_bsize)) {
423 *runp = size;
424 return(0);
425 }
426 if (retsize) {
427 retsize += fs->fs_bsize;
428 if(size >= retsize)
429 *runp = retsize;
430 else
431 *runp = size;
432 } else {
433 if (size < fs->fs_bsize) {
434 retsize = fragroundup(fs, size);
435 if(size >= retsize)
436 *runp = retsize;
437 else
438 *runp = size;
439 } else {
440 *runp = fs->fs_bsize;
441 }
442 }
443 }
444 return (0);
445 }
446
447
448 #if NOTTOBEUSED
449 /*
450 * Cmap converts a the file offset of a file to its physical block
451 * number on the disk And returns contiguous size for transfer.
452 */
453 int
454 ufs_cmap(ap)
455 struct vop_cmap_args /* {
456 struct vnode *a_vp;
457 off_t a_foffset;
458 size_t a_size;
459 daddr_t *a_bpn;
460 size_t *a_run;
461 void *a_poff;
462 } */ *ap;
463 {
464 struct vnode * vp = ap->a_vp;
465 ufs_daddr_t *bnp = ap->a_bpn;
466 size_t *runp = ap->a_run;
467 daddr_t bn;
468 int nblks, blks;
469 int *nump;
470 register struct inode *ip;
471 struct buf *bp;
472 struct ufsmount *ump;
473 struct mount *mp;
474 struct vnode *devvp;
475 struct indir a[NIADDR], *xap;
476 ufs_daddr_t daddr;
477 long metalbn;
478 int error, maxrun, num;
479 int devBlockSize=0;
480 struct fs *fs;
481 int size = ap->a_size;
482 int block_offset=0;
483 int retsize=0;
484 #if 1
485 daddr_t orig_blkno;
486 daddr_t orig_bblkno;
487 #endif /* 1 */
488 #if REV_ENDIAN_FS
489 int rev_endian=0;
490 #endif /* REV_ENDIAN_FS */
491
492 ip = VTOI(vp);
493 fs = ip->i_fs;
494
495 mp = vp->v_mount;
496 ump = VFSTOUFS(mp);
497
498 VOP_DEVBLOCKSIZE(ip->i_devvp, &devBlockSize);
499 bn = (daddr_t)lblkno(fs, ap->a_foffset);
500
501 if (size % devBlockSize) {
502 panic("ufs_cmap: size is not multiple of device block size\n");
503 }
504
505 block_offset = blkoff(fs, ap->a_foffset);
506 if (block_offset) {
507 panic("ufs_cmap; allocation requested inside a block");
508 }
509
510 #if 1
511 VOP_OFFTOBLK(vp, ap->a_foffset, & orig_blkno);
512 #endif /* 1 */
513 /* less than block size and not block offset aligned */
514 if ( (size < fs->fs_bsize) && fragoff(fs, size) && block_offset ) {
515 panic("ffs_cmap: size not a mult of fragment\n");
516 }
517 #if 0
518 if (size > fs->fs_bsize && fragoff(fs, size)) {
519 panic("ffs_cmap: more than bsize & not a multiple of fragment\n");
520 }
521 #endif /* 0 */
522 #if REV_ENDIAN_FS
523 rev_endian=(mp->mnt_flag & MNT_REVEND);
524 #endif /* REV_ENDIAN_FS */
525
526 if(runp)
527 *runp = 0;
528
529 if ( size > MAXPHYSIO)
530 size = MAXPHYSIO;
531 nblks = (blkroundup(fs, size))/fs->fs_bsize;
532
533 xap = a;
534 num = 0;
535 if (error = ufs_getlbns(vp, bn, xap, &num))
536 return (error);
537
538 blks = 0;
539 if (num == 0) {
540 daddr = blkptrtodb(ump, ip->i_db[bn]);
541 *bnp = ((daddr == 0) ? -1 : daddr);
542 if (daddr && runp) {
543 for (++bn; bn < NDADDR && blks < nblks &&
544 ip->i_db[bn] &&
545 is_sequential(ump, ip->i_db[bn - 1], ip->i_db[bn]);
546 ++bn, ++blks);
547
548 if (blks) {
549 retsize = lblktosize(fs, blks);
550 if(size >= retsize)
551 *runp = retsize;
552 else
553 *runp = size;
554 } else {
555 if (size < fs->fs_bsize) {
556 retsize = fragroundup(fs, size);
557 if(size >= retsize)
558 *runp = retsize;
559 else
560 *runp = size;
561 } else {
562 *runp = fs->fs_bsize;
563 }
564 }
565 if (ap->a_poff)
566 *(int *)ap->a_poff = 0;
567 }
568 #if 1
569 if (VOP_BMAP(vp, orig_blkno, NULL, &orig_bblkno, NULL)) {
570 panic("vop_bmap failed\n");
571 }
572 if(daddr != orig_bblkno) {
573 panic("vop_bmap and vop_cmap differ\n");
574 }
575 #endif /* 1 */
576 return (0);
577 }
578
579
580 /* Get disk address out of indirect block array */
581 daddr = ip->i_ib[xap->in_off];
582
583 devvp = VFSTOUFS(vp->v_mount)->um_devvp;
584 for (bp = NULL, ++xap; --num; ++xap) {
585 /*
586 * Exit the loop if there is no disk address assigned yet
587 * or if we were looking for an indirect block and we've
588 * found it.
589 */
590
591 metalbn = xap->in_lbn;
592 if (daddr == 0 || metalbn == bn)
593 break;
594 /*
595 * We have a disk address for it, go fetch it.
596 */
597 if (bp)
598 brelse(bp);
599
600 xap->in_exists = 1;
601 bp = getblk(vp, metalbn, mp->mnt_stat.f_iosize, 0, 0, BLK_META);
602 if (bp->b_flags & (B_DONE | B_DELWRI)) {
603 trace(TR_BREADHIT, pack(vp, mp->mnt_stat.f_iosize), metalbn);
604 }
605 else {
606 trace(TR_BREADMISS, pack(vp, mp->mnt_stat.f_iosize), metalbn);
607 bp->b_blkno = blkptrtodb(ump, daddr);
608 bp->b_flags |= B_READ;
609 VOP_STRATEGY(bp);
610 current_proc()->p_stats->p_ru.ru_inblock++; /* XXX */
611 if (error = biowait(bp)) {
612 brelse(bp);
613 return (error);
614 }
615 }
616
617 daddr = ((ufs_daddr_t *)bp->b_data)[xap->in_off];
618 #if REV_ENDIAN_FS
619 if (rev_endian)
620 daddr = NXSwapLong(daddr);
621 #endif /* REV_ENDIAN_FS */
622 if (num == 1 && daddr && runp) {
623 blks = 0;
624 #if REV_ENDIAN_FS
625 if (rev_endian) {
626 for (bn = xap->in_off + 1;
627 bn < MNINDIR(ump) && blks < maxrun &&
628 is_sequential(ump,
629 NXSwapLong(((ufs_daddr_t *)bp->b_data)[bn - 1]),
630 NXSwapLong(((ufs_daddr_t *)bp->b_data)[bn]));
631 ++bn, ++blks);
632 } else {
633 #endif /* REV_ENDIAN_FS */
634 for (bn = xap->in_off + 1;
635 bn < MNINDIR(ump) && blks < maxrun &&
636 is_sequential(ump,
637 ((ufs_daddr_t *)bp->b_data)[bn - 1],
638 ((ufs_daddr_t *)bp->b_data)[bn]);
639 ++bn, ++blks);
640 #if REV_ENDIAN_FS
641 }
642 #endif /* REV_ENDIAN_FS */
643 }
644 }
645 if (bp)
646 brelse(bp);
647
648 daddr = blkptrtodb(ump, daddr);
649 *bnp = ((daddr == 0) ? -1 : daddr);
650 if (daddr && runp) {
651 if (blks) {
652 retsize = lblktosize(fs, blks);
653 if(size >= retsize)
654 *runp = retsize;
655 else
656 *runp = size;
657 } else {
658 if (size < fs->fs_bsize) {
659 retsize = fragroundup(fs, size);
660 if(size >= retsize)
661 *runp = retsize;
662 else
663 *runp = size;
664 } else {
665 *runp = fs->fs_bsize;
666 }
667 }
668
669 }
670 if (daddr && ap->a_poff)
671 *(int *)ap->a_poff = 0;
672 #if 1
673 if (VOP_BMAP(vp, orig_blkno, (struct vnode **) 0, &orig_bblkno, 0)) {
674 panic("vop_bmap failed\n");
675 }
676 if(daddr != orig_bblkno) {
677 panic("vop_bmap and vop_cmap differ\n");
678 }
679 #endif /* 1 */
680 return (0);
681 }
682 #endif /* NOTTOBEUSED */