In message <[EMAIL PROTECTED]>, Kirk McKusick writ
es:
>FFS will never set a directory ino == 0 at a location other
>than the first entry in a directory, but fsck will do so to
>get rid of an unwanted entry. The readdir routines know to
>skip over an ino == 0 entry no matter where in the directory
>it is found, so applications will never see such entries.
>It would be a fair amount of work to change fsck to `do the
>right thing', as the checking code is given only the current
>entry with which to work. I am of the opinion that you
>should simply accept that mid-directory block ino == 0 is
>acceptable rather than trying to `fix' the problem.

Bleh, well I guess not too surprisingly, there is a case in
ufs_direnter() (ufs_lookup.c) where the kernel does the wrong thing
when a mid-block entry has d_ino == 0. The result can be serious
directory corruption, and the bug has been there since the Lite/2
merge:

        # fetch http://www.maths.tcd.ie/~iedowse/FreeBSD/dirbug_img.gz
        Receiving dirbug_img.gz (6745 bytes): 100%
        6745 bytes transferred in 0.0 seconds (4.69 MBps)
        # gunzip dirbug_img.gz
        # mdconfig -a -t vnode -f dirbug_img
        md0
        # fsck_ffs /dev/md0
        ** /dev/md0
        ** Last Mounted on /mnt
        ** Phase 1 - Check Blocks and Sizes
        ** Phase 2 - Check Pathnames
        ** Phase 3 - Check Connectivity
        ** Phase 4 - Check Reference Counts
        ** Phase 5 - Check Cyl groups
        20 files, 1 used, 2638 free (14 frags, 328 blocks, 0.5% fragmentation)
        # mount /dev/md0 /mnt
        # touch /mnt/ffffffffff12
        # umount /mnt
        # fsck_ffs /dev/md0
        ** /dev/md0
        ** Last Mounted on /mnt
        ** Phase 1 - Check Blocks and Sizes
        ** Phase 2 - Check Pathnames
        DIRECTORY CORRUPTED  I=2  OWNER=root MODE=40755
        SIZE=512 MTIME=Aug 21 22:28 2001 
        DIR=/

        SALVAGE? [yn]

The bug is that when compressing directory blocks, the code trusts
the DIRSIZ() macro to calculate the amount of data to be bcopy'd
when moving a directory entry. If d_ino is zero, DIRSIZ() cannot
be trusted, so random bytes in unused portions of the directory
determine how much gets copied. I think it is very unlikely in
practice for the value returned by DIRSIZ() to be harmful, but fsck
certainly doesn't check it so this bug can be triggered after other
types of corruption have been repaired by fsck.

I just found this while looking for a dirhash bug - the dirhash
code didn't check for d_ino == 0 when compressing directories,
so it would freak when it couldn't find the entry to move. The
patch below should fix both these issues, and it makes it clearer
that DIRSIZ() is not used when d_ino == 0.

Any comments welcome. The patch is a bit larger than it needs to
be, but that directory compression code is so hard to understand
that I think it is worth clarifying it slightly :-)

Ian


Index: ufs_lookup.c
===================================================================
RCS file: /FreeBSD/FreeBSD-CVS/src/sys/ufs/ufs/ufs_lookup.c,v
retrieving revision 1.52
diff -u -r1.52 ufs_lookup.c
--- ufs_lookup.c        2001/08/18 03:08:48     1.52
+++ ufs_lookup.c        2001/08/21 23:59:09
@@ -869,26 +869,38 @@
         * dp->i_offset + dp->i_count would yield the space.
         */
        ep = (struct direct *)dirbuf;
-       dsize = DIRSIZ(OFSFMT(dvp), ep);
+       dsize = ep->d_ino ? DIRSIZ(OFSFMT(dvp), ep) : 0;
        spacefree = ep->d_reclen - dsize;
        for (loc = ep->d_reclen; loc < dp->i_count; ) {
                nep = (struct direct *)(dirbuf + loc);
-               if (ep->d_ino) {
-                       /* trim the existing slot */
-                       ep->d_reclen = dsize;
-                       ep = (struct direct *)((char *)ep + dsize);
-               } else {
-                       /* overwrite; nothing there; header is ours */
-                       spacefree += dsize;
+
+               /* Trim the existing slot (NB: dsize may be zero). */
+               ep->d_reclen = dsize;
+               ep = (struct direct *)((char *)ep + dsize);
+
+               loc += nep->d_reclen;
+               if (nep->d_ino == 0) {
+                       /*
+                        * A mid-block unused entry. Such entries are
+                        * never created by the kernel, but fsck_ffs
+                        * can create them (and it doesn't fix them).
+                        *
+                        * Add up the free space, and initialise the
+                        * relocated entry since we don't bcopy it.
+                        */
+                       spacefree += nep->d_reclen;
+                       ep->d_ino = 0;
+                       dsize = 0;
+                       continue;
                }
                dsize = DIRSIZ(OFSFMT(dvp), nep);
                spacefree += nep->d_reclen - dsize;
 #ifdef UFS_DIRHASH
                if (dp->i_dirhash != NULL)
-                       ufsdirhash_move(dp, nep, dp->i_offset + loc,
+                       ufsdirhash_move(dp, nep,
+                           dp->i_offset + ((char *)nep - dirbuf),
                            dp->i_offset + ((char *)ep - dirbuf));
 #endif
-               loc += nep->d_reclen;
                if (DOINGSOFTDEP(dvp))
                        softdep_change_directoryentry_offset(dp, dirbuf,
                            (caddr_t)nep, (caddr_t)ep, dsize); 
@@ -896,6 +908,11 @@
                        bcopy((caddr_t)nep, (caddr_t)ep, dsize);
        }
        /*
+        * Here, `ep' points to a directory entry containing `dsize' in-use
+        * bytes followed by `spacefree' unused bytes. If ep->d_ino == 0,
+        * then the entry is completely unused (dsize == 0). The value
+        * of ep->d_reclen is indeterminate.
+        *
         * Update the pointer fields in the previous entry (if any),
         * copy in the new entry, and write out the block.
         */

To Unsubscribe: send mail to [EMAIL PROTECTED]
with "unsubscribe freebsd-current" in the body of the message

Reply via email to