X-Git-Url: http://www.aleph1.co.uk/gitweb/?p=yaffs2.git;a=blobdiff_plain;f=yaffs_fs.c;h=a62724fe16e5d8c81b19917dfce6c6ce2b1491d3;hp=3fd94df12bab57878658836f06b4a0056ce0a7e8;hb=1ecaab628c922b75b3fa6cdb9fd73bfe619fe1c8;hpb=3e61039bf6fc5a2f8af6a5cdd29e30b3fa10b4f5 diff --git a/yaffs_fs.c b/yaffs_fs.c index 3fd94df..a62724f 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.83 2009-09-23 23:24:55 charles Exp $"; + "$Id: yaffs_fs.c,v 1.90 2009-12-23 03:14:17 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 */ @@ -118,7 +121,7 @@ static uint32_t YCALCBLOCKS(uint64_t partition_size, uint32_t block_size) #include "yaffs_mtdif1.h" #include "yaffs_mtdif2.h" -unsigned int yaffs_traceMask = YAFFS_TRACE_BAD_BLOCKS; +unsigned int yaffs_traceMask = YAFFS_TRACE_BAD_BLOCKS | YAFFS_TRACE_ALWAYS; unsigned int yaffs_wr_attempts = YAFFS_WR_ATTEMPTS; unsigned int yaffs_auto_checkpoint = 1; @@ -367,17 +370,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 +708,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 +725,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 +765,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 +778,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 +795,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 +860,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 +876,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 +892,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 +904,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 +933,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 +963,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 +1004,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 +1185,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 +1258,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 +1273,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 +1296,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 +1316,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 +1329,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; } /* @@ -1510,18 +1659,30 @@ static int yaffs_setattr(struct dentry *dentry, struct iattr *attr) 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\n")); + return error; } @@ -1812,9 +1973,15 @@ typedef struct { int skip_checkpoint_read; int skip_checkpoint_write; int no_cache; + int tags_ecc_on; + int tags_ecc_overridden; + int lazy_loading_enabled; + int lazy_loading_overridden; + int empty_lost_and_found; + int empty_lost_and_found_overridden; } yaffs_options; -#define MAX_OPT_LEN 20 +#define MAX_OPT_LEN 30 static int yaffs_parse_options(yaffs_options *options, const char *options_str) { char cur_opt[MAX_OPT_LEN + 1]; @@ -1827,6 +1994,9 @@ static int yaffs_parse_options(yaffs_options *options, const char *options_str) memset(cur_opt, 0, MAX_OPT_LEN + 1); p = 0; + while(*options_str == ',') + options_str++; + while (*options_str && *options_str != ',') { if (p < MAX_OPT_LEN) { cur_opt[p] = *options_str; @@ -1837,7 +2007,25 @@ static int yaffs_parse_options(yaffs_options *options, const char *options_str) if (!strcmp(cur_opt, "inband-tags")) options->inband_tags = 1; - else if (!strcmp(cur_opt, "no-cache")) + else if (!strcmp(cur_opt, "tags-ecc-off")){ + options->tags_ecc_on = 0; + options->tags_ecc_overridden=1; + } else if (!strcmp(cur_opt, "tags-ecc-on")){ + options->tags_ecc_on = 1; + options->tags_ecc_overridden = 1; + } else if (!strcmp(cur_opt, "lazy-loading-off")){ + options->lazy_loading_enabled = 0; + options->lazy_loading_overridden=1; + } else if (!strcmp(cur_opt, "lazy-loading-on")){ + options->lazy_loading_enabled = 1; + options->lazy_loading_overridden = 1; + } else if (!strcmp(cur_opt, "empty-lost-and-found-off")){ + options->empty_lost_and_found = 0; + options->empty_lost_and_found_overridden=1; + } else if (!strcmp(cur_opt, "empty-lost-and-found-on")){ + options->empty_lost_and_found = 1; + options->empty_lost_and_found_overridden=1; + } else if (!strcmp(cur_opt, "no-cache")) options->no_cache = 1; else if (!strcmp(cur_opt, "no-checkpoint-read")) options->skip_checkpoint_read = 1; @@ -2053,6 +2241,24 @@ static struct super_block *yaffs_internal_read_super(int yaffsVersion, dev->nShortOpCaches = (options.no_cache) ? 0 : 10; dev->inbandTags = options.inband_tags; +#ifdef CONFIG_YAFFS_DISABLE_LAZY_LOAD + dev->disableLazyLoad = 1; +#endif + if(options.lazy_loading_overridden) + dev->disableLazyLoad = !options.lazy_loading_enabled; + +#ifdef CONFIG_YAFFS_DISABLE_TAGS_ECC + dev->noTagsECC = 1; +#endif + if(options.tags_ecc_overridden) + dev->noTagsECC = !options.tags_ecc_on; + +#ifdef CONFIG_YAFFS_EMPTY_LOST_AND_FOUND + dev->emptyLostAndFound = 1; +#endif + if(options.empty_lost_and_found_overridden) + dev->emptyLostAndFound = options.empty_lost_and_found; + /* ... and the functions. */ if (yaffsVersion == 2) { dev->writeChunkWithTagsToNAND = @@ -2113,6 +2319,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); @@ -2253,7 +2463,7 @@ static DECLARE_FSTYPE(yaffs2_fs_type, "yaffs2", yaffs2_read_super, static struct proc_dir_entry *my_proc_entry; -static char *yaffs_dump_dev(char *buf, yaffs_Device * dev) +static char *yaffs_dump_dev_part0(char *buf, yaffs_Device * dev) { buf += sprintf(buf, "startBlock......... %d\n", dev->startBlock); buf += sprintf(buf, "endBlock........... %d\n", dev->endBlock); @@ -2288,9 +2498,19 @@ static char *yaffs_dump_dev(char *buf, yaffs_Device * dev) buf += sprintf(buf, "nUnlinkedFiles..... %d\n", dev->nUnlinkedFiles); buf += sprintf(buf, "nBackgroudDeletions %d\n", dev->nBackgroundDeletions); + + return buf; +} + + +static char *yaffs_dump_dev_part1(char *buf, yaffs_Device * dev) +{ buf += sprintf(buf, "useNANDECC......... %d\n", dev->useNANDECC); + buf += sprintf(buf, "noTagsECC.......... %d\n", dev->noTagsECC); buf += sprintf(buf, "isYaffs2........... %d\n", dev->isYaffs2); buf += sprintf(buf, "inbandTags......... %d\n", dev->inbandTags); + buf += sprintf(buf, "emptyLostAndFound.. %d\n", dev->emptyLostAndFound); + buf += sprintf(buf, "disableLazyLoad.... %d\n", dev->disableLazyLoad); return buf; } @@ -2313,27 +2533,35 @@ static int yaffs_proc_read(char *page, *(int *)start = 1; /* Print header first */ - if (step == 0) { + if (step == 0) buf += sprintf(buf, "YAFFS built:" __DATE__ " " __TIME__ "\n%s\n%s\n", yaffs_fs_c_version, yaffs_guts_c_version); - } - - /* hold lock_kernel while traversing yaffs_dev_list */ - lock_kernel(); - - /* Locate and print the Nth entry. Order N-squared but N is small. */ - ylist_for_each(item, &yaffs_dev_list) { - yaffs_Device *dev = ylist_entry(item, yaffs_Device, devList); - if (n < step) { - n++; - continue; + else if (step == 1) + buf += sprintf(buf,"\n"); + else { + step-=2; + + /* hold lock_kernel while traversing yaffs_dev_list */ + lock_kernel(); + + /* Locate and print the Nth entry. Order N-squared but N is small. */ + ylist_for_each(item, &yaffs_dev_list) { + yaffs_Device *dev = ylist_entry(item, yaffs_Device, devList); + if (n < (step & ~1)) { + n+=2; + continue; + } + if((step & 1)==0){ + buf += sprintf(buf, "\nDevice %d \"%s\"\n", n, dev->name); + buf = yaffs_dump_dev_part0(buf, dev); + } else + buf = yaffs_dump_dev_part1(buf, dev); + + break; } - buf += sprintf(buf, "\nDevice %d \"%s\"\n", n, dev->name); - buf = yaffs_dump_dev(buf, dev); - break; + unlock_kernel(); } - unlock_kernel(); return buf - page < count ? buf - page : count; } @@ -2378,7 +2606,7 @@ static struct { }; #define MAX_MASK_NAME_LENGTH 40 -static int yaffs_proc_write(struct file *file, const char *buf, +static int yaffs_proc_write_trace_options(struct file *file, const char *buf, unsigned long count, void *data) { unsigned rg = 0, mask_bitfield; @@ -2470,6 +2698,13 @@ static int yaffs_proc_write(struct file *file, const char *buf, return count; } + +static int yaffs_proc_write(struct file *file, const char *buf, + unsigned long count, void *data) +{ + return yaffs_proc_write_trace_options(file, buf, count, data); +} + /* Stuff to handle installation of file systems */ struct file_system_to_install { struct file_system_type *fst;