When compacting directories, ufs_direnter() always trusted DIRSIZ()
to supply the number of bytes to be bcopy()'d to move an entry. If d_ino == 0 however, DIRSIZ() is not guaranteed to return a sensible length, so ufs_direnter could end up corrupting a directory during compaction. In practice I believe this can only happen after fsck_ffs has fixed a previously-corrupted directory. We now deal with any mid-block unused entries specially to avoid using DIRSIZ() or bcopy() on such entries. We also ensure that the variables 'dsize' and 'spacefree' contain meaningful values at all times. Add a few comments to describe better this intricate piece of code. The special handling of mid-block unused entries makes the dirhash- specific bugfix in the previous revision (1.53) now uncecessary, so this change removes it. Reviewed by: mckusick
This commit is contained in:
parent
8638e55f32
commit
c8ef91ce6c
@ -869,26 +869,39 @@ ufs_direnter(dvp, tvp, dirp, cnp, newdirbp)
|
||||
* 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);
|
||||
|
||||
/* Read nep->d_reclen now as the bcopy() may clobber it. */
|
||||
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 && nep->d_ino)
|
||||
ufsdirhash_move(dp, nep, dp->i_offset + loc,
|
||||
if (dp->i_dirhash != NULL)
|
||||
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 +909,11 @@ ufs_direnter(dvp, tvp, dirp, cnp, newdirbp)
|
||||
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 always indeterminate.
|
||||
*
|
||||
* Update the pointer fields in the previous entry (if any),
|
||||
* copy in the new entry, and write out the block.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user