copy individual buffers into 56K chunks for disk I/O
[unix-history] / usr / src / sys / ufs / lfs / lfs_segment.c
index 4e41344..fc92e50 100644 (file)
@@ -4,7 +4,7 @@
  *
  * %sccs.include.redist.c%
  *
  *
  * %sccs.include.redist.c%
  *
- *     @(#)lfs_segment.c       7.9 (Berkeley) %G%
+ *     @(#)lfs_segment.c       7.13 (Berkeley) %G%
  */
 
 #include <sys/param.h>
  */
 
 #include <sys/param.h>
@@ -64,7 +64,7 @@ int    lfs_match_dindir __P((struct lfs *, struct buf *));
 int     lfs_match_indir __P((struct lfs *, struct buf *));
 int     lfs_match_tindir __P((struct lfs *, struct buf *));
 struct buf *
 int     lfs_match_indir __P((struct lfs *, struct buf *));
 int     lfs_match_tindir __P((struct lfs *, struct buf *));
 struct buf *
-        lfs_newbuf __P((struct lfs *, struct segment *, daddr_t, size_t));
+        lfs_newbuf __P((struct lfs *, daddr_t, size_t));
 void    lfs_newseg __P((struct lfs *));
 void    lfs_shellsort __P((struct buf **, daddr_t *, register int));
 void    lfs_updatemeta __P((struct lfs *,
 void    lfs_newseg __P((struct lfs *));
 void    lfs_shellsort __P((struct buf **, daddr_t *, register int));
 void    lfs_updatemeta __P((struct lfs *,
@@ -76,6 +76,83 @@ void  lfs_writesuper __P((struct lfs *, struct segment *));
 
 int    lfs_allclean_wakeup;            /* Cleaner wakeup address. */
 
 
 int    lfs_allclean_wakeup;            /* Cleaner wakeup address. */
 
+/*
+ * Ifile and meta data blocks are not marked busy, so segment writes MUST be
+ * single threaded.  Currently, there are two paths into lfs_segwrite, sync()
+ * and getnewbuf().  They both mark the file system busy.  Lfs_vflush()
+ * explicitly marks the file system busy.  So lfs_segwrite is safe.  I think.
+ */
+
+int
+lfs_vflush(vp)
+       struct vnode *vp;
+{
+       struct inode *ip;
+       struct lfs *fs;
+       struct mount *mp;
+       struct segment *sp;
+       int error, s;
+
+#ifdef VERBOSE
+       printf("lfs_vflush\n");
+#endif
+       mp = vp->v_mount;
+       fs = VFSTOUFS(mp)->um_lfs;
+
+       /*
+        * XXX
+        * check flags?
+        * mp->mnt_flag & (MNT_MLOCK|MNT_RDONLY|MNT_MPBUSY) ||
+        */
+       if (vfs_busy(mp))
+               return (0);
+
+       /*
+        * Allocate a segment structure and enough space to hold pointers to
+        * the maximum possible number of buffers which can be described in a
+        * single summary block.
+        */
+       sp = malloc(sizeof(struct segment), M_SEGMENT, M_WAITOK);
+       sp->bpp = malloc(((LFS_SUMMARY_SIZE - sizeof(SEGSUM)) /
+           sizeof(daddr_t) + 1) * sizeof(struct buf *), M_SEGMENT, M_WAITOK);
+       sp->seg_flags = SEGM_CKP;
+       lfs_initseg(fs, sp);
+
+       /*
+        * Keep a cumulative count of the outstanding I/O operations.  If the
+        * disk drive catches up with us it could go to zero before we finish,
+        * so we artificially increment it by one until we've scheduled all of
+        * the writes we intend to do.
+        */
+       s = splbio();
+       ++fs->lfs_iocount;
+       splx(s);
+
+       if (vp->v_dirtyblkhd != NULL)
+               lfs_writefile(fs, sp, vp);
+       ip = VTOI(vp);
+       lfs_writeinode(fs, sp, ip);
+       ip->i_flags &= ~(IMOD | IACC | IUPD | ICHG);
+
+       lfs_writeseg(fs, sp);
+
+       /*
+        * If the I/O count is non-zero, sleep until it reaches zero.  At the
+        * moment, the user's process hangs around so we can sleep.
+        */
+       s = splbio();
+       if (--fs->lfs_iocount && (error =
+           tsleep(&fs->lfs_iocount, PRIBIO + 1, "lfs vflush", 0)))
+               return (error);
+       splx(s);
+       vfs_unbusy(mp);
+
+       free(sp->bpp, M_SEGMENT);
+       free(sp, M_SEGMENT);
+
+       return (0);
+}
+
 int
 lfs_segwrite(mp, do_ckp)
        struct mount *mp;
 int
 lfs_segwrite(mp, do_ckp)
        struct mount *mp;
@@ -85,30 +162,12 @@ lfs_segwrite(mp, do_ckp)
        struct lfs *fs;
        struct segment *sp;
        struct vnode *vp;
        struct lfs *fs;
        struct segment *sp;
        struct vnode *vp;
-       int s, error;
+       int error, islocked, s;
 
 
-       /*
-        * Ifile and meta data blocks are not marked busy, so segment writes
-        * must be single threaded.  Currently, there are two paths into this
-        * code, sync() and getnewbuf().  They both mark the file system busy,
-        * so lfs_segwrite is safe.  I think.
-        */
 #ifdef VERBOSE
        printf("lfs_segwrite\n");
 #endif
 #ifdef VERBOSE
        printf("lfs_segwrite\n");
 #endif
-
-       /*
-        * If doing a checkpoint, we keep a cumulative count of the outstanding
-        * I/O operations.  If the disk drive catches up with us it could go to
-        * zero before we finish, so we artificially increment it by one until
-        * we've scheduled all of the writes we intend to do.
-        */
        fs = VFSTOUFS(mp)->um_lfs;
        fs = VFSTOUFS(mp)->um_lfs;
-       if (do_ckp) {
-               s = splbio();
-               fs->lfs_iocount = 1;
-               splx(s);
-       }
 
        /*
         * Allocate a segment structure and enough space to hold pointers to
 
        /*
         * Allocate a segment structure and enough space to hold pointers to
@@ -120,26 +179,27 @@ lfs_segwrite(mp, do_ckp)
            sizeof(daddr_t) + 1) * sizeof(struct buf *), M_SEGMENT, M_WAITOK);
        sp->seg_flags = do_ckp ? SEGM_CKP : 0;
        lfs_initseg(fs, sp);
            sizeof(daddr_t) + 1) * sizeof(struct buf *), M_SEGMENT, M_WAITOK);
        sp->seg_flags = do_ckp ? SEGM_CKP : 0;
        lfs_initseg(fs, sp);
-loop:
-       for (vp = mp->mnt_mounth; vp; vp = vp->v_mountf) {
+
+       /*
+        * Keep a cumulative count of the outstanding I/O operations.  If the
+        * disk drive catches up with us it could go to zero before we finish,
+        * so we artificially increment it by one until we've scheduled all of
+        * the writes we intend to do.  If not a checkpoint, we never do the
+        * final decrement, avoiding the wakeup in the callback routine.
+        */
+       s = splbio();
+       ++fs->lfs_iocount;
+       splx(s);
+
+loop:  for (vp = mp->mnt_mounth; vp; vp = vp->v_mountf) {
                /*
                 * If the vnode that we are about to sync is no longer
                 * associated with this mount point, start over.
                 */
                if (vp->v_mount != mp)
                        goto loop;
                /*
                 * If the vnode that we are about to sync is no longer
                 * associated with this mount point, start over.
                 */
                if (vp->v_mount != mp)
                        goto loop;
-               if (VOP_ISLOCKED(vp))
-                       continue;
 
 
-               /*
-                * Write the inode/file if dirty and it's not the
-                * the IFILE.
-                */
-               ip = VTOI(vp);
-               if (ip->i_flag & (IMOD | IACC | IUPD | ICHG) == 0 &&
-                   vp->v_dirtyblkhd == NULL ||
-                   ip->i_number == LFS_IFILE_INUM)
-                       continue;
+               islocked = VOP_ISLOCKED(vp);
 
                /*
                 * XXX
 
                /*
                 * XXX
@@ -147,17 +207,30 @@ loop:
                 * get the vnode and go on.  Probably going to reschedule
                 * all of the writes we already scheduled...
                 */
                 * get the vnode and go on.  Probably going to reschedule
                 * all of the writes we already scheduled...
                 */
-               if (vget(vp))
+               if (islocked)
+                       VREF(vp);
+               else if (vget(vp))
 {
 printf("lfs_segment: failed to get vnode (tell Keith)!\n");
                        goto loop;
 }
 {
 printf("lfs_segment: failed to get vnode (tell Keith)!\n");
                        goto loop;
 }
-
-               if (vp->v_dirtyblkhd != NULL)
-                       lfs_writefile(fs, sp, vp);
-               lfs_writeinode(fs, sp, ip);
-               ip->i_flags &= ~(IMOD | IACC | IUPD | ICHG);
-               vput(vp);
+               /*
+                * Write the inode/file if dirty and it's not the
+                * the IFILE.
+                */
+               ip = VTOI(vp);
+               if ((ip->i_flag & (IMOD | IACC | IUPD | ICHG) ||
+                   vp->v_dirtyblkhd != NULL) &&
+                   ip->i_number != LFS_IFILE_INUM) {
+                       if (vp->v_dirtyblkhd != NULL)
+                               lfs_writefile(fs, sp, vp);
+                       lfs_writeinode(fs, sp, ip);
+                       ip->i_flags &= ~(IMOD | IACC | IUPD | ICHG);
+               }
+               if (islocked)
+                       vrele(vp);
+               else
+                       vput(vp);
        }
        if (do_ckp) {
                vp = fs->lfs_ivnode;
        }
        if (do_ckp) {
                vp = fs->lfs_ivnode;
@@ -175,22 +248,20 @@ printf("lfs_segment: failed to get vnode (tell Keith)!\n");
         * If the I/O count is non-zero, sleep until it reaches zero.  At the
         * moment, the user's process hangs around so we can sleep.
         */
         * If the I/O count is non-zero, sleep until it reaches zero.  At the
         * moment, the user's process hangs around so we can sleep.
         */
+       s = splbio();
+       --fs->lfs_iocount;
        if (do_ckp) {
        if (do_ckp) {
-               s = splbio();
-               if (--fs->lfs_iocount && (error =
+               if (fs->lfs_iocount && (error =
                    tsleep(&fs->lfs_iocount, PRIBIO + 1, "lfs sync", 0)))
                        return (error);
                splx(s);
                lfs_writesuper(fs, sp);
                    tsleep(&fs->lfs_iocount, PRIBIO + 1, "lfs sync", 0)))
                        return (error);
                splx(s);
                lfs_writesuper(fs, sp);
-       }
+       } else 
+               splx(s);
 
        free(sp->bpp, M_SEGMENT);
        free(sp, M_SEGMENT);
 
 
        free(sp->bpp, M_SEGMENT);
        free(sp, M_SEGMENT);
 
-       /* Wake up any cleaning processes waiting on this file system. */
-       wakeup(&fs->lfs_nextseg);
-       wakeup(&lfs_allclean_wakeup);
-
        return (0);
 }
 
        return (0);
 }
 
@@ -245,7 +316,8 @@ lfs_writefile(fs, sp, vp)
                sp->fip =
                    (struct finfo *)((caddr_t)fip + sizeof(struct finfo) +
                    sizeof(daddr_t) * (fip->fi_nblocks - 1));
                sp->fip =
                    (struct finfo *)((caddr_t)fip + sizeof(struct finfo) +
                    sizeof(daddr_t) * (fip->fi_nblocks - 1));
-       }
+       } else
+               sp->sum_bytes_left += sizeof(struct finfo) - sizeof(daddr_t);
 }
 
 void
 }
 
 void
@@ -256,7 +328,8 @@ lfs_writeinode(fs, sp, ip)
 {
        struct buf *bp, *ibp;
        IFILE *ifp;
 {
        struct buf *bp, *ibp;
        IFILE *ifp;
-       daddr_t next_addr;
+       SEGUSE *sup;
+       daddr_t daddr;
        ino_t ino;
        int ndx;
 
        ino_t ino;
        int ndx;
 
@@ -273,17 +346,17 @@ lfs_writeinode(fs, sp, ip)
                }
 
                /* Get next inode block. */
                }
 
                /* Get next inode block. */
-               next_addr = fs->lfs_offset;
+               daddr = fs->lfs_offset;
                fs->lfs_offset += fsbtodb(fs, 1);
                sp->ibp = *sp->cbpp++ =
                fs->lfs_offset += fsbtodb(fs, 1);
                sp->ibp = *sp->cbpp++ =
-                   lfs_newbuf(fs, sp, next_addr, fs->lfs_bsize);
+                   lfs_newbuf(fs, daddr, fs->lfs_bsize);
 
 
-               /* Set remaining space counter. */
+               /* Set remaining space counters. */
                sp->seg_bytes_left -= fs->lfs_bsize;
                sp->sum_bytes_left -= sizeof(daddr_t);
                ndx = LFS_SUMMARY_SIZE / sizeof(daddr_t) -
                    sp->ninodes / INOPB(fs) - 1;
                sp->seg_bytes_left -= fs->lfs_bsize;
                sp->sum_bytes_left -= sizeof(daddr_t);
                ndx = LFS_SUMMARY_SIZE / sizeof(daddr_t) -
                    sp->ninodes / INOPB(fs) - 1;
-               ((daddr_t *)(sp->segsum))[ndx] = next_addr;
+               ((daddr_t *)(sp->segsum))[ndx] = daddr;
        }
 
        /* Update the inode times and copy the inode onto the inode page. */
        }
 
        /* Update the inode times and copy the inode onto the inode page. */
@@ -307,8 +380,20 @@ lfs_writeinode(fs, sp, ip)
                fs->lfs_idaddr = bp->b_blkno;
 
        LFS_IENTRY(ifp, fs, ino, ibp);
                fs->lfs_idaddr = bp->b_blkno;
 
        LFS_IENTRY(ifp, fs, ino, ibp);
+       daddr = ifp->if_daddr;
        ifp->if_daddr = bp->b_blkno;
        LFS_UBWRITE(ibp);
        ifp->if_daddr = bp->b_blkno;
        LFS_UBWRITE(ibp);
+
+       if (daddr != LFS_UNUSED_DADDR) {
+               LFS_SEGENTRY(sup, fs, datosn(fs, daddr), bp);
+#ifdef DIAGNOSTIC
+               if (sup->su_nbytes < sizeof(struct dinode))
+                       panic("lfs: negative bytes (segment %d)\n",
+                           datosn(fs, daddr));
+#endif
+               sup->su_nbytes -= sizeof(struct dinode);
+               LFS_UBWRITE(bp);
+       }
 }
 
 void
 }
 
 void
@@ -338,10 +423,9 @@ lfs_gather(fs, sp, vp, match)
                nbp = bp->b_blockf;
                /*
                 * XXX
                nbp = bp->b_blockf;
                /*
                 * XXX
-                * Should probably sleep on any BUSY buffer if
-                * doing an fsync?
+                * Should sleep on any BUSY buffer if doing an fsync?
                 */
                 */
-               if (bp->b_flags & B_BUSY)
+               if (bp->b_flags & B_BUSY || !match(fs, bp))
                        continue;
 #ifdef DIAGNOSTIC
                if (!(bp->b_flags & B_DELWRI))
                        continue;
 #ifdef DIAGNOSTIC
                if (!(bp->b_flags & B_DELWRI))
@@ -349,20 +433,10 @@ lfs_gather(fs, sp, vp, match)
                if (!(bp->b_flags & B_LOCKED))
                        panic("lfs_gather: bp not B_LOCKED");
 #endif
                if (!(bp->b_flags & B_LOCKED))
                        panic("lfs_gather: bp not B_LOCKED");
 #endif
-               if (!match(fs, bp))
-                       continue;
-
-               /* Insert into the buffer list, update the FINFO block. */
-               *sp->cbpp++ = bp;
-               ++fip->fi_nblocks;
-               *lbp++ = bp->b_lblkno;
-
                /*
                 * If full, finish this segment.  We may be doing I/O, so
                 * release and reacquire the splbio().
                 */
                /*
                 * If full, finish this segment.  We may be doing I/O, so
                 * release and reacquire the splbio().
                 */
-               sp->sum_bytes_left -= sizeof(daddr_t);
-               sp->seg_bytes_left -= bp->b_bufsize;
                if (sp->sum_bytes_left < sizeof(daddr_t) ||
                    sp->seg_bytes_left < fs->lfs_bsize) {
                        splx(s);
                if (sp->sum_bytes_left < sizeof(daddr_t) ||
                    sp->seg_bytes_left < fs->lfs_bsize) {
                        splx(s);
@@ -381,9 +455,20 @@ lfs_gather(fs, sp, vp, match)
                        fip->fi_ino = ip->i_number;
                        start_lbp = lbp = fip->fi_blocks;
 
                        fip->fi_ino = ip->i_number;
                        start_lbp = lbp = fip->fi_blocks;
 
+                       sp->sum_bytes_left -= 
+                           sizeof(struct finfo) - sizeof(daddr_t);
+
                        bpp = sp->cbpp;
                        s = splbio();
                }
                        bpp = sp->cbpp;
                        s = splbio();
                }
+
+               /* Insert into the buffer list, update the FINFO block. */
+               *sp->cbpp++ = bp;
+               ++fip->fi_nblocks;
+               *lbp++ = bp->b_lblkno;
+
+               sp->sum_bytes_left -= sizeof(daddr_t);
+               sp->seg_bytes_left -= bp->b_bufsize;
        }
        splx(s);
        lfs_updatemeta(fs, sp, vp, start_lbp, bpp, lbp - start_lbp);
        }
        splx(s);
        lfs_updatemeta(fs, sp, vp, start_lbp, bpp, lbp - start_lbp);
@@ -450,7 +535,6 @@ lfs_updatemeta(fs, sp, vp, lbp, bpp, nblocks)
                /* Update segment usage information. */
                if (daddr != UNASSIGNED) {
                        LFS_SEGENTRY(sup, fs, datosn(fs, daddr), bp);
                /* Update segment usage information. */
                if (daddr != UNASSIGNED) {
                        LFS_SEGENTRY(sup, fs, datosn(fs, daddr), bp);
-                       sup->su_lastmod = time.tv_sec;
 #ifdef DIAGNOSTIC
                        if (sup->su_nbytes < fs->lfs_bsize)
                                panic("lfs: negative bytes (segment %d)\n",
 #ifdef DIAGNOSTIC
                        if (sup->su_nbytes < fs->lfs_bsize)
                                panic("lfs: negative bytes (segment %d)\n",
@@ -480,6 +564,10 @@ lfs_initseg(fs, sp)
 #endif
        /* Advance to the next segment. */
        if (!LFS_PARTIAL_FITS(fs)) {
 #endif
        /* Advance to the next segment. */
        if (!LFS_PARTIAL_FITS(fs)) {
+               /* Wake up any cleaning procs waiting on this file system. */
+               wakeup(&fs->lfs_nextseg);
+               wakeup(&lfs_allclean_wakeup);
+
                lfs_newseg(fs);
                fs->lfs_offset = fs->lfs_curseg;
                sp->seg_number = datosn(fs, fs->lfs_curseg);
                lfs_newseg(fs);
                fs->lfs_offset = fs->lfs_curseg;
                sp->seg_number = datosn(fs, fs->lfs_curseg);
@@ -506,7 +594,7 @@ lfs_initseg(fs, sp)
 
        /* Get a new buffer for SEGSUM and enter it into the buffer list. */
        sp->cbpp = sp->bpp;
 
        /* Get a new buffer for SEGSUM and enter it into the buffer list. */
        sp->cbpp = sp->bpp;
-       *sp->cbpp = lfs_newbuf(fs, sp, fs->lfs_offset, LFS_SUMMARY_SIZE);
+       *sp->cbpp = lfs_newbuf(fs, fs->lfs_offset, LFS_SUMMARY_SIZE);
        sp->segsum = (*sp->cbpp)->b_un.b_addr;
        ++sp->cbpp;
        fs->lfs_offset += LFS_SUMMARY_SIZE / DEV_BSIZE;
        sp->segsum = (*sp->cbpp)->b_un.b_addr;
        ++sp->cbpp;
        fs->lfs_offset += LFS_SUMMARY_SIZE / DEV_BSIZE;
@@ -578,13 +666,14 @@ lfs_writeseg(fs, sp)
        struct lfs *fs;
        struct segment *sp;
 {
        struct lfs *fs;
        struct segment *sp;
 {
-       struct buf **bpp, *bp;
+       struct buf **bpp, *bp, *cbp;
        SEGUSE *sup;
        SEGSUM *ssp;
        dev_t i_dev;
        u_long *datap, *dp;
        SEGUSE *sup;
        SEGSUM *ssp;
        dev_t i_dev;
        u_long *datap, *dp;
-       void *pmeta;
-       int flags, i, nblocks, s, (*strategy)__P((struct buf *));
+       size_t size;
+       int ch_per_blk, i, nblocks, num, s, (*strategy)__P((struct buf *));
+       char *p;
 
 #ifdef VERBOSE
        printf("lfs_writeseg\n");
 
 #ifdef VERBOSE
        printf("lfs_writeseg\n");
@@ -592,12 +681,6 @@ lfs_writeseg(fs, sp)
        if ((nblocks = sp->cbpp - sp->bpp) == 0)
                return;
 
        if ((nblocks = sp->cbpp - sp->bpp) == 0)
                return;
 
-       /* Update the segment usage information. */
-       LFS_SEGENTRY(sup, fs, sp->seg_number, bp);
-       sup->su_nbytes += nblocks - 1 << fs->lfs_bshift;
-       sup->su_lastmod = time.tv_sec;
-       LFS_UBWRITE(bp);
-
        /*
         * Compute checksum across data and then across summary; the first
         * block (the summary block) is skipped.  Set the create time here
        /*
         * Compute checksum across data and then across summary; the first
         * block (the summary block) is skipped.  Set the create time here
@@ -616,39 +699,66 @@ lfs_writeseg(fs, sp)
            cksum(&ssp->ss_datasum, LFS_SUMMARY_SIZE - sizeof(ssp->ss_sumsum));
        free(datap, M_SEGMENT);
 
            cksum(&ssp->ss_datasum, LFS_SUMMARY_SIZE - sizeof(ssp->ss_sumsum));
        free(datap, M_SEGMENT);
 
-       /*
-        * When we gathered the blocks for I/O we did not mark them busy or
-        * remove them from the freelist.  As we do this, turn off the B_LOCKED
-        * bit so the future brelse will put them on the LRU list, and add the
-        * B_CALL flags if we're doing a checkpoint so we can count I/O's.  LFS
-        * requires that the super blocks (on checkpoint) be written after all
-        * the segment data.
-        */
        i_dev = VTOI(fs->lfs_ivnode)->i_dev;
        strategy = VTOI(fs->lfs_ivnode)->i_devvp->v_op->vop_strategy;
 
        i_dev = VTOI(fs->lfs_ivnode)->i_dev;
        strategy = VTOI(fs->lfs_ivnode)->i_devvp->v_op->vop_strategy;
 
-       s = splbio();
-       if (sp->seg_flags & SEGM_CKP) {
-               fs->lfs_iocount += nblocks;
-               flags = B_ASYNC | B_BUSY | B_CALL;
-       } else
-               flags = B_ASYNC | B_BUSY;
-       for (bpp = sp->bpp, i = nblocks; i--;) {
-               bp = *bpp++;
-               bp->b_flags |= flags;
-               bp->b_flags &=
-                   ~(B_DONE | B_ERROR | B_READ | B_DELWRI | B_LOCKED);
-               bp->b_dev = i_dev;
-               bp->b_iodone = lfs_callback;
-               if (!(bp->b_flags & B_NOCACHE)) {
-                       bremfree(bp);
-                       reassignbuf(bp, bp->b_vp);
+       /*
+        * When we simply write the blocks we lose a rotation for every block
+        * written.  To avoid this problem, we allocate memory in chunks, copy
+        * the buffers into the chunk and write the chunk.  56K was chosen as
+        * some driver/controllers can't handle unsigned 16 bit transfers.
+        * When the data is copied to the chunk, turn off the the B_LOCKED bit
+        * and brelse the buffer (which will move them to the LRU list).  Add
+        * the B_CALL flag to the buffer header so we can count I/O's for the
+        * checkpoints and so we can release the allocated memory.
+        *
+        * XXX
+        * This should be removed if the new virtual memory system allows us to
+        * easily make the buffers contiguous in kernel memory and if that's
+        * fast enough.
+        */
+#define        LFS_CHUNKSIZE   (56 * 1024)
+       ch_per_blk = LFS_CHUNKSIZE / fs->lfs_bsize;
+       for (bpp = sp->bpp, i = nblocks; i;) {
+               num = ch_per_blk;
+               if (num > i)
+                       num = i;
+               i -= num;
+               size = num * fs->lfs_bsize;
+
+               cbp = lfs_newbuf(fs, (*bpp)->b_blkno, 0);
+               cbp->b_dev = i_dev;
+               cbp->b_flags = B_ASYNC | B_BUSY | B_CALL;
+               cbp->b_iodone = lfs_callback;
+               cbp->b_saveaddr = cbp->b_un.b_addr;
+               cbp->b_un.b_addr = malloc(size, M_SEGMENT, M_WAITOK);
+
+               s = splbio();
+               ++fs->lfs_iocount;
+               for (p = cbp->b_un.b_addr; num--;) {
+                       bp = *bpp++;
+                       bcopy(bp->b_un.b_addr, p, bp->b_bcount);
+                       p += bp->b_bcount;
+                       bp->b_flags &=
+                           ~(B_DONE | B_ERROR | B_READ | B_DELWRI | B_LOCKED);
+                       if (!(bp->b_flags & B_NOCACHE)) {
+                               bremfree(bp);
+                               reassignbuf(bp, bp->b_vp);
+                       }
+                       brelse(bp);
                }
                }
+               splx(s);
+               cbp->b_bcount = p - cbp->b_un.b_addr;
+               (strategy)(cbp);
        }
        }
-       splx(s);
 
 
-       for (bpp = sp->bpp, i = nblocks; i--;)
-               (strategy)(*bpp++);
+       /* Update the segment usage information. */
+       LFS_SEGENTRY(sup, fs, sp->seg_number, bp);
+       sup->su_nbytes += nblocks - 1 - 
+           (ssp->ss_ninos + INOPB(fs) - 1) / INOPB(fs) << fs->lfs_bshift;
+       sup->su_nbytes += ssp->ss_ninos * sizeof(struct dinode);
+       sup->su_lastmod = time.tv_sec;
+       LFS_UBWRITE(bp);
 }
 
 void
 }
 
 void
@@ -668,7 +778,7 @@ lfs_writesuper(fs, sp)
 
        /* Checksum the superblock and copy it into a buffer. */
        fs->lfs_cksum = cksum(fs, sizeof(struct lfs) - sizeof(fs->lfs_cksum));
 
        /* Checksum the superblock and copy it into a buffer. */
        fs->lfs_cksum = cksum(fs, sizeof(struct lfs) - sizeof(fs->lfs_cksum));
-       bp = lfs_newbuf(fs, sp, fs->lfs_sboffs[0], LFS_SBPAD);
+       bp = lfs_newbuf(fs, fs->lfs_sboffs[0], LFS_SBPAD);
        *bp->b_un.b_lfs = *fs;
 
        /* Write the first superblock (wait). */
        *bp->b_un.b_lfs = *fs;
 
        /* Write the first superblock (wait). */
@@ -734,9 +844,8 @@ lfs_match_tindir(fs, bp)
  * Allocate a new buffer header.
  */
 struct buf *
  * Allocate a new buffer header.
  */
 struct buf *
-lfs_newbuf(fs, sp, daddr, size)
+lfs_newbuf(fs, daddr, size)
        struct lfs *fs;
        struct lfs *fs;
-       struct segment *sp;
        daddr_t daddr;
        size_t size;
 {
        daddr_t daddr;
        size_t size;
 {
@@ -753,8 +862,10 @@ lfs_newbuf(fs, sp, daddr, size)
        bp->b_blkno = daddr;
        bp->b_error = 0;
        bp->b_resid = 0;
        bp->b_blkno = daddr;
        bp->b_error = 0;
        bp->b_resid = 0;
-       allocbuf(bp, size);
+       if (size)
+               allocbuf(bp, size);
        bp->b_flags |= B_NOCACHE;
        bp->b_flags |= B_NOCACHE;
+       bp->b_saveaddr = NULL;
        binshash(bp, &bfreelist[BQ_AGE]);
        return (bp);
 }
        binshash(bp, &bfreelist[BQ_AGE]);
        return (bp);
 }
@@ -773,6 +884,10 @@ lfs_callback(bp)
        if (--fs->lfs_iocount == 0)
                wakeup(&fs->lfs_iocount);
 
        if (--fs->lfs_iocount == 0)
                wakeup(&fs->lfs_iocount);
 
+       if (bp->b_saveaddr) {
+               free(bp->b_un.b_addr, M_SEGMENT);
+               bp->b_un.b_addr = bp->b_saveaddr;
+       }
        brelse(bp);
 }
 
        brelse(bp);
 }