X-Git-Url: http://www.aleph1.co.uk/gitweb/?p=yaffs2.git;a=blobdiff_plain;f=yaffs_fs.c;h=8034a066edd39f72414692656d60355cd360bdf6;hp=625683ca8bcdc4988616bacb261a8efbc77bf6b4;hb=7e03669f1fdf249268ca1021522e5d0f45eec19c;hpb=f0193bed34c1323e2894c72ffccc28e7b31215f7 diff --git a/yaffs_fs.c b/yaffs_fs.c index 625683c..8034a06 100644 --- a/yaffs_fs.c +++ b/yaffs_fs.c @@ -32,7 +32,7 @@ */ const char *yaffs_fs_c_version = - "$Id: yaffs_fs.c,v 1.88 2009-11-29 21:42:47 charles Exp $"; + "$Id: yaffs_fs.c,v 1.93 2010-02-10 03:54:08 charles Exp $"; extern const char *yaffs_guts_c_version; #include @@ -54,6 +54,9 @@ extern const char *yaffs_guts_c_version; #include "asm/div64.h" + +#define LOCK_TRACE 0 + #if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 5, 0)) #include /* Added NCB 15-8-2003 */ @@ -111,6 +114,7 @@ static uint32_t YCALCBLOCKS(uint64_t partition_size, uint32_t block_size) #include #include "yportenv.h" +#include "yaffs_trace.h" #include "yaffs_guts.h" #include @@ -320,6 +324,17 @@ static const struct file_operations yaffs_file_operations = { }; #endif +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)) +static void zero_user_segment(struct page *page, unsigned start, unsigned end) +{ + void * kaddr = kmap_atomic(page, KM_USER0); + memset(kaddr + start, 0, end - start); + kunmap_atomic(kaddr, KM_USER0); + flush_dcache_page(page); +} +#endif + + static const struct inode_operations yaffs_file_inode_operations = { .setattr = yaffs_setattr, }; @@ -367,17 +382,133 @@ static const struct super_operations yaffs_super_ops = { static void yaffs_GrossLock(yaffs_Device *dev) { - T(YAFFS_TRACE_OS, ("yaffs locking %p\n", current)); + T(LOCK_TRACE && YAFFS_TRACE_OS, ("yaffs locking %p\n", current)); down(&dev->grossLock); - T(YAFFS_TRACE_OS, ("yaffs locked %p\n", current)); + T(LOCK_TRACE && YAFFS_TRACE_OS, ("yaffs locked %p\n", current)); } static void yaffs_GrossUnlock(yaffs_Device *dev) { - T(YAFFS_TRACE_OS, ("yaffs unlocking %p\n", current)); + T(LOCK_TRACE && YAFFS_TRACE_OS, ("yaffs unlocking %p\n", current)); up(&dev->grossLock); } + +/*-----------------------------------------------------------------*/ +/* Directory search context allows us to unlock access to yaffs during + * filldir without causing problems with the directory being modified. + * This is similar to the tried and tested mechanism used in yaffs direct. + * + * A search context iterates along a doubly linked list of siblings in the + * directory. If the iterating object is deleted then this would corrupt + * the list iteration, likely causing a crash. The search context avoids + * this by using the removeObjectCallback to move the search context to the + * next object before the object is deleted. + * + * Many readdirs (and thus seach conexts) may be alive simulateously so + * each yaffs_Device has a list of these. + * + * A seach context lives for the duration of a readdir. + * + * All these functions must be called while yaffs is locked. + */ + +struct yaffs_SearchContext { + yaffs_Device *dev; + yaffs_Object *dirObj; + yaffs_Object *nextReturn; + struct ylist_head others; +}; + +/* + * yaffs_NewSearch() creates a new search context, initialises it and + * adds it to the device's search context list. + * + * Called at start of readdir. + */ +static struct yaffs_SearchContext * yaffs_NewSearch(yaffs_Object *dir) +{ + yaffs_Device *dev = dir->myDev; + struct yaffs_SearchContext *sc = YMALLOC(sizeof(struct yaffs_SearchContext)); + if(sc){ + sc->dirObj = dir; + sc->dev = dev; + if( ylist_empty(&sc->dirObj->variant.directoryVariant.children)) + sc->nextReturn = NULL; + else + sc->nextReturn = ylist_entry( + dir->variant.directoryVariant.children.next, + yaffs_Object,siblings); + YINIT_LIST_HEAD(&sc->others); + ylist_add(&sc->others,&dev->searchContexts); + } + return sc; +} + +/* + * yaffs_EndSearch() disposes of a search context and cleans up. + */ +static void yaffs_EndSearch(struct yaffs_SearchContext * sc) +{ + if(sc){ + ylist_del(&sc->others); + YFREE(sc); + } +} + +/* + * yaffs_SearchAdvance() moves a search context to the next object. + * Called when the search iterates or when an object removal causes + * the search context to be moved to the next object. + */ +static void yaffs_SearchAdvance(struct yaffs_SearchContext *sc) +{ + if(!sc) + return; + + if( sc->nextReturn == NULL || + ylist_empty(&sc->dirObj->variant.directoryVariant.children)) + sc->nextReturn = NULL; + else { + struct ylist_head *next = sc->nextReturn->siblings.next; + + if( next == &sc->dirObj->variant.directoryVariant.children) + sc->nextReturn = NULL; /* end of list */ + else + sc->nextReturn = ylist_entry(next,yaffs_Object,siblings); + } +} + +/* + * yaffs_RemoveObjectCallback() is called when an object is unlinked. + * We check open search contexts and advance any which are currently + * on the object being iterated. + */ +static void yaffs_RemoveObjectCallback(yaffs_Object *obj) +{ + + struct ylist_head *i; + struct yaffs_SearchContext *sc; + struct ylist_head *search_contexts = &obj->myDev->searchContexts; + + + /* Iterate through the directory search contexts. + * If any are currently on the object being removed, then advance + * the search context to the next object to prevent a hanging pointer. + */ + ylist_for_each(i, search_contexts) { + if (i) { + sc = ylist_entry(i, struct yaffs_SearchContext,others); + if(sc->nextReturn == obj) + yaffs_SearchAdvance(sc); + } + } + +} + + +/*-----------------------------------------------------------------*/ + static int yaffs_readlink(struct dentry *dentry, char __user *buffer, int buflen) { @@ -589,7 +720,7 @@ static int yaffs_file_flush(struct file *file) yaffs_GrossLock(dev); - yaffs_FlushFile(obj, 1,0); + yaffs_FlushFile(obj, 1, 0); yaffs_GrossUnlock(dev); @@ -606,7 +737,7 @@ static int yaffs_readpage_nolock(struct file *f, struct page *pg) yaffs_Device *dev; - T(YAFFS_TRACE_OS, ("yaffs_readpage at %08x, size %08x\n", + T(YAFFS_TRACE_OS, ("yaffs_readpage_nolock at %08x, size %08x\n", (unsigned)(pg->index << PAGE_CACHE_SHIFT), (unsigned)PAGE_CACHE_SIZE)); @@ -646,7 +777,7 @@ static int yaffs_readpage_nolock(struct file *f, struct page *pg) flush_dcache_page(pg); kunmap(pg); - T(YAFFS_TRACE_OS, ("yaffs_readpage done\n")); + T(YAFFS_TRACE_OS, ("yaffs_readpage_nolock done\n")); return ret; } @@ -659,7 +790,12 @@ static int yaffs_readpage_unlock(struct file *f, struct page *pg) static int yaffs_readpage(struct file *f, struct page *pg) { - return yaffs_readpage_unlock(f, pg); + int ret; + + T(YAFFS_TRACE_OS, ("yaffs_readpage\n")); + ret=yaffs_readpage_unlock(f, pg); + T(YAFFS_TRACE_OS, ("yaffs_readpage done\n")); + return ret; } /* writepage inspired by/stolen from smbfs */ @@ -671,38 +807,46 @@ static int yaffs_writepage(struct page *page) #endif { struct address_space *mapping = page->mapping; - loff_t offset = (loff_t) page->index << PAGE_CACHE_SHIFT; struct inode *inode; unsigned long end_index; char *buffer; yaffs_Object *obj; int nWritten = 0; unsigned nBytes; + loff_t i_size; if (!mapping) BUG(); inode = mapping->host; if (!inode) BUG(); + i_size = i_size_read(inode); - if (offset > inode->i_size) { - T(YAFFS_TRACE_OS, - ("yaffs_writepage at %08x, inode size = %08x!!!\n", - (unsigned)(page->index << PAGE_CACHE_SHIFT), - (unsigned)inode->i_size)); - T(YAFFS_TRACE_OS, - (" -> don't care!!\n")); - unlock_page(page); - return 0; - } - - end_index = inode->i_size >> PAGE_CACHE_SHIFT; + end_index = i_size >> PAGE_CACHE_SHIFT; - /* easy case */ - if (page->index < end_index) + if(page->index < end_index) nBytes = PAGE_CACHE_SIZE; - else - nBytes = inode->i_size & (PAGE_CACHE_SIZE - 1); + else { + nBytes = i_size & (PAGE_CACHE_SIZE -1); + + if (page->index > end_index || !nBytes) { + T(YAFFS_TRACE_OS, + ("yaffs_writepage at %08x, inode size = %08x!!!\n", + (unsigned)(page->index << PAGE_CACHE_SHIFT), + (unsigned)inode->i_size)); + T(YAFFS_TRACE_OS, + (" -> don't care!!\n")); + + zero_user_segment(page,0,PAGE_CACHE_SIZE); + set_page_writeback(page); + unlock_page(page); + end_page_writeback(page); + return 0; + } + } + + if(nBytes != PAGE_CACHE_SIZE) + zero_user_segment(page,nBytes,PAGE_CACHE_SIZE); get_page(page); @@ -728,8 +872,9 @@ static int yaffs_writepage(struct page *page) yaffs_GrossUnlock(obj->myDev); kunmap(page); - SetPageUptodate(page); - UnlockPage(page); + set_page_writeback(page); + unlock_page(page); + end_page_writeback(page); put_page(page); return (nWritten == nBytes) ? 0 : -ENOSPC; @@ -743,13 +888,10 @@ static int yaffs_write_begin(struct file *filp, struct address_space *mapping, { struct page *pg = NULL; pgoff_t index = pos >> PAGE_CACHE_SHIFT; - uint32_t offset = pos & (PAGE_CACHE_SIZE - 1); - uint32_t to = offset + len; int ret = 0; int space_held = 0; - T(YAFFS_TRACE_OS, ("start yaffs_write_begin\n")); /* Get a page */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 28) pg = grab_cache_page_write_begin(mapping, index, flags); @@ -762,6 +904,8 @@ static int yaffs_write_begin(struct file *filp, struct address_space *mapping, ret = -ENOMEM; goto out; } + T(YAFFS_TRACE_OS, ("start yaffs_write_begin index %d(%x) uptodate %d\n",(int)index,(int)index,Page_Uptodate(pg) ? 1 : 0)); + /* Get fs space */ space_held = yaffs_hold_space(filp); @@ -772,7 +916,7 @@ static int yaffs_write_begin(struct file *filp, struct address_space *mapping, /* Update page if required */ - if (!Page_Uptodate(pg) && (offset || to < PAGE_CACHE_SIZE)) + if (!Page_Uptodate(pg)) ret = yaffs_readpage_nolock(filp, pg); if (ret) @@ -801,7 +945,7 @@ static int yaffs_prepare_write(struct file *f, struct page *pg, { T(YAFFS_TRACE_OS, ("yaffs_prepair_write\n")); - if (!Page_Uptodate(pg) && (offset || to < PAGE_CACHE_SIZE)) + if (!Page_Uptodate(pg)) return yaffs_readpage_nolock(f, pg); return 0; } @@ -831,9 +975,8 @@ static int yaffs_write_end(struct file *filp, struct address_space *mapping, ("yaffs_write_end not same size ret %d copied %d\n", ret, copied)); SetPageError(pg); - ClearPageUptodate(pg); } else { - SetPageUptodate(pg); + /* Nothing */ } kunmap(pg); @@ -873,9 +1016,8 @@ static int yaffs_commit_write(struct file *f, struct page *pg, unsigned offset, ("yaffs_commit_write not same size nWritten %d nBytes %d\n", nWritten, nBytes)); SetPageError(pg); - ClearPageUptodate(pg); } else { - SetPageUptodate(pg); + /* Nothing */ } kunmap(pg); @@ -1055,15 +1197,15 @@ static ssize_t yaffs_file_write(struct file *f, const char *buf, size_t n, ("yaffs_file_write: hey obj is null!\n")); else T(YAFFS_TRACE_OS, - ("yaffs_file_write about to write writing %zu bytes" - "to object %d at %d\n", - n, obj->objectId, ipos)); + ("yaffs_file_write about to write writing %u(%x) bytes" + "to object %d at %d(%x)\n", + (unsigned) n, (unsigned) n, obj->objectId, ipos,ipos)); nWritten = yaffs_WriteDataToFile(obj, buf, ipos, n, 0); T(YAFFS_TRACE_OS, - ("yaffs_file_write writing %zu bytes, %d written at %d\n", - n, nWritten, ipos)); + ("yaffs_file_write: %d(%x) bytes written\n", + (unsigned )n,(unsigned)n)); if (nWritten > 0) { ipos += nWritten; @@ -1128,10 +1270,11 @@ static int yaffs_readdir(struct file *f, void *dirent, filldir_t filldir) { yaffs_Object *obj; yaffs_Device *dev; + struct yaffs_SearchContext *sc; struct inode *inode = f->f_dentry->d_inode; unsigned long offset, curoffs; - struct ylist_head *i; yaffs_Object *l; + int retVal = 0; char name[YAFFS_MAX_NAME_LENGTH + 1]; @@ -1142,14 +1285,22 @@ static int yaffs_readdir(struct file *f, void *dirent, filldir_t filldir) offset = f->f_pos; + sc = yaffs_NewSearch(obj); + if(!sc){ + retVal = -ENOMEM; + goto unlock_out; + } + T(YAFFS_TRACE_OS, ("yaffs_readdir: starting at %d\n", (int)offset)); if (offset == 0) { T(YAFFS_TRACE_OS, ("yaffs_readdir: entry . ino %d \n", (int)inode->i_ino)); + yaffs_GrossUnlock(dev); if (filldir(dirent, ".", 1, offset, inode->i_ino, DT_DIR) < 0) goto out; + yaffs_GrossLock(dev); offset++; f->f_pos++; } @@ -1157,9 +1308,11 @@ static int yaffs_readdir(struct file *f, void *dirent, filldir_t filldir) T(YAFFS_TRACE_OS, ("yaffs_readdir: entry .. ino %d \n", (int)f->f_dentry->d_parent->d_inode->i_ino)); + yaffs_GrossUnlock(dev); if (filldir(dirent, "..", 2, offset, f->f_dentry->d_parent->d_inode->i_ino, DT_DIR) < 0) goto out; + yaffs_GrossLock(dev); offset++; f->f_pos++; } @@ -1175,10 +1328,12 @@ static int yaffs_readdir(struct file *f, void *dirent, filldir_t filldir) f->f_version = inode->i_version; } - ylist_for_each(i, &obj->variant.directoryVariant.children) { + while(sc->nextReturn){ curoffs++; + l = sc->nextReturn; if (curoffs >= offset) { - l = ylist_entry(i, yaffs_Object, siblings); + int this_inode = yaffs_GetObjectInode(l); + int this_type = yaffs_GetObjectType(l); yaffs_GetObjectName(l, name, YAFFS_MAX_NAME_LENGTH + 1); @@ -1186,24 +1341,30 @@ static int yaffs_readdir(struct file *f, void *dirent, filldir_t filldir) ("yaffs_readdir: %s inode %d\n", name, yaffs_GetObjectInode(l))); + yaffs_GrossUnlock(dev); + if (filldir(dirent, name, strlen(name), offset, - yaffs_GetObjectInode(l), - yaffs_GetObjectType(l)) < 0) - goto up_and_out; + this_inode, + this_type) < 0) + goto out; + + yaffs_GrossLock(dev); offset++; f->f_pos++; } + yaffs_SearchAdvance(sc); } -up_and_out: -out: +unlock_out: yaffs_GrossUnlock(dev); +out: + yaffs_EndSearch(sc); - return 0; + return retVal; } /* @@ -1436,7 +1597,7 @@ static int yaffs_sync_object(struct file *file, struct dentry *dentry, dev = obj->myDev; - T(YAFFS_TRACE_OS, ("yaffs_sync_object\n")); + T(YAFFS_TRACE_OS | YAFFS_TRACE_SYNC, ("yaffs_sync_object\n")); yaffs_GrossLock(dev); yaffs_FlushFile(obj, 1, datasync); yaffs_GrossUnlock(dev); @@ -1501,27 +1662,46 @@ static int yaffs_rename(struct inode *old_dir, struct dentry *old_dentry, static int yaffs_setattr(struct dentry *dentry, struct iattr *attr) { struct inode *inode = dentry->d_inode; - int error; + int error = 0; yaffs_Device *dev; T(YAFFS_TRACE_OS, ("yaffs_setattr of object %d\n", yaffs_InodeToObject(inode)->objectId)); - error = inode_change_ok(inode, attr); + /* Fail if a requested resize >= 2GB */ + if (attr->ia_valid & ATTR_SIZE && + (attr->ia_size >> 31)) + error = -EINVAL; + + if (error == 0) + error = inode_change_ok(inode, attr); if (error == 0) { + int result; + if (!error){ + error = inode_setattr(inode, attr); + T(YAFFS_TRACE_OS,("inode_setattr called\n")); + if (attr->ia_valid & ATTR_SIZE) + truncate_inode_pages(&inode->i_data,attr->ia_size); + } dev = yaffs_InodeToObject(inode)->myDev; + if (attr->ia_valid & ATTR_SIZE){ + T(YAFFS_TRACE_OS,("resize to %d(%x)\n",(int)(attr->ia_size),(int)(attr->ia_size))); + } yaffs_GrossLock(dev); - if (yaffs_SetAttributes(yaffs_InodeToObject(inode), attr) == - YAFFS_OK) { + result = yaffs_SetAttributes(yaffs_InodeToObject(inode), attr); + if(result == YAFFS_OK) { error = 0; } else { error = -EPERM; } yaffs_GrossUnlock(dev); - if (!error) - error = inode_setattr(inode, attr); + } + + T(YAFFS_TRACE_OS, + ("yaffs_setattr done returning %d\n",error)); + return error; } @@ -1615,7 +1795,7 @@ static int yaffs_do_sync_fs(struct super_block *sb) { yaffs_Device *dev = yaffs_SuperToDevice(sb); - T(YAFFS_TRACE_OS, ("yaffs_do_sync_fs\n")); + T(YAFFS_TRACE_OS | YAFFS_TRACE_SYNC, ("yaffs_do_sync_fs\n")); if (sb->s_dirt) { yaffs_GrossLock(dev); @@ -1641,7 +1821,7 @@ static int yaffs_write_super(struct super_block *sb) #endif { - T(YAFFS_TRACE_OS, ("yaffs_write_super\n")); + T(YAFFS_TRACE_OS | YAFFS_TRACE_SYNC, ("yaffs_write_super\n")); if (yaffs_auto_checkpoint >= 2) yaffs_do_sync_fs(sb); #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) @@ -1656,7 +1836,7 @@ static int yaffs_sync_fs(struct super_block *sb, int wait) static int yaffs_sync_fs(struct super_block *sb) #endif { - T(YAFFS_TRACE_OS, ("yaffs_sync_fs\n")); + T(YAFFS_TRACE_OS | YAFFS_TRACE_SYNC, ("yaffs_sync_fs\n")); if (yaffs_auto_checkpoint >= 1) yaffs_do_sync_fs(sb); @@ -2158,6 +2338,10 @@ static struct super_block *yaffs_internal_read_super(int yaffsVersion, /* we assume this is protected by lock_kernel() in mount/umount */ ylist_add_tail(&dev->devList, &yaffs_dev_list); + /* Directory search handling...*/ + YINIT_LIST_HEAD(&dev->searchContexts); + dev->removeObjectCallback = yaffs_RemoveObjectCallback; + init_MUTEX(&dev->grossLock); yaffs_GrossLock(dev); @@ -2428,6 +2612,7 @@ static struct { {"scan_debug", YAFFS_TRACE_SCAN_DEBUG}, {"scan", YAFFS_TRACE_SCAN}, {"tracing", YAFFS_TRACE_TRACING}, + {"sync", YAFFS_TRACE_SYNC}, {"verify", YAFFS_TRACE_VERIFY}, {"verify_nand", YAFFS_TRACE_VERIFY_NAND},