2005-04-17 02:20:36 +04:00
/*
* JFFS2 - - Journalling Flash File System , Version 2.
*
2007-04-25 17:16:47 +04:00
* Copyright © 2001 - 2007 Red Hat , Inc .
2005-04-17 02:20:36 +04:00
*
* Created by David Woodhouse < dwmw2 @ infradead . org >
*
* For licensing information , see the file ' LICENCE ' in this directory .
*
*/
# include <linux/kernel.h>
# include <linux/sched.h>
# include <linux/slab.h>
# include <linux/vmalloc.h>
# include <linux/mtd/mtd.h>
# include "nodelist.h"
2005-09-22 15:25:00 +04:00
static void jffs2_build_remove_unlinked_inode ( struct jffs2_sb_info * ,
struct jffs2_inode_cache * , struct jffs2_full_dirent * * ) ;
2005-04-17 02:20:36 +04:00
static inline struct jffs2_inode_cache *
first_inode_chain ( int * i , struct jffs2_sb_info * c )
{
for ( ; * i < INOCACHE_HASHSIZE ; ( * i ) + + ) {
if ( c - > inocache_list [ * i ] )
return c - > inocache_list [ * i ] ;
}
return NULL ;
}
static inline struct jffs2_inode_cache *
next_inode ( int * i , struct jffs2_inode_cache * ic , struct jffs2_sb_info * c )
{
/* More in this chain? */
if ( ic - > next )
return ic - > next ;
( * i ) + + ;
return first_inode_chain ( i , c ) ;
}
# define for_each_inode(i, c, ic) \
for ( i = 0 , ic = first_inode_chain ( & i , ( c ) ) ; \
ic ; \
ic = next_inode ( & i , ic , ( c ) ) )
2006-01-15 00:20:43 +03:00
static void jffs2_build_inode_pass1 ( struct jffs2_sb_info * c ,
2005-09-22 15:25:00 +04:00
struct jffs2_inode_cache * ic )
2005-04-17 02:20:36 +04:00
{
struct jffs2_full_dirent * fd ;
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " building directory inode #%u \n " , ic - > ino ) ;
2005-04-17 02:20:36 +04:00
/* For each child, increase nlink */
for ( fd = ic - > scan_dents ; fd ; fd = fd - > next ) {
struct jffs2_inode_cache * child_ic ;
if ( ! fd - > ino )
continue ;
2005-09-22 15:25:00 +04:00
/* we can get high latency here with huge directories */
2005-04-17 02:20:36 +04:00
child_ic = jffs2_get_ino_cache ( c , fd - > ino ) ;
if ( ! child_ic ) {
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " child \" %s \" (ino #%u) of dir ino #%u doesn't exist! \n " ,
2005-04-17 02:20:36 +04:00
fd - > name , fd - > ino , ic - > ino ) ;
jffs2_mark_node_obsolete ( c , fd - > raw ) ;
continue ;
}
if ( child_ic - > nlink + + & & fd - > type = = DT_DIR ) {
2005-09-22 15:25:00 +04:00
JFFS2_ERROR ( " child dir \" %s \" (ino #%u) of dir ino #%u appears to be a hard link \n " ,
fd - > name , fd - > ino , ic - > ino ) ;
/* TODO: What do we do about it? */
2005-04-17 02:20:36 +04:00
}
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " increased nlink for child \" %s \" (ino #%u) \n " , fd - > name , fd - > ino ) ;
/* Can't free scan_dents so far. We might need them in pass 2 */
2005-04-17 02:20:36 +04:00
}
}
/* Scan plan:
- Scan physical nodes . Build map of inodes / dirents . Allocate inocaches as we go
- Scan directory tree from top down , setting nlink in inocaches
- Scan inocaches for inodes with nlink = = 0
*/
static int jffs2_build_filesystem ( struct jffs2_sb_info * c )
{
int ret ;
int i ;
struct jffs2_inode_cache * ic ;
struct jffs2_full_dirent * fd ;
struct jffs2_full_dirent * dead_fds = NULL ;
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " build FS data structures \n " ) ;
2005-04-17 02:20:36 +04:00
/* First, scan the medium and build all the inode caches with
lists of physical nodes */
2005-02-28 11:21:09 +03:00
c - > flags | = JFFS2_SB_FLAG_SCANNING ;
2005-04-17 02:20:36 +04:00
ret = jffs2_scan_medium ( c ) ;
2005-02-28 11:21:09 +03:00
c - > flags & = ~ JFFS2_SB_FLAG_SCANNING ;
2005-04-17 02:20:36 +04:00
if ( ret )
goto exit ;
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " scanned flash completely \n " ) ;
2005-07-24 19:14:17 +04:00
jffs2_dbg_dump_block_lists_nolock ( c ) ;
2005-04-17 02:20:36 +04:00
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " pass 1 starting \n " ) ;
2005-02-28 11:21:09 +03:00
c - > flags | = JFFS2_SB_FLAG_BUILDING ;
2005-04-17 02:20:36 +04:00
/* Now scan the directory tree, increasing nlink according to every dirent found. */
for_each_inode ( i , c , ic ) {
if ( ic - > scan_dents ) {
jffs2_build_inode_pass1 ( c , ic ) ;
cond_resched ( ) ;
}
}
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " pass 1 complete \n " ) ;
2005-04-17 02:20:36 +04:00
/* Next, scan for inodes with nlink == 0 and remove them. If
they were directories , then decrement the nlink of their
children too , and repeat the scan . As that ' s going to be
a fairly uncommon occurrence , it ' s not so evil to do it this
way . Recursion bad . */
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " pass 2 starting \n " ) ;
2005-04-17 02:20:36 +04:00
for_each_inode ( i , c , ic ) {
if ( ic - > nlink )
continue ;
2005-11-07 14:16:07 +03:00
2005-04-17 02:20:36 +04:00
jffs2_build_remove_unlinked_inode ( c , ic , & dead_fds ) ;
cond_resched ( ) ;
2005-11-07 14:16:07 +03:00
}
2005-04-17 02:20:36 +04:00
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " pass 2a starting \n " ) ;
2005-04-17 02:20:36 +04:00
while ( dead_fds ) {
fd = dead_fds ;
dead_fds = fd - > next ;
ic = jffs2_get_ino_cache ( c , fd - > ino ) ;
if ( ic )
jffs2_build_remove_unlinked_inode ( c , ic , & dead_fds ) ;
jffs2_free_full_dirent ( fd ) ;
}
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " pass 2a complete \n " ) ;
dbg_fsbuild ( " freeing temporary data structures \n " ) ;
2005-11-07 14:16:07 +03:00
2005-04-17 02:20:36 +04:00
/* Finally, we can scan again and free the dirent structs */
for_each_inode ( i , c , ic ) {
while ( ic - > scan_dents ) {
fd = ic - > scan_dents ;
ic - > scan_dents = fd - > next ;
jffs2_free_full_dirent ( fd ) ;
}
ic - > scan_dents = NULL ;
cond_resched ( ) ;
}
2006-05-13 10:09:47 +04:00
jffs2_build_xattr_subsystem ( c ) ;
2005-02-28 11:21:09 +03:00
c - > flags & = ~ JFFS2_SB_FLAG_BUILDING ;
2005-11-07 14:16:07 +03:00
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " FS build complete \n " ) ;
2005-04-17 02:20:36 +04:00
/* Rotate the lists by some number to ensure wear levelling */
jffs2_rotate_lists ( c ) ;
ret = 0 ;
exit :
if ( ret ) {
for_each_inode ( i , c , ic ) {
while ( ic - > scan_dents ) {
fd = ic - > scan_dents ;
ic - > scan_dents = fd - > next ;
jffs2_free_full_dirent ( fd ) ;
}
}
2006-05-13 10:09:47 +04:00
jffs2_clear_xattr_subsystem ( c ) ;
2005-04-17 02:20:36 +04:00
}
return ret ;
}
2005-09-22 15:25:00 +04:00
static void jffs2_build_remove_unlinked_inode ( struct jffs2_sb_info * c ,
struct jffs2_inode_cache * ic ,
struct jffs2_full_dirent * * dead_fds )
2005-04-17 02:20:36 +04:00
{
struct jffs2_raw_node_ref * raw ;
struct jffs2_full_dirent * fd ;
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " removing ino #%u with nlink == zero. \n " , ic - > ino ) ;
2005-11-07 14:16:07 +03:00
2005-04-17 02:20:36 +04:00
raw = ic - > nodes ;
while ( raw ! = ( void * ) ic ) {
struct jffs2_raw_node_ref * next = raw - > next_in_ino ;
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " obsoleting node at 0x%08x \n " , ref_offset ( raw ) ) ;
2005-04-17 02:20:36 +04:00
jffs2_mark_node_obsolete ( c , raw ) ;
raw = next ;
}
if ( ic - > scan_dents ) {
int whinged = 0 ;
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " inode #%u was a directory which may have children... \n " , ic - > ino ) ;
2005-04-17 02:20:36 +04:00
while ( ic - > scan_dents ) {
struct jffs2_inode_cache * child_ic ;
fd = ic - > scan_dents ;
ic - > scan_dents = fd - > next ;
if ( ! fd - > ino ) {
/* It's a deletion dirent. Ignore it */
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " child \" %s \" is a deletion dirent, skipping... \n " , fd - > name ) ;
2005-04-17 02:20:36 +04:00
jffs2_free_full_dirent ( fd ) ;
continue ;
}
2005-09-22 15:25:00 +04:00
if ( ! whinged )
2005-04-17 02:20:36 +04:00
whinged = 1 ;
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " removing child \" %s \" , ino #%u \n " , fd - > name , fd - > ino ) ;
2005-11-07 14:16:07 +03:00
2005-04-17 02:20:36 +04:00
child_ic = jffs2_get_ino_cache ( c , fd - > ino ) ;
if ( ! child_ic ) {
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " cannot remove child \" %s \" , ino #%u, because it doesn't exist \n " ,
fd - > name , fd - > ino ) ;
2005-04-17 02:20:36 +04:00
jffs2_free_full_dirent ( fd ) ;
continue ;
}
2005-11-07 14:16:07 +03:00
/* Reduce nlink of the child. If it's now zero, stick it on the
2005-04-17 02:20:36 +04:00
dead_fds list to be cleaned up later . Else just free the fd */
child_ic - > nlink - - ;
2005-11-07 14:16:07 +03:00
2005-04-17 02:20:36 +04:00
if ( ! child_ic - > nlink ) {
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " inode #%u ( \" %s \" ) has now got zero nlink, adding to dead_fds list. \n " ,
fd - > ino , fd - > name ) ;
2005-04-17 02:20:36 +04:00
fd - > next = * dead_fds ;
* dead_fds = fd ;
} else {
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " inode #%u ( \" %s \" ) has now got nlink %d. Ignoring. \n " ,
fd - > ino , fd - > name , child_ic - > nlink ) ;
2005-04-17 02:20:36 +04:00
jffs2_free_full_dirent ( fd ) ;
}
}
}
/*
2005-11-07 14:16:07 +03:00
We don ' t delete the inocache from the hash list and free it yet .
2005-04-17 02:20:36 +04:00
The erase code will do that , when all the nodes are completely gone .
*/
}
static void jffs2_calc_trigger_levels ( struct jffs2_sb_info * c )
{
uint32_t size ;
/* Deletion should almost _always_ be allowed. We're fairly
buggered once we stop allowing people to delete stuff
because there ' s not enough free space . . . */
c - > resv_blocks_deletion = 2 ;
2005-11-07 14:16:07 +03:00
/* Be conservative about how much space we need before we allow writes.
2005-04-17 02:20:36 +04:00
On top of that which is required for deletia , require an extra 2 %
of the medium to be available , for overhead caused by nodes being
split across blocks , etc . */
size = c - > flash_size / 50 ; /* 2% of flash size */
size + = c - > nr_blocks * 100 ; /* And 100 bytes per eraseblock */
size + = c - > sector_size - 1 ; /* ... and round up */
c - > resv_blocks_write = c - > resv_blocks_deletion + ( size / c - > sector_size ) ;
/* When do we let the GC thread run in the background */
c - > resv_blocks_gctrigger = c - > resv_blocks_write + 1 ;
2005-11-07 14:16:07 +03:00
/* When do we allow garbage collection to merge nodes to make
2005-04-17 02:20:36 +04:00
long - term progress at the expense of short - term space exhaustion ? */
c - > resv_blocks_gcmerge = c - > resv_blocks_deletion + 1 ;
/* When do we allow garbage collection to eat from bad blocks rather
than actually making progress ? */
c - > resv_blocks_gcbad = 0 ; //c->resv_blocks_deletion + 2;
/* If there's less than this amount of dirty space, don't bother
trying to GC to make more space . It ' ll be a fruitless task */
c - > nospc_dirty_size = c - > sector_size + ( c - > flash_size / 100 ) ;
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " JFFS2 trigger levels (size %d KiB, block size %d KiB, %d blocks) \n " ,
c - > flash_size / 1024 , c - > sector_size / 1024 , c - > nr_blocks ) ;
dbg_fsbuild ( " Blocks required to allow deletion: %d (%d KiB) \n " ,
c - > resv_blocks_deletion , c - > resv_blocks_deletion * c - > sector_size / 1024 ) ;
dbg_fsbuild ( " Blocks required to allow writes: %d (%d KiB) \n " ,
c - > resv_blocks_write , c - > resv_blocks_write * c - > sector_size / 1024 ) ;
dbg_fsbuild ( " Blocks required to quiesce GC thread: %d (%d KiB) \n " ,
c - > resv_blocks_gctrigger , c - > resv_blocks_gctrigger * c - > sector_size / 1024 ) ;
dbg_fsbuild ( " Blocks required to allow GC merges: %d (%d KiB) \n " ,
c - > resv_blocks_gcmerge , c - > resv_blocks_gcmerge * c - > sector_size / 1024 ) ;
dbg_fsbuild ( " Blocks required to GC bad blocks: %d (%d KiB) \n " ,
c - > resv_blocks_gcbad , c - > resv_blocks_gcbad * c - > sector_size / 1024 ) ;
dbg_fsbuild ( " Amount of dirty space required to GC: %d bytes \n " ,
c - > nospc_dirty_size ) ;
2005-11-07 14:16:07 +03:00
}
2005-04-17 02:20:36 +04:00
int jffs2_do_mount_fs ( struct jffs2_sb_info * c )
{
2005-09-07 15:22:01 +04:00
int ret ;
2005-04-17 02:20:36 +04:00
int i ;
2005-09-27 17:40:52 +04:00
int size ;
2005-04-17 02:20:36 +04:00
c - > free_size = c - > flash_size ;
c - > nr_blocks = c - > flash_size / c - > sector_size ;
2005-09-27 17:40:52 +04:00
size = sizeof ( struct jffs2_eraseblock ) * c - > nr_blocks ;
2005-07-30 19:29:30 +04:00
# ifndef __ECOS
2005-08-31 17:51:04 +04:00
if ( jffs2_blocks_use_vmalloc ( c ) )
2005-09-27 17:40:52 +04:00
c - > blocks = vmalloc ( size ) ;
2005-04-17 02:20:36 +04:00
else
2005-07-30 19:29:30 +04:00
# endif
2005-09-27 17:40:52 +04:00
c - > blocks = kmalloc ( size , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! c - > blocks )
return - ENOMEM ;
2005-09-27 17:40:52 +04:00
memset ( c - > blocks , 0 , size ) ;
2005-04-17 02:20:36 +04:00
for ( i = 0 ; i < c - > nr_blocks ; i + + ) {
INIT_LIST_HEAD ( & c - > blocks [ i ] . list ) ;
c - > blocks [ i ] . offset = i * c - > sector_size ;
c - > blocks [ i ] . free_size = c - > sector_size ;
}
INIT_LIST_HEAD ( & c - > clean_list ) ;
INIT_LIST_HEAD ( & c - > very_dirty_list ) ;
INIT_LIST_HEAD ( & c - > dirty_list ) ;
INIT_LIST_HEAD ( & c - > erasable_list ) ;
INIT_LIST_HEAD ( & c - > erasing_list ) ;
INIT_LIST_HEAD ( & c - > erase_pending_list ) ;
INIT_LIST_HEAD ( & c - > erasable_pending_wbuf_list ) ;
INIT_LIST_HEAD ( & c - > erase_complete_list ) ;
INIT_LIST_HEAD ( & c - > free_list ) ;
INIT_LIST_HEAD ( & c - > bad_list ) ;
INIT_LIST_HEAD ( & c - > bad_used_list ) ;
c - > highest_ino = 1 ;
2005-09-07 12:35:26 +04:00
c - > summary = NULL ;
2005-09-07 15:22:01 +04:00
ret = jffs2_sum_init ( c ) ;
if ( ret )
2007-01-19 03:20:30 +03:00
goto out_free ;
2005-04-17 02:20:36 +04:00
if ( jffs2_build_filesystem ( c ) ) {
2005-09-22 15:25:00 +04:00
dbg_fsbuild ( " build_fs failed \n " ) ;
2005-04-17 02:20:36 +04:00
jffs2_free_ino_caches ( c ) ;
jffs2_free_raw_node_refs ( c ) ;
2007-01-19 03:20:30 +03:00
ret = - EIO ;
goto out_free ;
2005-04-17 02:20:36 +04:00
}
jffs2_calc_trigger_levels ( c ) ;
return 0 ;
2007-01-19 03:20:30 +03:00
out_free :
# ifndef __ECOS
if ( jffs2_blocks_use_vmalloc ( c ) )
vfree ( c - > blocks ) ;
else
# endif
kfree ( c - > blocks ) ;
return ret ;
2005-04-17 02:20:36 +04:00
}