aboutsummaryrefslogtreecommitdiff
path: root/ext2fs/dir.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext2fs/dir.c')
-rw-r--r--ext2fs/dir.c1082
1 files changed, 1082 insertions, 0 deletions
diff --git a/ext2fs/dir.c b/ext2fs/dir.c
new file mode 100644
index 00000000..9d341b17
--- /dev/null
+++ b/ext2fs/dir.c
@@ -0,0 +1,1082 @@
+/* Directory management routines
+
+ Copyright (C) 1994,95,96,97,98,99 Free Software Foundation, Inc.
+
+ Converted for ext2fs by Miles Bader <miles@gnu.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include "ext2fs.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <stddef.h>
+
+/* This isn't quite right because a file system block may straddle several
+ device blocks, and so a write failure between writing two device blocks
+ may scramble things up a bit. But the linux doesn't do this. We could
+ try and make sure that we never wrote any modified directories with
+ entries that straddle device blocks (but read those that do)... */
+#define DIRBLKSIZ block_size
+
+enum slot_status
+{
+ /* This means we haven't yet found room for a new entry. */
+ LOOKING,
+
+ /* This means that the specified entry is free and should be used. */
+ TAKE,
+
+ /* This means that the specified entry has enough room at the end
+ to hold the new entry. */
+ SHRINK,
+
+ /* This means that there is enough space in the block, but not in
+ any one single entry, so they all have to be shifted to make
+ room. */
+ COMPRESS,
+
+ /* This means that the directory will have to be grown to hold the
+ entry. */
+ EXTEND,
+
+ /* For removal and rename, this means that this is the location
+ of the entry found. */
+ HERE_TIS,
+};
+
+struct dirstat
+{
+ /* Type of followp operation expected */
+ enum lookup_type type;
+
+ /* One of the statuses above */
+ enum slot_status stat;
+
+ /* Mapped address and length of directory */
+ vm_address_t mapbuf;
+ vm_size_t mapextent;
+
+ /* Index of this directory block. */
+ int idx;
+
+ /* For stat COMPRESS, this is the address (inside mapbuf)
+ of the first direct in the directory block to be compressed. */
+ /* For stat HERE_TIS, SHRINK, and TAKE, this is the entry referenced. */
+ struct ext2_dir_entry_2 *entry;
+
+ /* For stat HERE_TIS, type REMOVE, this is the address of the immediately
+ previous direct in this directory block, or zero if this is the first. */
+ struct ext2_dir_entry_2 *preventry;
+
+ /* For stat COMPRESS, this is the number of bytes needed to be copied
+ in order to undertake the compression. */
+ size_t nbytes;
+};
+
+size_t diskfs_dirstat_size = sizeof (struct dirstat);
+
+/* Initialize DS such that diskfs_drop_dirstat will ignore it. */
+void
+diskfs_null_dirstat (struct dirstat *ds)
+{
+ ds->type = LOOKUP;
+}
+
+static error_t
+dirscanblock (vm_address_t blockoff, struct node *dp, int idx,
+ const char *name, int namelen, enum lookup_type type,
+ struct dirstat *ds, ino_t *inum);
+
+
+#if 0 /* XXX unused for now */
+static const unsigned char ext2_file_type[EXT2_FT_MAX] =
+{
+ [EXT2_FT_UNKNOWN] = DT_UNKNOWN,
+ [EXT2_FT_REG_FILE] = DT_REG,
+ [EXT2_FT_DIR] = DT_DIR,
+ [EXT2_FT_CHRDEV] = DT_CHR,
+ [EXT2_FT_BLKDEV] = DT_BLK,
+ [EXT2_FT_FIFO] = DT_FIFO,
+ [EXT2_FT_SOCK] = DT_SOCK,
+ [EXT2_FT_SYMLINK] = DT_LNK,
+};
+
+static const unsigned char file_type_ext2[] =
+{
+ [DT_UNKNOWN] = EXT2_FT_UNKNOWN,
+ [DT_REG] = EXT2_FT_REG_FILE,
+ [DT_DIR] = EXT2_FT_DIR,
+ [DT_CHR] = EXT2_FT_CHRDEV,
+ [DT_BLK] = EXT2_FT_BLKDEV,
+ [DT_FIFO] = EXT2_FT_FIFO,
+ [DT_SOCK] = EXT2_FT_SOCK,
+ [DT_LNK] = EXT2_FT_SYMLINK,
+};
+#endif
+
+/* Implement the diskfs_lookup from the diskfs library. See
+ <hurd/diskfs.h> for the interface specification. */
+error_t
+diskfs_lookup_hard (struct node *dp, const char *name, enum lookup_type type,
+ struct node **npp, struct dirstat *ds, struct protid *cred)
+{
+ error_t err;
+ ino_t inum;
+ int namelen;
+ int spec_dotdot;
+ struct node *np = 0;
+ int retry_dotdot = 0;
+ vm_prot_t prot =
+ (type == LOOKUP) ? VM_PROT_READ : (VM_PROT_READ | VM_PROT_WRITE);
+ memory_object_t memobj;
+ vm_address_t buf = 0;
+ vm_size_t buflen = 0;
+ int blockaddr;
+ int idx, lastidx;
+ int looped;
+
+ if ((type == REMOVE) || (type == RENAME))
+ assert (npp);
+
+ if (npp)
+ *npp = 0;
+
+ spec_dotdot = type & SPEC_DOTDOT;
+ type &= ~SPEC_DOTDOT;
+
+ namelen = strlen (name);
+
+ if (namelen > EXT2_NAME_LEN)
+ return ENAMETOOLONG;
+
+ try_again:
+ if (ds)
+ {
+ ds->type = LOOKUP;
+ ds->mapbuf = 0;
+ ds->mapextent = 0;
+ }
+ if (buf)
+ {
+ munmap ((caddr_t) buf, buflen);
+ buf = 0;
+ }
+ if (ds && (type == CREATE || type == RENAME))
+ ds->stat = LOOKING;
+
+ /* Map in the directory contents. */
+ memobj = diskfs_get_filemap (dp, prot);
+
+ if (memobj == MACH_PORT_NULL)
+ return errno;
+
+ buf = 0;
+ /* We allow extra space in case we have to do an EXTEND. */
+ buflen = round_page (dp->dn_stat.st_size + DIRBLKSIZ);
+ err = vm_map (mach_task_self (),
+ &buf, buflen, 0, 1, memobj, 0, 0, prot, prot, 0);
+ mach_port_deallocate (mach_task_self (), memobj);
+
+ inum = 0;
+
+ if (!diskfs_check_readonly ())
+ dp->dn_set_atime = 1;
+
+ /* Start the lookup at DP->dn->dir_idx. */
+ idx = dp->dn->dir_idx;
+ if (idx * DIRBLKSIZ > dp->dn_stat.st_size)
+ idx = 0; /* just in case */
+ blockaddr = buf + idx * DIRBLKSIZ;
+ looped = (idx == 0);
+ lastidx = idx;
+ if (lastidx == 0)
+ lastidx = dp->dn_stat.st_size / DIRBLKSIZ;
+
+ while (!looped || idx < lastidx)
+ {
+ err = dirscanblock (blockaddr, dp, idx, name, namelen, type, ds, &inum);
+ if (!err)
+ {
+ dp->dn->dir_idx = idx;
+ break;
+ }
+ if (err != ENOENT)
+ {
+ munmap ((caddr_t) buf, buflen);
+ return err;
+ }
+
+ blockaddr += DIRBLKSIZ;
+ idx++;
+ if (blockaddr - buf >= dp->dn_stat.st_size && !looped)
+ {
+ /* We've gotten to the end; start back at the beginning */
+ looped = 1;
+ blockaddr = buf;
+ idx = 0;
+ }
+ }
+
+ if (!diskfs_check_readonly ())
+ dp->dn_set_atime = 1;
+ if (diskfs_synchronous)
+ diskfs_node_update (dp, 1);
+
+ /* If err is set here, it's ENOENT, and we don't want to
+ think about that as an error yet. */
+ err = 0;
+
+ if (inum && npp)
+ {
+ if (namelen != 2 || name[0] != '.' || name[1] != '.')
+ {
+ if (inum == dp->cache_id)
+ {
+ np = dp;
+ diskfs_nref (np);
+ }
+ else
+ {
+ err = diskfs_cached_lookup (inum, &np);
+ if (err)
+ goto out;
+ }
+ }
+
+ /* We are looking up .. */
+ /* Check to see if this is the root of the filesystem. */
+ else if (dp->cache_id == 2)
+ {
+ err = EAGAIN;
+ goto out;
+ }
+
+ /* We can't just do diskfs_cached_lookup, because we would then deadlock.
+ So we do this. Ick. */
+ else if (retry_dotdot)
+ {
+ /* Check to see that we got the same answer as last time. */
+ if (inum != retry_dotdot)
+ {
+ /* Drop what we *thought* was .. (but isn't any more) and
+ try *again*. */
+ diskfs_nput (np);
+ mutex_unlock (&dp->lock);
+ err = diskfs_cached_lookup (inum, &np);
+ mutex_lock (&dp->lock);
+ if (err)
+ goto out;
+ retry_dotdot = inum;
+ goto try_again;
+ }
+ /* Otherwise, we got it fine and np is already set properly. */
+ }
+ else if (!spec_dotdot)
+ {
+ /* Lock them in the proper order, and then
+ repeat the directory scan to see if this is still
+ right. */
+ mutex_unlock (&dp->lock);
+ err = diskfs_cached_lookup (inum, &np);
+ mutex_lock (&dp->lock);
+ if (err)
+ goto out;
+ retry_dotdot = inum;
+ goto try_again;
+ }
+
+ /* Here below are the spec dotdot cases. */
+ else if (type == RENAME || type == REMOVE)
+ np = ifind (inum);
+
+ else if (type == LOOKUP)
+ {
+ diskfs_nput (dp);
+ err = diskfs_cached_lookup (inum, &np);
+ if (err)
+ goto out;
+ }
+ else
+ assert (0);
+ }
+
+ if ((type == CREATE || type == RENAME) && !inum && ds && ds->stat == LOOKING)
+ {
+ /* We didn't find any room, so mark ds to extend the dir */
+ ds->type = CREATE;
+ ds->stat = EXTEND;
+ ds->idx = dp->dn_stat.st_size / DIRBLKSIZ;
+ }
+
+ /* Return to the user; if we can't, release the reference
+ (and lock) we acquired above. */
+ out:
+ /* Deallocate or save the mapping. */
+ if ((err && err != ENOENT)
+ || !ds
+ || ds->type == LOOKUP)
+ {
+ munmap ((caddr_t) buf, buflen);
+ if (ds)
+ ds->type = LOOKUP; /* set to be ignored by drop_dirstat */
+ }
+ else
+ {
+ ds->mapbuf = buf;
+ ds->mapextent = buflen;
+ }
+
+ if (np)
+ {
+ assert (npp);
+ if (err)
+ {
+ if (!spec_dotdot)
+ {
+ /* Normal case */
+ if (np == dp)
+ diskfs_nrele (np);
+ else
+ diskfs_nput (np);
+ }
+ else if (type == RENAME || type == REMOVE)
+ /* We just did ifind to get np; that allocates
+ no new references, so we don't have anything to do */
+ ;
+ else if (type == LOOKUP)
+ /* We did diskfs_cached_lookup */
+ diskfs_nput (np);
+ }
+ else
+ *npp = np;
+ }
+
+ return err ? : inum ? 0 : ENOENT;
+}
+
+/* Scan block at address BLKADDR (of node DP; block index IDX), for
+ name NAME of length NAMELEN. Args TYPE, DS are as for
+ diskfs_lookup. If found, set *INUM to the inode number, else
+ return ENOENT. */
+static error_t
+dirscanblock (vm_address_t blockaddr, struct node *dp, int idx,
+ const char *name, int namelen, enum lookup_type type,
+ struct dirstat *ds, ino_t *inum)
+{
+ int nfree = 0;
+ int needed = 0;
+ vm_address_t currentoff, prevoff;
+ struct ext2_dir_entry_2 *entry = 0;
+ int nentries = 0;
+ size_t nbytes = 0;
+ int looking = 0;
+ int countcopies = 0;
+ int consider_compress = 0;
+
+ if (ds && (ds->stat == LOOKING
+ || ds->stat == COMPRESS))
+ {
+ looking = 1;
+ countcopies = 1;
+ needed = EXT2_DIR_REC_LEN (namelen);
+ }
+
+ for (currentoff = blockaddr, prevoff = 0;
+ currentoff < blockaddr + DIRBLKSIZ;
+ prevoff = currentoff, currentoff += entry->rec_len)
+ {
+ entry = (struct ext2_dir_entry_2 *)currentoff;
+
+ if (!entry->rec_len
+ || entry->rec_len % EXT2_DIR_PAD
+ || entry->name_len > EXT2_NAME_LEN
+ || currentoff + entry->rec_len > blockaddr + DIRBLKSIZ
+ || EXT2_DIR_REC_LEN (entry->name_len) > entry->rec_len
+ || memchr (entry->name, '\0', entry->name_len))
+ {
+ ext2_warning ("bad directory entry: inode: %d offset: %d",
+ dp->cache_id,
+ currentoff - blockaddr + idx * DIRBLKSIZ);
+ return ENOENT;
+ }
+
+ if (looking || countcopies)
+ {
+ int thisfree;
+
+ /* Count how much free space this entry has in it. */
+ if (entry->inode == 0)
+ thisfree = entry->rec_len;
+ else
+ thisfree = entry->rec_len - EXT2_DIR_REC_LEN (entry->name_len);
+
+ /* If this isn't at the front of the block, then it will
+ have to be copied if we do a compression; count the
+ number of bytes there too. */
+ if (countcopies && currentoff != blockaddr)
+ nbytes += EXT2_DIR_REC_LEN (entry->name_len);
+
+ if (ds->stat == COMPRESS && nbytes > ds->nbytes)
+ /* The previously found compress is better than
+ this one, so don't bother counting any more. */
+ countcopies = 0;
+
+ if (thisfree >= needed)
+ {
+ ds->type = CREATE;
+ ds->stat = entry->inode == 0 ? TAKE : SHRINK;
+ ds->entry = entry;
+ ds->idx = idx;
+ looking = countcopies = 0;
+ }
+ else
+ {
+ nfree += thisfree;
+ if (nfree >= needed)
+ consider_compress = 1;
+ }
+ }
+
+ if (entry->inode)
+ nentries++;
+
+ if (entry->name_len == namelen
+ && entry->name[0] == name[0]
+ && entry->inode
+ && !bcmp (entry->name, name, namelen))
+ break;
+ }
+
+ if (consider_compress
+ && (ds->type == LOOKING
+ || (ds->type == COMPRESS && ds->nbytes > nbytes)))
+ {
+ ds->type = CREATE;
+ ds->stat = COMPRESS;
+ ds->entry = (struct ext2_dir_entry_2 *) blockaddr;
+ ds->idx = idx;
+ ds->nbytes = nbytes;
+ }
+
+ if (currentoff >= blockaddr + DIRBLKSIZ)
+ {
+ int i;
+ /* The name is not in this block. */
+
+ /* Because we scanned the entire block, we should write
+ down how many entries there were. */
+ if (!dp->dn->dirents)
+ {
+ dp->dn->dirents = malloc ((dp->dn_stat.st_size / DIRBLKSIZ)
+ * sizeof (int));
+ for (i = 0; i < dp->dn_stat.st_size/DIRBLKSIZ; i++)
+ dp->dn->dirents[i] = -1;
+ }
+ /* Make sure the count is correct if there is one now. */
+ assert (dp->dn->dirents[idx] == -1
+ || dp->dn->dirents[idx] == nentries);
+ dp->dn->dirents[idx] = nentries;
+
+ return ENOENT;
+ }
+
+ /* We have found the required name. */
+
+ if (ds && type == CREATE)
+ ds->type = LOOKUP; /* it's invalid now */
+ else if (ds && (type == REMOVE || type == RENAME))
+ {
+ ds->type = type;
+ ds->stat = HERE_TIS;
+ ds->entry = entry;
+ ds->idx = idx;
+ ds->preventry = (struct ext2_dir_entry_2 *) prevoff;
+ }
+
+ *inum = entry->inode;
+ return 0;
+}
+
+/* Following a lookup call for CREATE, this adds a node to a directory.
+ DP is the directory to be modified; NAME is the name to be entered;
+ NP is the node being linked in; DS is the cached information returned
+ by lookup; CRED describes the user making the call. This call may
+ only be made if the directory has been held locked continuously since
+ the preceding lookup call, and only if that call returned ENOENT. */
+error_t
+diskfs_direnter_hard (struct node *dp, const char *name, struct node *np,
+ struct dirstat *ds, struct protid *cred)
+{
+ struct ext2_dir_entry_2 *new;
+ int namelen = strlen (name);
+ int needed = EXT2_DIR_REC_LEN (namelen);
+ int oldneeded;
+ vm_address_t fromoff, tooff;
+ int totfreed;
+ error_t err;
+ off_t oldsize = 0;
+
+ assert (ds->type == CREATE);
+
+ assert (!diskfs_readonly);
+
+ dp->dn_set_mtime = 1;
+
+ /* Select a location for the new directory entry. Each branch of this
+ switch is responsible for setting NEW to point to the on-disk
+ directory entry being written, and setting NEW->rec_len appropriately. */
+
+ switch (ds->stat)
+ {
+ case TAKE:
+ /* We are supposed to consume this slot. */
+ assert (ds->entry->inode == 0 && ds->entry->rec_len >= needed);
+
+ new = ds->entry;
+ break;
+
+ case SHRINK:
+ /* We are supposed to take the extra space at the end
+ of this slot. */
+ oldneeded = EXT2_DIR_REC_LEN (ds->entry->name_len);
+ assert (ds->entry->rec_len - oldneeded >= needed);
+
+ new = (struct ext2_dir_entry_2 *) ((vm_address_t) ds->entry + oldneeded);
+
+ new->rec_len = ds->entry->rec_len - oldneeded;
+ ds->entry->rec_len = oldneeded;
+ break;
+
+ case COMPRESS:
+ /* We are supposed to move all the entries to the
+ front of the block, giving each the minimum
+ necessary room. This should free up enough space
+ for the new entry. */
+ fromoff = tooff = (vm_address_t) ds->entry;
+
+ while (fromoff < (vm_address_t) ds->entry + DIRBLKSIZ)
+ {
+ struct ext2_dir_entry_2 *from = (struct ext2_dir_entry_2 *)fromoff;
+ struct ext2_dir_entry_2 *to = (struct ext2_dir_entry_2 *) tooff;
+ int fromreclen = from->rec_len;
+
+ if (from->inode != 0)
+ {
+ assert (fromoff >= tooff);
+
+ memmove (to, from, fromreclen);
+ to->rec_len = EXT2_DIR_REC_LEN (to->name_len);
+
+ tooff += to->rec_len;
+ }
+ fromoff += fromreclen;
+ }
+
+ totfreed = (vm_address_t) ds->entry + DIRBLKSIZ - tooff;
+ assert (totfreed >= needed);
+
+ new = (struct ext2_dir_entry_2 *) tooff;
+ new->rec_len = totfreed;
+ break;
+
+ case EXTEND:
+ /* Extend the file. */
+ assert (needed <= DIRBLKSIZ);
+
+ oldsize = dp->dn_stat.st_size;
+ while (oldsize + DIRBLKSIZ > dp->allocsize)
+ {
+ err = diskfs_grow (dp, oldsize + DIRBLKSIZ, cred);
+ if (err)
+ {
+ munmap ((caddr_t) ds->mapbuf, ds->mapextent);
+ return err;
+ }
+ }
+
+ new = (struct ext2_dir_entry_2 *) (ds->mapbuf + oldsize);
+
+ dp->dn_stat.st_size = oldsize + DIRBLKSIZ;
+ dp->dn_set_ctime = 1;
+
+ new->rec_len = DIRBLKSIZ;
+ break;
+
+ default:
+ new = 0;
+ assert (! "impossible: bogus status field in dirstat");
+ }
+
+ /* NEW points to the directory entry being written, and its
+ rec_len field is already filled in. Now fill in the rest. */
+
+ new->inode = np->cache_id;
+#if 0
+ /* XXX We cannot enable this code because file types can change
+ (and conceivably quite often) with translator settings.
+ There is no way for the translator that determines the type of
+ the virtual node to cause all the directory entries linked to
+ its underlying inode to reflect the proper type. */
+ new->file_type = (EXT2_HAS_INCOMPAT_FEATURE (sblock,
+ EXT2_FEATURE_INCOMPAT_FILETYPE)
+ ? file_type_ext2[IFTODT (np->dn_stat.st_mode & S_IFMT)]
+ : 0);
+#else
+ new->file_type = 0;
+#endif
+ new->name_len = namelen;
+ memcpy (new->name, name, namelen);
+
+ /* Mark the directory inode has having been written. */
+ dp->dn->info.i_flags &= ~EXT2_BTREE_FL;
+ dp->dn_set_mtime = 1;
+
+ munmap ((caddr_t) ds->mapbuf, ds->mapextent);
+
+ if (ds->stat != EXTEND)
+ {
+ /* If we are keeping count of this block, then keep the count up
+ to date. */
+ if (dp->dn->dirents && dp->dn->dirents[ds->idx] != -1)
+ dp->dn->dirents[ds->idx]++;
+ }
+ else
+ {
+ int i;
+ /* It's cheap, so start a count here even if we aren't counting
+ anything at all. */
+ if (dp->dn->dirents)
+ {
+ dp->dn->dirents = realloc (dp->dn->dirents,
+ (dp->dn_stat.st_size / DIRBLKSIZ
+ * sizeof (int)));
+ for (i = oldsize / DIRBLKSIZ;
+ i < dp->dn_stat.st_size / DIRBLKSIZ;
+ i++)
+ dp->dn->dirents[i] = -1;
+
+ dp->dn->dirents[ds->idx] = 1;
+ }
+ else
+ {
+ dp->dn->dirents = malloc (dp->dn_stat.st_size / DIRBLKSIZ
+ * sizeof (int));
+ for (i = 0; i < dp->dn_stat.st_size / DIRBLKSIZ; i++)
+ dp->dn->dirents[i] = -1;
+ dp->dn->dirents[ds->idx] = 1;
+ }
+ }
+
+ diskfs_file_update (dp, 1);
+
+ return 0;
+}
+
+/* Following a lookup call for REMOVE, this removes the link from the
+ directory. DP is the directory being changed and DS is the cached
+ information returned from lookup. This call is only valid if the
+ directory has been locked continously since the call to lookup, and
+ only if that call succeeded. */
+error_t
+diskfs_dirremove_hard (struct node *dp, struct dirstat *ds)
+{
+ assert (ds->type == REMOVE);
+ assert (ds->stat == HERE_TIS);
+
+ assert (!diskfs_readonly);
+
+ dp->dn_set_mtime = 1;
+
+ if (ds->preventry == 0)
+ ds->entry->inode = 0;
+ else
+ {
+ assert ((vm_address_t) ds->entry - (vm_address_t) ds->preventry
+ == ds->preventry->rec_len);
+ ds->preventry->rec_len += ds->entry->rec_len;
+ }
+
+ dp->dn_set_mtime = 1;
+ dp->dn->info.i_flags &= ~EXT2_BTREE_FL;
+
+ munmap ((caddr_t) ds->mapbuf, ds->mapextent);
+
+ /* If we are keeping count of this block, then keep the count up
+ to date. */
+ if (dp->dn->dirents && dp->dn->dirents[ds->idx] != -1)
+ dp->dn->dirents[ds->idx]--;
+
+ diskfs_file_update (dp, 1);
+
+ return 0;
+}
+
+
+/* Following a lookup call for RENAME, this changes the inode number
+ on a directory entry. DP is the directory being changed; NP is
+ the new node being linked in; DP is the cached information returned
+ by lookup. This call is only valid if the directory has been locked
+ continuously since the call to lookup, and only if that call
+ succeeded. */
+error_t
+diskfs_dirrewrite_hard (struct node *dp, struct node *np, struct dirstat *ds)
+{
+ assert (ds->type == RENAME);
+ assert (ds->stat == HERE_TIS);
+
+ assert (!diskfs_readonly);
+
+ ds->entry->inode = np->cache_id;
+ dp->dn_set_mtime = 1;
+ dp->dn->info.i_flags &= ~EXT2_BTREE_FL;
+
+ munmap ((caddr_t) ds->mapbuf, ds->mapextent);
+
+ diskfs_file_update (dp, 1);
+
+ return 0;
+}
+
+/* Tell if DP is an empty directory (has only "." and ".." entries).
+ This routine must be called from inside a catch_exception (). */
+int
+diskfs_dirempty (struct node *dp, struct protid *cred)
+{
+ error_t err;
+ vm_address_t buf = 0, curoff;
+ struct ext2_dir_entry_2 *entry;
+ int hit = 0; /* Found something in the directory. */
+ memory_object_t memobj = diskfs_get_filemap (dp, VM_PROT_READ);
+
+ if (memobj == MACH_PORT_NULL)
+ /* XXX should reflect error properly. */
+ return 0;
+
+ err = vm_map (mach_task_self (), &buf, dp->dn_stat.st_size, 0,
+ 1, memobj, 0, 0, VM_PROT_READ, VM_PROT_READ, 0);
+ mach_port_deallocate (mach_task_self (), memobj);
+ assert (!err);
+
+ if (! diskfs_check_readonly ())
+ dp->dn_set_atime = 1;
+
+ for (curoff = buf;
+ !hit && curoff < buf + dp->dn_stat.st_size;
+ curoff += entry->rec_len)
+ {
+ entry = (struct ext2_dir_entry_2 *) curoff;
+
+ if (entry->inode != 0
+ && (entry->name_len > 2
+ || entry->name[0] != '.'
+ || (entry->name[1] != '.'
+ && entry->name[1] != '\0')))
+ hit = 1;
+ }
+
+ if (! diskfs_check_readonly ())
+ dp->dn_set_atime = 1;
+ if (diskfs_synchronous)
+ diskfs_node_update (dp, 1);
+
+ munmap ((caddr_t) buf, dp->dn_stat.st_size);
+
+ return !hit;
+}
+
+/* Make DS an invalid dirstat. */
+error_t
+diskfs_drop_dirstat (struct node *dp, struct dirstat *ds)
+{
+ if (ds->type != LOOKUP)
+ {
+ assert (ds->mapbuf);
+ munmap ((caddr_t) ds->mapbuf, ds->mapextent);
+ ds->type = LOOKUP;
+ }
+ return 0;
+}
+
+
+/* Count the entries in directory block NB for directory DP and
+ write the answer down in its dirents array. As a side affect
+ fill BUF with the block. */
+static error_t
+count_dirents (struct node *dp, int nb, char *buf)
+{
+ int amt;
+ char *offinblk;
+ struct ext2_dir_entry_2 *entry;
+ int count = 0;
+ error_t err;
+
+ assert (dp->dn->dirents);
+ assert ((nb + 1) * DIRBLKSIZ <= dp->dn_stat.st_size);
+
+ err = diskfs_node_rdwr (dp, buf, nb * DIRBLKSIZ, DIRBLKSIZ, 0, 0, &amt);
+ if (err)
+ return err;
+ assert (amt == DIRBLKSIZ);
+
+ for (offinblk = buf;
+ offinblk < buf + DIRBLKSIZ;
+ offinblk += entry->rec_len)
+ {
+ entry = (struct ext2_dir_entry_2 *) offinblk;
+ if (entry->inode)
+ count++;
+ }
+
+ assert (dp->dn->dirents[nb] == -1 || dp->dn->dirents[nb] == count);
+ dp->dn->dirents[nb] = count;
+ return 0;
+}
+
+/* Returned directory entries are aligned to blocks this many bytes long.
+ Must be a power of two. */
+#define DIRENT_ALIGN 4
+
+/* Implement the disikfs_get_directs callback as described in
+ <hurd/diskfs.h>. */
+error_t
+diskfs_get_directs (struct node *dp,
+ int entry,
+ int nentries,
+ char **data,
+ u_int *datacnt,
+ vm_size_t bufsiz,
+ int *amt)
+{
+ int blkno;
+ int nblks;
+ int curentry;
+ char buf[DIRBLKSIZ];
+ char *bufp;
+ int bufvalid;
+ error_t err;
+ int i;
+ char *datap;
+ struct ext2_dir_entry_2 *entryp;
+ int allocsize;
+ int checklen;
+ struct dirent *userp;
+
+ nblks = dp->dn_stat.st_size/DIRBLKSIZ;
+
+ if (!dp->dn->dirents)
+ {
+ dp->dn->dirents = malloc (nblks * sizeof (int));
+ for (i = 0; i < nblks; i++)
+ dp->dn->dirents[i] = -1;
+ }
+
+ /* Allocate enough space to hold the maximum we might return */
+ if (!bufsiz || bufsiz > dp->dn_stat.st_size)
+ /* Allocate enough to return the entire directory. Since ext2's
+ directory format is different than the format used to return the
+ entries, we allocate enough to hold the on disk directory plus
+ whatever extra would be necessary in the worst-case. */
+ {
+ /* The minimum size of an ext2fs directory entry. */
+ size_t min_entry_size = EXT2_DIR_REC_LEN (0);
+ /* The minimum size of a returned dirent entry. The +1 is for '\0'. */
+ size_t min_dirent_size = offsetof (struct dirent, d_name) + 1;
+ /* The maximum possible number of ext2fs dir entries in this dir. */
+ size_t max_entries = dp->dn_stat.st_size / min_entry_size;
+ /* The maximum difference in size per directory entry. */
+ size_t entry_extra =
+ DIRENT_ALIGN
+ + (min_dirent_size > min_entry_size
+ ? min_dirent_size - min_entry_size : 0);
+
+ allocsize = round_page (dp->dn_stat.st_size + max_entries * entry_extra);
+ }
+ else
+ allocsize = round_page (bufsiz);
+
+ if (allocsize > *datacnt)
+ *data = mmap (0, allocsize, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0);
+
+ /* Scan through the entries to find ENTRY. If we encounter
+ a -1 in the process then stop to fill it. When we run
+ off the end, ENTRY is too big. */
+ curentry = 0;
+ bufvalid = 0;
+ for (blkno = 0; blkno < nblks; blkno++)
+ {
+ if (dp->dn->dirents[blkno] == -1)
+ {
+ err = count_dirents (dp, blkno, buf);
+ if (err)
+ return err;
+ bufvalid = 1;
+ }
+
+ if (curentry + dp->dn->dirents[blkno] > entry)
+ /* ENTRY starts in this block. */
+ break;
+
+ curentry += dp->dn->dirents[blkno];
+
+ bufvalid = 0;
+ }
+
+ if (blkno == nblks)
+ {
+ *datacnt = 0;
+ *amt = 0;
+ return 0;
+ }
+
+ /* Set bufp appropriately */
+ bufp = buf;
+ if (curentry != entry)
+ {
+ /* Look through the block to find out where to start,
+ setting bufp appropriately. */
+ if (!bufvalid)
+ {
+ err = diskfs_node_rdwr (dp, buf, blkno * DIRBLKSIZ, DIRBLKSIZ,
+ 0, 0, &checklen);
+ if (err)
+ return err;
+ assert (checklen == DIRBLKSIZ);
+ bufvalid = 1;
+ }
+ for (i = 0, bufp = buf;
+ i < entry - curentry && bufp - buf < DIRBLKSIZ;
+ bufp += ((struct ext2_dir_entry_2 *)bufp)->rec_len, i++)
+ ;
+ /* Make sure we didn't run off the end. */
+ assert (bufp - buf < DIRBLKSIZ);
+ }
+
+ i = 0;
+ datap = *data;
+
+ /* Copy the entries, one at a time. */
+ while (((nentries == -1) || (i < nentries))
+ && (!bufsiz || (datap - *data < bufsiz) )
+ && blkno < nblks)
+ {
+ if (!bufvalid)
+ {
+ err = diskfs_node_rdwr (dp, buf, blkno * DIRBLKSIZ, DIRBLKSIZ,
+ 0, 0, &checklen);
+ if (err)
+ return err;
+ assert (checklen == DIRBLKSIZ);
+ bufvalid = 1;
+ bufp = buf;
+ }
+
+ entryp = (struct ext2_dir_entry_2 *)bufp;
+
+ if (entryp->inode)
+ {
+ int rec_len;
+ int name_len = entryp->name_len;
+
+ userp = (struct dirent *) datap;
+
+ /* Length is structure before the name + the name + '\0', all
+ padded to a four-byte alignment. */
+ rec_len =
+ ((offsetof (struct dirent, d_name)
+ + name_len + 1
+ + (DIRENT_ALIGN - 1))
+ & ~(DIRENT_ALIGN - 1));
+
+ /* See if this record would run over the end of the return buffer. */
+ if (bufsiz == 0)
+ /* It shouldn't ever, as we calculated the worst case size. */
+ assert (datap + rec_len <= *data + allocsize);
+ else
+ /* It's ok if it does, just leave off returning this entry. */
+ if (datap + rec_len > *data + allocsize)
+ break;
+
+ userp->d_fileno = entryp->inode;
+ userp->d_reclen = rec_len;
+ userp->d_namlen = name_len;
+
+#if 0
+ /* We don't bother to check the EXT2_FEATURE_INCOMPAT_FILETYPE
+ flag in the superblock, because in old filesystems the
+ file_type field is the high byte of the length field and is
+ always zero because names cannot be that long. */
+ if (entryp->file_type < EXT2_FT_MAX)
+ userp->d_type = ext2_file_type[entryp->file_type];
+ else
+ {
+ ext2_warning ("bad type %d in directory entry: "
+ "inode: %d offset: %d",
+ entryp->file_type,
+ dp->cache_id,
+ blkno * DIRBLKSIZ + bufp - buf);
+ userp->d_type = DT_UNKNOWN;
+ }
+#else
+ /* XXX
+ For complex reasons it might not be correct to return
+ the filesystem's d_type value to the user. */
+ userp->d_type = DT_UNKNOWN;
+#endif
+ memcpy (userp->d_name, entryp->name, name_len);
+ userp->d_name[name_len] = '\0';
+
+ datap += rec_len;
+ i++;
+ }
+
+ if (entryp->rec_len == 0)
+ {
+ ext2_warning ("zero length directory entry: inode: %d offset: %d",
+ dp->cache_id,
+ blkno * DIRBLKSIZ + bufp - buf);
+ return EIO;
+ }
+
+ bufp += entryp->rec_len;
+ if (bufp - buf == DIRBLKSIZ)
+ {
+ blkno++;
+ bufvalid = 0;
+ }
+ else if (bufp - buf > DIRBLKSIZ)
+ {
+ ext2_warning ("directory entry too long: inode: %d offset: %d",
+ dp->cache_id,
+ blkno * DIRBLKSIZ + bufp - buf - entryp->rec_len);
+ return EIO;
+ }
+ }
+
+ /* We've copied all we can. If we allocated our own array
+ but didn't fill all of it, then free whatever memory we didn't use. */
+ if (allocsize > *datacnt)
+ {
+ if (round_page (datap - *data) < allocsize)
+ munmap ((caddr_t) (*data + round_page (datap - *data)),
+ allocsize - round_page (datap - *data));
+ }
+
+ /* Set variables for return */
+ *datacnt = datap - *data;
+ *amt = i;
+ return 0;
+}