diff options
-rw-r--r-- | ext2fs/pager.c | 570 |
1 files changed, 570 insertions, 0 deletions
diff --git a/ext2fs/pager.c b/ext2fs/pager.c new file mode 100644 index 00000000..d41b792d --- /dev/null +++ b/ext2fs/pager.c @@ -0,0 +1,570 @@ +/* Pager for ufs + Copyright (C) 1994 Free Software Foundation + + 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 "ufs.h" +#include <strings.h> +#include <stdio.h> + +spin_lock_t pagerlistlock = SPIN_LOCK_INITIALIZER; +struct user_pager_info *filepagerlist; + +spin_lock_t node2pagelock = SPIN_LOCK_INITIALIZER; + +#ifdef DONT_CACHE_MEMORY_OBJECTS +#define MAY_CACHE 0 +#else +#define MAY_CACHE 1 +#endif + +/* Find the location on disk of page OFFSET in pager UPI. Return the + disk address (in disk block) in *ADDR. If *NPLOCK is set on + return, then release that mutex after I/O on the data has + completed. Set DISKSIZE to be the amount of valid data on disk. + (If this is an unallocated block, then set *ADDR to zero.) */ +static error_t +find_address (struct user_pager_info *upi, + vm_address_t offset, + daddr_t *addr, + int *disksize, + struct rwlock **nplock) +{ + error_t err; + + assert (upi->type == DISK || upi->type == FILE_DATA); + + if (upi->type == DISK) + { + *disksize = __vm_page_size; + *addr = offset / DEV_BSIZE; + *nplock = 0; + return 0; + } + else + { + struct iblock_spec indirs[NIADDR + 1]; + struct node *np; + + np = upi->np; + + rwlock_reader_lock (&np->dn->allocptrlock); + *nplock = &np->dn->allocptrlock; + + if (offset >= np->allocsize) + { + rwlock_reader_unlock (&np->dn->allocptrlock); + return EIO; + } + + if (offset + __vm_page_size > np->allocsize) + *disksize = np->allocsize - offset; + else + *disksize = __vm_page_size; + + err = fetch_indir_spec (np, lblkno (sblock, offset), indirs); + if (err) + rwlock_reader_unlock (&np->dn->allocptrlock); + else + { + if (indirs[0].bno) + *addr = (fsbtodb (sblock, indirs[0].bno) + + blkoff (sblock, offset) / DEV_BSIZE); + else + *addr = 0; + } + + return err; + } +} + + +/* Implement the pager_read_page callback from the pager library. See + <hurd/pager.h> for the interface description. */ +error_t +pager_read_page (struct user_pager_info *pager, + vm_offset_t page, + vm_address_t *buf, + int *writelock) +{ + error_t err; + struct rwlock *nplock; + daddr_t addr; + int disksize; + + err = find_address (pager, page, &addr, &disksize, &nplock); + if (err) + return err; + + if (addr) + { + err = dev_read_sync (addr, (void *)buf, disksize); + if (!err && disksize != __vm_page_size) + bzero ((void *)(*buf + disksize), __vm_page_size - disksize); + *writelock = 0; + } + else + { +#if 0 + printf ("Write-locked pagein Object %#x\tOffset %#x\n", pager, page); + fflush (stdout); +#endif + vm_allocate (mach_task_self (), buf, __vm_page_size, 1); + *writelock = 1; + } + + if (nplock) + rwlock_reader_unlock (nplock); + + return err; +} + +/* Implement the pager_write_page callback from the pager library. See + <hurd/pager.h> for the interface description. */ +error_t +pager_write_page (struct user_pager_info *pager, + vm_offset_t page, + vm_address_t buf) +{ + daddr_t addr; + int disksize; + struct rwlock *nplock; + error_t err; + + err = find_address (pager, page, &addr, &disksize, &nplock); + if (err) + return err; + + if (addr) + err = dev_write_sync (addr, buf, disksize); + else + { + printf ("Attempt to write unallocated disk\n."); + printf ("Object %p\tOffset %#x\n", pager, page); + fflush (stdout); + err = 0; /* unallocated disk; + error would be pointless */ + } + + if (nplock) + rwlock_reader_unlock (nplock); + + return err; +} + +/* Implement the pager_unlock_page callback from the pager library. See + <hurd/pager.h> for the interface description. */ +error_t +pager_unlock_page (struct user_pager_info *pager, + vm_offset_t address) +{ + struct node *np; + error_t err; + struct iblock_spec indirs[NIADDR + 1]; + daddr_t bno; + struct disknode *dn; + struct dinode *di; + + /* Zero an sblock->fs_bsize piece of disk starting at BNO, + synchronously. We do this on newly allocated indirect + blocks before setting the pointer to them to ensure that an + indirect block absolutely never points to garbage. */ + void zero_disk_block (int bno) + { + bzero (indir_block (bno), sblock->fs_bsize); + sync_disk_blocks (bno, sblock->fs_bsize, 1); + }; + + /* Problem--where to get cred values for allocation here? */ + +#if 0 + printf ("Unlock page request, Object %#x\tOffset %#x...", pager, address); + fflush (stdout); +#endif + + if (pager->type == DISK) + return 0; + + np = pager->np; + dn = np->dn; + di = dino (dn->number); + + rwlock_writer_lock (&dn->allocptrlock); + + /* If this is the last block, we don't let it get unlocked. */ + if (address + __vm_page_size + > blkroundup (sblock, np->allocsize) - sblock->fs_bsize) + { + printf ("attempt to unlock at last block denied\n"); + fflush (stdout); + rwlock_writer_unlock (&dn->allocptrlock); + return EIO; + } + + err = fetch_indir_spec (np, lblkno (sblock, address), indirs); + if (err) + { + rwlock_writer_unlock (&dn->allocptrlock); + return EIO; + } + + err = diskfs_catch_exception (); + if (err) + { + rwlock_writer_unlock (&dn->allocptrlock); + return EIO; + } + + /* See if we need a triple indirect block; fail if we do. */ + assert (indirs[0].offset == -1 + || indirs[1].offset == -1 + || indirs[2].offset == -1); + + /* Check to see if this block is allocated. */ + if (indirs[0].bno == 0) + { + if (indirs[0].offset == -1) + { + err = ffs_alloc (np, lblkno (sblock, address), + ffs_blkpref (np, lblkno (sblock, address), + lblkno (sblock, address), di->di_db), + sblock->fs_bsize, &bno, 0); + if (err) + goto out; + assert (lblkno (sblock, address) < NDADDR); + indirs[0].bno = di->di_db[lblkno (sblock, address)] = bno; + record_poke (di, sizeof (struct dinode)); + } + else + { + daddr_t *siblock; + + /* We need to set siblock to the single indirect block + array; see if the single indirect block is allocated. */ + if (indirs[1].bno == 0) + { + if (indirs[1].offset == -1) + { + err = ffs_alloc (np, lblkno (sblock, address), + ffs_blkpref (np, lblkno (sblock, address), + INDIR_SINGLE, di->di_ib), + sblock->fs_bsize, &bno, 0); + if (err) + goto out; + zero_disk_block (bno); + indirs[1].bno = di->di_ib[INDIR_SINGLE] = bno; + record_poke (di, sizeof (struct dinode)); + } + else + { + daddr_t *diblock; + + /* We need to set diblock to the double indirect + block array; see if the double indirect block is + allocated. */ + if (indirs[2].bno == 0) + { + /* This assert because triple indirection is + not supported. */ + assert (indirs[2].offset == -1); + + err = ffs_alloc (np, lblkno (sblock, address), + ffs_blkpref (np, lblkno (sblock, + address), + INDIR_DOUBLE, di->di_ib), + sblock->fs_bsize, &bno, 0); + if (err) + goto out; + zero_disk_block (bno); + indirs[2].bno = di->di_ib[INDIR_DOUBLE] = bno; + record_poke (di, sizeof (struct dinode)); + } + + diblock = indir_block (indirs[2].bno); + mark_indir_dirty (np, indirs[2].bno); + + /* Now we can allocate the single indirect block */ + + err = ffs_alloc (np, lblkno (sblock, address), + ffs_blkpref (np, lblkno (sblock, address), + indirs[1].offset, diblock), + sblock->fs_bsize, &bno, 0); + if (err) + goto out; + zero_disk_block (bno); + indirs[1].bno = diblock[indirs[1].offset] = bno; + record_poke (diblock, sblock->fs_bsize); + } + } + + siblock = indir_block (indirs[1].bno); + mark_indir_dirty (np, indirs[1].bno); + + /* Now we can allocate the data block. */ + + err = ffs_alloc (np, lblkno (sblock, address), + ffs_blkpref (np, lblkno (sblock, address), + indirs[0].offset, siblock), + sblock->fs_bsize, &bno, 0); + if (err) + goto out; + + dev_write_sync (fsbtodb (sblock, bno), zeroblock, sblock->fs_bsize); + + indirs[0].bno = siblock[indirs[0].offset] = bno; + record_poke (siblock, sblock->fs_bsize); + } + } + + out: + diskfs_end_catch_exception (); + rwlock_writer_unlock (&dn->allocptrlock); + return err; +} + +/* Implement the pager_report_extent callback from the pager library. See + <hurd/pager.h> for the interface description. */ +inline error_t +pager_report_extent (struct user_pager_info *pager, + vm_address_t *offset, + vm_size_t *size) +{ + assert (pager->type == DISK || pager->type == FILE_DATA); + + *offset = 0; + + if (pager->type == DISK) + *size = diskpagersize; + else + *size = pager->np->allocsize; + + return 0; +} + +/* Implement the pager_clear_user_data callback from the pager library. + See <hurd/pager.h> for the interface description. */ +void +pager_clear_user_data (struct user_pager_info *upi) +{ + assert (upi->type == FILE_DATA); + spin_lock (&node2pagelock); + upi->np->dn->fileinfo = 0; + spin_unlock (&node2pagelock); + diskfs_nrele_light (upi->np); + *upi->prevp = upi->next; + if (upi->next) + upi->next->prevp = upi->prevp; + free (upi); +} + + + +/* Create a the DISK pager, initializing DISKPAGER, and DISKPAGERPORT */ +void +create_disk_pager () +{ + diskpager = malloc (sizeof (struct user_pager_info)); + diskpager->type = DISK; + diskpager->np = 0; + diskpager->p = pager_create (diskpager, MAY_CACHE, MEMORY_OBJECT_COPY_NONE); + diskpagerport = pager_get_port (diskpager->p); + mach_port_insert_right (mach_task_self (), diskpagerport, diskpagerport, + MACH_MSG_TYPE_MAKE_SEND); +} + +/* This syncs a single file (NP) to disk. Wait for all I/O to complete + if WAIT is set. NP->lock must be held. */ +void +diskfs_file_update (struct node *np, + int wait) +{ + struct dirty_indir *d, *tmp; + struct user_pager_info *upi; + + spin_lock (&node2pagelock); + upi = np->dn->fileinfo; + if (upi) + pager_reference (upi->p); + spin_unlock (&node2pagelock); + + if (upi) + { + pager_sync (upi->p, wait); + pager_unreference (upi->p); + } + + for (d = np->dn->dirty; d; d = tmp) + { + sync_disk_blocks (d->bno, sblock->fs_bsize, wait); + tmp = d->next; + free (d); + } + np->dn->dirty = 0; + + diskfs_node_update (np, wait); +} + +/* Call this to create a FILE_DATA pager and return a send right. + NP must be locked. */ +mach_port_t +diskfs_get_filemap (struct node *np) +{ + struct user_pager_info *upi; + mach_port_t right; + + assert (S_ISDIR (np->dn_stat.st_mode) + || S_ISREG (np->dn_stat.st_mode) + || (S_ISLNK (np->dn_stat.st_mode) + && (!direct_symlink_extension + || np->dn_stat.st_size >= sblock->fs_maxsymlinklen))); + + spin_lock (&node2pagelock); + if (!np->dn->fileinfo) + { + upi = malloc (sizeof (struct user_pager_info)); + upi->type = FILE_DATA; + upi->np = np; + diskfs_nref_light (np); + upi->p = pager_create (upi, MAY_CACHE, MEMORY_OBJECT_COPY_DELAY); + np->dn->fileinfo = upi; + + spin_lock (&pagerlistlock); + upi->next = filepagerlist; + upi->prevp = &filepagerlist; + if (upi->next) + upi->next->prevp = &upi->next; + filepagerlist = upi; + spin_unlock (&pagerlistlock); + } + right = pager_get_port (np->dn->fileinfo->p); + spin_unlock (&node2pagelock); + + mach_port_insert_right (mach_task_self (), right, right, + MACH_MSG_TYPE_MAKE_SEND); + + return right; +} + +/* Call this when we should turn off caching so that unused memory object + ports get freed. */ +void +drop_pager_softrefs (struct node *np) +{ + struct user_pager_info *upi; + + spin_lock (&node2pagelock); + upi = np->dn->fileinfo; + if (upi) + pager_reference (upi->p); + spin_unlock (&node2pagelock); + + if (MAY_CACHE && upi) + pager_change_attributes (upi->p, 0, MEMORY_OBJECT_COPY_DELAY, 0); + if (upi) + pager_unreference (upi->p); +} + +/* Call this when we should turn on caching because it's no longer + important for unused memory object ports to get freed. */ +void +allow_pager_softrefs (struct node *np) +{ + struct user_pager_info *upi; + + spin_lock (&node2pagelock); + upi = np->dn->fileinfo; + if (upi) + pager_reference (upi->p); + spin_unlock (&node2pagelock); + + if (MAY_CACHE && upi) + pager_change_attributes (upi->p, 1, MEMORY_OBJECT_COPY_DELAY, 0); + if (upi) + pager_unreference (upi->p); +} + +/* Call this to find out the struct pager * corresponding to the + FILE_DATA pager of inode IP. This should be used *only* as a subsequent + argument to register_memory_fault_area, and will be deleted when + the kernel interface is fixed. NP must be locked. */ +struct pager * +diskfs_get_filemap_pager_struct (struct node *np) +{ + /* This is safe because fileinfo can't be cleared; there must be + an active mapping for this to be called. */ + return np->dn->fileinfo->p; +} + +/* Call function FUNC (which takes one argument, a pager) on each pager, with + all file pagers being processed before the disk pager. Make the calls + while holding no locks. */ +static void +pager_traverse (void (*func)(struct user_pager_info *)) +{ + struct user_pager_info *p; + struct item {struct item *next; struct user_pager_info *p;} *list = 0; + struct item *i; + + spin_lock (&pagerlistlock); + for (p = filepagerlist; p; p = p->next) + { + i = alloca (sizeof (struct item)); + i->next = list; + list = i; + pager_reference (p->p); + i->p = p; + } + spin_unlock (&pagerlistlock); + + for (i = list; i; i = i->next) + { + (*func)(i->p); + pager_unreference (i->p->p); + } + + (*func)(diskpager); +} + +/* Shutdown all the pagers. */ +void +diskfs_shutdown_pager () +{ + void shutdown_one (struct user_pager_info *p) + { + pager_shutdown (p->p); + } + + copy_sblock (); + write_all_disknodes (); + pager_traverse (shutdown_one); +} + +/* Sync all the pagers. */ +void +diskfs_sync_everything (int wait) +{ + void sync_one (struct user_pager_info *p) + { + if (p != diskpager) + pager_sync (p->p, wait); + else + sync_disk (wait); + } + + copy_sblock (); + write_all_disknodes (); + pager_traverse (sync_one); +} + |