diff options
Diffstat (limited to 'fatfs/dir.c')
-rw-r--r-- | fatfs/dir.c | 952 |
1 files changed, 952 insertions, 0 deletions
diff --git a/fatfs/dir.c b/fatfs/dir.c new file mode 100644 index 00000000..9ef76c49 --- /dev/null +++ b/fatfs/dir.c @@ -0,0 +1,952 @@ +/* main.c - FAT filesystem. + Copyright (C) 1997, 1998, 1999, 2002 Free Software Foundation, Inc. + Written by Thomas Bushnell, n/BSG and Marcus Brinkmann. + + This file is part of the GNU Hurd. + + The GNU Hurd 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. + + The GNU Hurd 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */ + +#include <ctype.h> +#include <string.h> +#include <dirent.h> +#include "fatfs.h" + +/* The size of a directory block is usually just the cluster size. + However, the root directory of FAT12/16 file systems is stored in + sectors in a special region, so we settle on the greatest common + divisor here. */ +#define DIRBLKSIZ bytes_per_sector +#define LOG2_DIRBLKSIZ log2_bytes_per_sector + +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 dirrect *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 dirrect *preventry; + + /* For stat COMPRESS, this is the number of bytes needed to be copied + in order to undertake the compression. */ + size_t nbytes; +}; + +const 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; +} + +/* Forward declaration. */ +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); + +static int +fatnamematch (const char *dirname, const char *username, size_t unamelen) +{ + char *dn = strdup(dirname); + int dpos = 0; + int upos = 0; + int ext = 0; + + /* Deleted files. */ + if (dn[0] == FAT_DIR_NAME_DELETED || dn[0] == FAT_DIR_NAME_LAST) + return 0; + if (dn[0] == FAT_DIR_NAME_REPLACE_DELETED) + dn[0] = FAT_DIR_NAME_DELETED; + + /* Special representations for `.' and `..'. */ + if (!memcmp(dn, FAT_DIR_NAME_DOT, 11)) + return unamelen == 1 && username[0] == '.'; + + if (!memcmp (dn, FAT_DIR_NAME_DOTDOT, 11)) + return unamelen == 2 && username[0] == '.' && username[1] == '.'; + + if (unamelen > 12) + return 0; + + do + { + /* First check if we have reached the extension without coming + across blanks. */ + if (dpos == 8 && !ext) + { + if (username[upos] == '.') + { + upos++; + ext = 1; + } + else + break; + } + /* Second, skip blanks in base part. */ + if (dn[dpos] == ' ') + { + if (ext) + break; + while (dpos < 8 && dn[++dpos] == ' '); + if (username[upos] == '.') + upos++; + ext = 1; + } + else + { + if (tolower(dn[dpos]) == tolower(username[upos])) + { + dpos++; + upos++; + } + else + break; + } + } while (upos < unamelen && dpos < 11); + while (dpos < 11 && dn[dpos] == ' ') + dpos++; + return (upos == unamelen && dpos == 11); +} + +/* Implement the diskfs_lookup callback 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 > FAT_NAME_MAX) + 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 << LOG2_DIRBLKSIZ > dp->dn_stat.st_size) + idx = 0; /* just in case */ + blockaddr = buf + (idx << LOG2_DIRBLKSIZ); + looped = (idx == 0); + lastidx = idx; + if (lastidx == 0) + lastidx = dp->dn_stat.st_size >> LOG2_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_in_dirbuf (inum, &np, buf); + if (err) + goto out; + } + } + + /* We are looking up "..". */ + /* Check to see if this is the root of the filesystem. */ + else if (dp == diskfs_root_node) + { + 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_in_dirbuf (inum, &np, buf); + 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_in_dirbuf (inum, &np, buf); + 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_in_dirbuf (inum, &np, buf); + 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 >> LOG2_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 = 0; + struct dirrect *entry = 0; + size_t nbytes = 0; + int looking = 0; + int countcopies = 0; + int consider_compress = 0; + inode_t inode; + vi_key_t entry_key = vi_zero_key; + + /* FAT lacks the "." and ".." directory record in the root directory, + so we emulate them here. */ + if (idx == 0 && dp == diskfs_root_node + && (fatnamematch (FAT_DIR_NAME_DOT, name, namelen) + || fatnamematch (FAT_DIR_NAME_DOTDOT, name, namelen))) + { + entry_key.dir_inode = diskfs_root_node->cache_id; + currentoff = blockaddr; + } + else + { + if (ds && (ds->stat == LOOKING + || ds->stat == COMPRESS)) + { + looking = 1; + countcopies = 1; + needed = FAT_DIR_RECORDS (namelen); + } + + for (currentoff = blockaddr, prevoff = 0; + currentoff < blockaddr + DIRBLKSIZ; + prevoff = currentoff, currentoff += FAT_DIR_REC_LEN) + { + entry = (struct dirrect *)currentoff; + + if (looking || countcopies) + { + int thisfree; + + /* Count how much free space this entry has in it. */ + if ((char) entry->name[0] == FAT_DIR_NAME_LAST || + (char) entry->name[0] == FAT_DIR_NAME_DELETED) + thisfree = FAT_DIR_REC_LEN; + else + thisfree = 0; + + /* 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 += FAT_DIR_REC_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 = TAKE; + ds->entry = entry; + ds->idx = idx; + looking = countcopies = 0; + } + else + { + nfree += thisfree; + if (nfree >= needed) + consider_compress = 1; + } + } + + if (entry->attribute & FAT_DIR_ATTR_LABEL) + /* Either the volume label in root dir or a long filename + component. */ + continue; + + if (fatnamematch (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 dirrect *) blockaddr; + ds->idx = idx; + ds->nbytes = nbytes; + } + } + + if (currentoff >= blockaddr + DIRBLKSIZ) + { + /* The name is not in this block. */ + + 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 dirrect *) prevoff; + } + + if (entry_key.dir_inode) + { + /* The required name is "." or ".." in the root dir. */ + *inum = entry_key.dir_inode; + } + else if ((entry->attribute & FAT_DIR_ATTR_DIR) + && !memcmp (entry->name, FAT_DIR_NAME_DOT, 11)) + { + /* "." and ".." have to be treated special. We don't want their + directory records, but the records of the directories they + point to. */ + + *inum = dp->cache_id; + } + else if ((entry->attribute & FAT_DIR_ATTR_DIR) + && !memcmp (entry->name, FAT_DIR_NAME_DOTDOT, 11)) + { + if (entry->first_cluster_low[0] == 0 + && entry->first_cluster_low[1] == 0 + && entry->first_cluster_high[0] == 0 + && entry->first_cluster_high[1] == 0) + { + *inum = diskfs_root_node->cache_id; + } + else + { + struct vi_key vk = vi_key (dp->dn->inode); + *inum = vk.dir_inode; + } + } + else + { + entry_key.dir_inode = dp->cache_id; + entry_key.dir_offset = (currentoff - blockaddr) + (idx << LOG2_DIRBLKSIZ); + return vi_rlookup(entry_key, inum, &inode, 1); + } + 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 dirrect *new; + int namelen = strlen (name); + int needed = FAT_DIR_RECORDS (namelen); + error_t err; + loff_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. */ + + switch (ds->stat) + { + case TAKE: + /* We are supposed to consume this slot. */ + assert ((char)ds->entry->name[0] == FAT_DIR_NAME_LAST + || (char)ds->entry->name[0] == FAT_DIR_NAME_DELETED); + + new = ds->entry; + break; + + case EXTEND: + /* Extend the file. */ + assert (needed <= bytes_per_cluster); + + oldsize = dp->dn_stat.st_size; + while (oldsize + bytes_per_cluster > dp->allocsize) + { + err = diskfs_grow (dp, oldsize + bytes_per_cluster, cred); + if (err) + { + munmap ((caddr_t) ds->mapbuf, ds->mapextent); + return err; + } + } + + new = (struct dirrect *) ((char *) ds->mapbuf + oldsize); + + dp->dn_stat.st_size = oldsize + bytes_per_cluster; + dp->dn_set_ctime = 1; + + break; + + case SHRINK: + case COMPRESS: + default: + assert(0); + + /* COMPRESS will be used later, with long filenames, but shrink + does not make sense on fat, as all entries have fixed + size. */ + } + + /* NEW points to the directory entry being written. Now fill in the + data. */ + + memcpy (new->name, " ", 11); + memcpy (new->name, name, namelen % 11); /* XXX */ + + /* XXX We need to do much, much more here. */ + /* XXX What about creating . and .. for dirs? */ + + /* Mark the directory inode has having been written. */ + dp->dn_set_mtime = 1; + + munmap ((caddr_t) ds->mapbuf, ds->mapextent); + + 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; + + ds->entry->name[0] = FAT_DIR_NAME_DELETED; + + /* XXX Do something with dirrect? inode? */ + + dp->dn_set_mtime = 1; + + munmap ((caddr_t) ds->mapbuf, ds->mapextent); + + 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); + + /* XXX We have to reimplement rename completely. */ + /* + ds->entry->inode = np->cache_id; + */ + dp->dn_set_mtime = 1; + + 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 dirrect *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 += FAT_DIR_REC_LEN) + { + entry = (struct dirrect *) curoff; + + if (entry->name[0] == FAT_DIR_NAME_LAST) + break; + if (!entry->name[0] == FAT_DIR_NAME_DELETED + && memcmp (entry->name, FAT_DIR_NAME_DOT, 11) + && memcmp (entry->name, FAT_DIR_NAME_DOTDOT, 11)) + 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; +} + + +/* Implement the diskfs_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) +{ + volatile vm_size_t allocsize; + struct dirrect *ep; + struct dirent *userp; + int i; + char *datap; + volatile int ouralloc = 0; + error_t err; + vm_prot_t prot = VM_PROT_READ; + memory_object_t memobj; + vm_address_t buf = 0, bufp; + vm_size_t buflen = 0; + + /* Allocate some space to hold the returned data. */ + allocsize = bufsiz ? round_page (bufsiz) : vm_page_size * 4; + if (allocsize > *datacnt) + { + *data = mmap (0, allocsize, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0); + ouralloc = 1; + } + + /* Map in the directory contents. */ + memobj = diskfs_get_filemap (dp, prot); + + if (memobj == MACH_PORT_NULL) + return errno; + + /* We allow extra space in case we have to do an EXTEND. */ + buflen = round_page (dp->dn_stat.st_size); + err = vm_map (mach_task_self (), + &buf, buflen, 0, 1, memobj, 0, 0, prot, prot, 0); + mach_port_deallocate (mach_task_self (), memobj); + + bufp = buf; + for (i = 0; i < entry; i ++) + { + /* The root directory in FAT file systems doesn't contain + entries for DOT and DOTDOT, they are special cased below. */ + if (dp == diskfs_root_node && i < 2) + continue; + + ep = (struct dirrect *) bufp; + + if (bufp >= buf + buflen || (char)ep->name[0] == FAT_DIR_NAME_LAST) + { + /* Not that many entries in the directory; return nothing. */ + if (allocsize > *datacnt) + munmap (data, allocsize); + munmap ((caddr_t) buf, buflen); + *datacnt = 0; + *amt = 0; + return 0; + } + + /* Ignore and skip deleted and label entries (catches also long + filenames). */ + if ((char)ep->name[0] == FAT_DIR_NAME_DELETED + || (ep->attribute & FAT_DIR_ATTR_LABEL)) + i--; + bufp = bufp + FAT_DIR_REC_LEN; + } + + /* Now copy entries one at a time. */ + i = 0; + datap = *data; + while (((nentries == -1) || (i < nentries)) + && (!bufsiz || datap - *data < bufsiz) + && bufp < buf + buflen) + { + char name[13]; + size_t namlen, reclen; + struct dirrect dot = { FAT_DIR_NAME_DOT, FAT_DIR_ATTR_DIR }; + struct dirrect dotdot = { FAT_DIR_NAME_DOTDOT, FAT_DIR_ATTR_DIR }; + + /* The root directory in FAT file systems doesn't contain + entries for DOT and DOTDOT, they are special cased below. */ + if (dp == diskfs_root_node && i < 2) + { + if (i == 0) + ep = ˙ + else + ep = &dotdot; + } + else + ep = (struct dirrect *) bufp; + + if ((char)ep->name[0] == FAT_DIR_NAME_LAST) + { + /* Last entry. */ + bufp = buf + buflen; + continue; + } + + if ((char)ep->name[0] == FAT_DIR_NAME_DELETED || (ep->attribute & FAT_DIR_ATTR_LABEL)) + { + bufp = bufp + FAT_DIR_REC_LEN; + continue; + } + + /* See if there's room to hold this one. */ + + fat_to_unix_filename(ep->name, name); + namlen = strlen(name); + + /* Perhaps downcase it? */ + + reclen = sizeof (struct dirent) + namlen; + reclen = (reclen + 3) & ~3; + + /* Expand buffer if necessary. */ + if (datap - *data + reclen > allocsize) + { + vm_address_t newdata; + + vm_allocate (mach_task_self (), &newdata, + (ouralloc + ? (allocsize *= 2) + : (allocsize = vm_page_size * 2)), 1); + memcpy ((void *) newdata, (void *) *data, datap - *data); + + if (ouralloc) + munmap (*data, allocsize / 2); + + datap = (char *) newdata + (datap - *data); + *data = (char *) newdata; + ouralloc = 1; + } + + userp = (struct dirent *) datap; + + /* Fill in entry. */ + { + ino_t inode; + inode_t v_inode; + vi_key_t entry_key; + + entry_key.dir_inode = dp->cache_id; + entry_key.dir_offset = bufp - buf; + + vi_rlookup (entry_key, &inode, &v_inode, 1); + userp->d_fileno = inode; + } + userp->d_type = DT_UNKNOWN; + userp->d_reclen = reclen; + userp->d_namlen = namlen; + memcpy (userp->d_name, name, namlen); + userp->d_name[namlen] = '\0'; + + /* And move along. */ + datap = datap + reclen; + if (!(dp == diskfs_root_node && i < 2)) + bufp = bufp + FAT_DIR_REC_LEN; + i++; + } + + /* If we didn't use all the pages of a buffer we allocated, free + the excess. */ + if (ouralloc + && round_page (datap - *data) < round_page (allocsize)) + munmap ((caddr_t) round_page (datap), + round_page (allocsize) - round_page (datap - *data)); + + munmap ((caddr_t) buf, buflen); + + /* Return. */ + *amt = i; + *datacnt = datap - *data; + return 0; +} |