2005-04-17 02:20:36 +04:00
# include <linux/init.h>
# include <linux/fs.h>
# include <linux/slab.h>
# include <linux/types.h>
# include <linux/fcntl.h>
# include <linux/delay.h>
# include <linux/string.h>
2008-08-13 13:26:01 +04:00
# include <linux/dirent.h>
2005-04-17 02:20:36 +04:00
# include <linux/syscalls.h>
2008-10-16 09:01:40 +04:00
# include <linux/utime.h>
2005-04-17 02:20:36 +04:00
static __initdata char * message ;
static void __init error ( char * x )
{
if ( ! message )
message = x ;
}
/* link hash */
2006-05-15 20:44:03 +04:00
# define N_ALIGN(len) ((((len) + 1) & ~3) + 2)
2005-04-17 02:20:36 +04:00
static __initdata struct hash {
int ino , minor , major ;
2006-06-26 11:28:02 +04:00
mode_t mode ;
2005-04-17 02:20:36 +04:00
struct hash * next ;
2006-05-15 20:44:03 +04:00
char name [ N_ALIGN ( PATH_MAX ) ] ;
2005-04-17 02:20:36 +04:00
} * head [ 32 ] ;
static inline int hash ( int major , int minor , int ino )
{
unsigned long tmp = ino + minor + ( major < < 3 ) ;
tmp + = tmp > > 5 ;
return tmp & 31 ;
}
2006-06-26 11:28:02 +04:00
static char __init * find_link ( int major , int minor , int ino ,
mode_t mode , char * name )
2005-04-17 02:20:36 +04:00
{
struct hash * * p , * q ;
for ( p = head + hash ( major , minor , ino ) ; * p ; p = & ( * p ) - > next ) {
if ( ( * p ) - > ino ! = ino )
continue ;
if ( ( * p ) - > minor ! = minor )
continue ;
if ( ( * p ) - > major ! = major )
continue ;
2006-06-26 11:28:02 +04:00
if ( ( ( * p ) - > mode ^ mode ) & S_IFMT )
continue ;
2005-04-17 02:20:36 +04:00
return ( * p ) - > name ;
}
2008-04-29 11:59:43 +04:00
q = kmalloc ( sizeof ( struct hash ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! q )
panic ( " can't allocate link hash entry " ) ;
q - > major = major ;
2006-06-26 11:28:02 +04:00
q - > minor = minor ;
q - > ino = ino ;
q - > mode = mode ;
2006-05-15 20:44:03 +04:00
strcpy ( q - > name , name ) ;
2005-04-17 02:20:36 +04:00
q - > next = NULL ;
* p = q ;
return NULL ;
}
static void __init free_hash ( void )
{
struct hash * * p , * q ;
for ( p = head ; p < head + 32 ; p + + ) {
while ( * p ) {
q = * p ;
* p = q - > next ;
2008-04-29 11:59:43 +04:00
kfree ( q ) ;
2005-04-17 02:20:36 +04:00
}
}
}
2008-10-16 09:01:40 +04:00
static long __init do_utime ( char __user * filename , time_t mtime )
{
struct timespec t [ 2 ] ;
t [ 0 ] . tv_sec = mtime ;
t [ 0 ] . tv_nsec = 0 ;
t [ 1 ] . tv_sec = mtime ;
t [ 1 ] . tv_nsec = 0 ;
return do_utimes ( AT_FDCWD , filename , t , AT_SYMLINK_NOFOLLOW ) ;
}
static __initdata LIST_HEAD ( dir_list ) ;
struct dir_entry {
struct list_head list ;
char * name ;
time_t mtime ;
} ;
static void __init dir_add ( const char * name , time_t mtime )
{
struct dir_entry * de = kmalloc ( sizeof ( struct dir_entry ) , GFP_KERNEL ) ;
if ( ! de )
panic ( " can't allocate dir_entry buffer " ) ;
INIT_LIST_HEAD ( & de - > list ) ;
de - > name = kstrdup ( name , GFP_KERNEL ) ;
de - > mtime = mtime ;
list_add ( & de - > list , & dir_list ) ;
}
static void __init dir_utime ( void )
{
struct dir_entry * de , * tmp ;
list_for_each_entry_safe ( de , tmp , & dir_list , list ) {
list_del ( & de - > list ) ;
do_utime ( de - > name , de - > mtime ) ;
kfree ( de - > name ) ;
kfree ( de ) ;
}
}
static __initdata time_t mtime ;
2005-04-17 02:20:36 +04:00
/* cpio header parsing */
static __initdata unsigned long ino , major , minor , nlink ;
static __initdata mode_t mode ;
static __initdata unsigned long body_len , name_len ;
static __initdata uid_t uid ;
static __initdata gid_t gid ;
static __initdata unsigned rdev ;
static void __init parse_header ( char * s )
{
unsigned long parsed [ 12 ] ;
char buf [ 9 ] ;
int i ;
buf [ 8 ] = ' \0 ' ;
for ( i = 0 , s + = 6 ; i < 12 ; i + + , s + = 8 ) {
memcpy ( buf , s , 8 ) ;
parsed [ i ] = simple_strtoul ( buf , NULL , 16 ) ;
}
ino = parsed [ 0 ] ;
mode = parsed [ 1 ] ;
uid = parsed [ 2 ] ;
gid = parsed [ 3 ] ;
nlink = parsed [ 4 ] ;
2008-10-16 09:01:40 +04:00
mtime = parsed [ 5 ] ;
2005-04-17 02:20:36 +04:00
body_len = parsed [ 6 ] ;
major = parsed [ 7 ] ;
minor = parsed [ 8 ] ;
rdev = new_encode_dev ( MKDEV ( parsed [ 9 ] , parsed [ 10 ] ) ) ;
name_len = parsed [ 11 ] ;
}
/* FSM */
static __initdata enum state {
Start ,
Collect ,
GotHeader ,
SkipIt ,
GotName ,
CopyFile ,
GotSymlink ,
Reset
} state , next_state ;
static __initdata char * victim ;
static __initdata unsigned count ;
static __initdata loff_t this_header , next_header ;
2007-07-26 20:33:59 +04:00
static inline void __init eat ( unsigned n )
2005-04-17 02:20:36 +04:00
{
victim + = n ;
this_header + = n ;
count - = n ;
}
2008-10-16 09:01:40 +04:00
static __initdata char * vcollected ;
2005-04-17 02:20:36 +04:00
static __initdata char * collected ;
static __initdata int remains ;
static __initdata char * collect ;
static void __init read_into ( char * buf , unsigned size , enum state next )
{
if ( count > = size ) {
collected = victim ;
eat ( size ) ;
state = next ;
} else {
collect = collected = buf ;
remains = size ;
next_state = next ;
state = Collect ;
}
}
static __initdata char * header_buf , * symlink_buf , * name_buf ;
static int __init do_start ( void )
{
read_into ( header_buf , 110 , GotHeader ) ;
return 0 ;
}
static int __init do_collect ( void )
{
unsigned n = remains ;
if ( count < n )
n = count ;
memcpy ( collect , victim , n ) ;
eat ( n ) ;
collect + = n ;
if ( ( remains - = n ) ! = 0 )
return 1 ;
state = next_state ;
return 0 ;
}
static int __init do_header ( void )
{
2006-12-07 07:37:19 +03:00
if ( memcmp ( collected , " 070707 " , 6 ) = = 0 ) {
error ( " incorrect cpio method used: use -H newc option " ) ;
return 1 ;
}
2005-04-17 02:20:36 +04:00
if ( memcmp ( collected , " 070701 " , 6 ) ) {
error ( " no cpio magic " ) ;
return 1 ;
}
parse_header ( collected ) ;
next_header = this_header + N_ALIGN ( name_len ) + body_len ;
next_header = ( next_header + 3 ) & ~ 3 ;
state = SkipIt ;
if ( name_len < = 0 | | name_len > PATH_MAX )
return 0 ;
if ( S_ISLNK ( mode ) ) {
if ( body_len > PATH_MAX )
return 0 ;
collect = collected = symlink_buf ;
remains = N_ALIGN ( name_len ) + body_len ;
next_state = GotSymlink ;
state = Collect ;
return 0 ;
}
if ( S_ISREG ( mode ) | | ! body_len )
read_into ( name_buf , N_ALIGN ( name_len ) , GotName ) ;
return 0 ;
}
static int __init do_skip ( void )
{
if ( this_header + count < next_header ) {
eat ( count ) ;
return 1 ;
} else {
eat ( next_header - this_header ) ;
state = next_state ;
return 0 ;
}
}
static int __init do_reset ( void )
{
while ( count & & * victim = = ' \0 ' )
eat ( 1 ) ;
if ( count & & ( this_header & 3 ) )
error ( " broken padding " ) ;
return 1 ;
}
static int __init maybe_link ( void )
{
if ( nlink > = 2 ) {
2006-06-26 11:28:02 +04:00
char * old = find_link ( major , minor , ino , mode , collected ) ;
2005-04-17 02:20:36 +04:00
if ( old )
return ( sys_link ( old , collected ) < 0 ) ? - 1 : 1 ;
}
return 0 ;
}
2006-06-26 11:28:02 +04:00
static void __init clean_path ( char * path , mode_t mode )
{
struct stat st ;
if ( ! sys_newlstat ( path , & st ) & & ( st . st_mode ^ mode ) & S_IFMT ) {
if ( S_ISDIR ( st . st_mode ) )
sys_rmdir ( path ) ;
else
sys_unlink ( path ) ;
}
}
2005-04-17 02:20:36 +04:00
static __initdata int wfd ;
static int __init do_name ( void )
{
state = SkipIt ;
next_state = Reset ;
if ( strcmp ( collected , " TRAILER!!! " ) = = 0 ) {
free_hash ( ) ;
return 0 ;
}
2006-06-26 11:28:02 +04:00
clean_path ( collected , mode ) ;
2005-04-17 02:20:36 +04:00
if ( S_ISREG ( mode ) ) {
2006-06-26 11:28:02 +04:00
int ml = maybe_link ( ) ;
if ( ml > = 0 ) {
int openflags = O_WRONLY | O_CREAT ;
if ( ml ! = 1 )
openflags | = O_TRUNC ;
wfd = sys_open ( collected , openflags , mode ) ;
2005-04-17 02:20:36 +04:00
if ( wfd > = 0 ) {
sys_fchown ( wfd , uid , gid ) ;
sys_fchmod ( wfd , mode ) ;
2009-04-14 01:40:04 +04:00
if ( body_len )
sys_ftruncate ( wfd , body_len ) ;
2008-10-16 09:01:40 +04:00
vcollected = kstrdup ( collected , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
state = CopyFile ;
}
}
} else if ( S_ISDIR ( mode ) ) {
sys_mkdir ( collected , mode ) ;
sys_chown ( collected , uid , gid ) ;
sys_chmod ( collected , mode ) ;
2008-10-16 09:01:40 +04:00
dir_add ( collected , mtime ) ;
2005-04-17 02:20:36 +04:00
} else if ( S_ISBLK ( mode ) | | S_ISCHR ( mode ) | |
S_ISFIFO ( mode ) | | S_ISSOCK ( mode ) ) {
if ( maybe_link ( ) = = 0 ) {
sys_mknod ( collected , mode , rdev ) ;
sys_chown ( collected , uid , gid ) ;
sys_chmod ( collected , mode ) ;
2008-10-16 09:01:40 +04:00
do_utime ( collected , mtime ) ;
2005-04-17 02:20:36 +04:00
}
}
return 0 ;
}
static int __init do_copy ( void )
{
if ( count > = body_len ) {
sys_write ( wfd , victim , body_len ) ;
sys_close ( wfd ) ;
2008-10-16 09:01:40 +04:00
do_utime ( vcollected , mtime ) ;
kfree ( vcollected ) ;
2005-04-17 02:20:36 +04:00
eat ( body_len ) ;
state = SkipIt ;
return 0 ;
} else {
sys_write ( wfd , victim , count ) ;
body_len - = count ;
eat ( count ) ;
return 1 ;
}
}
static int __init do_symlink ( void )
{
collected [ N_ALIGN ( name_len ) + body_len ] = ' \0 ' ;
2006-06-26 11:28:02 +04:00
clean_path ( collected , 0 ) ;
2005-04-17 02:20:36 +04:00
sys_symlink ( collected + N_ALIGN ( name_len ) , collected ) ;
sys_lchown ( collected , uid , gid ) ;
2008-10-16 09:01:40 +04:00
do_utime ( collected , mtime ) ;
2005-04-17 02:20:36 +04:00
state = SkipIt ;
next_state = Reset ;
return 0 ;
}
static __initdata int ( * actions [ ] ) ( void ) = {
[ Start ] = do_start ,
[ Collect ] = do_collect ,
[ GotHeader ] = do_header ,
[ SkipIt ] = do_skip ,
[ GotName ] = do_name ,
[ CopyFile ] = do_copy ,
[ GotSymlink ] = do_symlink ,
[ Reset ] = do_reset ,
} ;
static int __init write_buffer ( char * buf , unsigned len )
{
count = len ;
victim = buf ;
while ( ! actions [ state ] ( ) )
;
return len - count ;
}
2009-01-05 00:46:17 +03:00
static int __init flush_buffer ( void * bufv , unsigned len )
2005-04-17 02:20:36 +04:00
{
2009-01-05 00:46:17 +03:00
char * buf = ( char * ) bufv ;
2005-04-17 02:20:36 +04:00
int written ;
2009-01-05 00:46:17 +03:00
int origLen = len ;
2005-04-17 02:20:36 +04:00
if ( message )
2009-01-05 00:46:17 +03:00
return - 1 ;
2005-04-17 02:20:36 +04:00
while ( ( written = write_buffer ( buf , len ) ) < len & & ! message ) {
char c = buf [ written ] ;
if ( c = = ' 0 ' ) {
buf + = written ;
len - = written ;
state = Start ;
} else if ( c = = 0 ) {
buf + = written ;
len - = written ;
state = Reset ;
} else
error ( " junk in compressed archive " ) ;
}
2009-01-05 00:46:17 +03:00
return origLen ;
2005-04-17 02:20:36 +04:00
}
2009-01-05 00:46:17 +03:00
static unsigned my_inptr ; /* index of next byte to be processed in inbuf */
2005-04-17 02:20:36 +04:00
2009-01-09 02:14:17 +03:00
# include <linux/decompress/generic.h>
2005-04-17 02:20:36 +04:00
2008-08-13 13:26:01 +04:00
static char * __init unpack_to_rootfs ( char * buf , unsigned len )
2005-04-17 02:20:36 +04:00
{
2009-12-15 00:45:19 +03:00
int written , res ;
2009-01-09 02:14:17 +03:00
decompress_fn decompress ;
2009-01-13 01:24:04 +03:00
const char * compress_name ;
static __initdata char msg_buf [ 64 ] ;
2009-01-09 02:14:17 +03:00
2008-04-29 11:59:43 +04:00
header_buf = kmalloc ( 110 , GFP_KERNEL ) ;
symlink_buf = kmalloc ( PATH_MAX + N_ALIGN ( PATH_MAX ) + 1 , GFP_KERNEL ) ;
name_buf = kmalloc ( N_ALIGN ( PATH_MAX ) , GFP_KERNEL ) ;
2009-01-05 00:46:17 +03:00
if ( ! header_buf | | ! symlink_buf | | ! name_buf )
2005-04-17 02:20:36 +04:00
panic ( " can't allocate buffers " ) ;
2009-01-05 00:46:17 +03:00
2005-04-17 02:20:36 +04:00
state = Start ;
this_header = 0 ;
message = NULL ;
while ( ! message & & len ) {
loff_t saved_offset = this_header ;
if ( * buf = = ' 0 ' & & ! ( this_header & 3 ) ) {
state = Start ;
written = write_buffer ( buf , len ) ;
buf + = written ;
len - = written ;
continue ;
}
if ( ! * buf ) {
buf + + ;
len - - ;
this_header + + ;
continue ;
}
this_header = 0 ;
2009-01-13 01:24:04 +03:00
decompress = decompress_method ( buf , len , & compress_name ) ;
2009-12-15 00:45:19 +03:00
if ( decompress ) {
res = decompress ( buf , len , NULL , flush_buffer , NULL ,
2009-01-09 02:14:17 +03:00
& my_inptr , error ) ;
2009-12-15 00:45:19 +03:00
if ( res )
error ( " decompressor failed " ) ;
} else if ( compress_name ) {
2009-01-13 01:24:04 +03:00
if ( ! message ) {
snprintf ( msg_buf , sizeof msg_buf ,
" compression method %s not configured " ,
compress_name ) ;
message = msg_buf ;
}
2010-04-23 21:18:11 +04:00
} else
error ( " junk in compressed archive " ) ;
2005-04-17 02:20:36 +04:00
if ( state ! = Reset )
2009-01-05 00:46:17 +03:00
error ( " junk in compressed archive " ) ;
this_header = saved_offset + my_inptr ;
buf + = my_inptr ;
len - = my_inptr ;
2005-04-17 02:20:36 +04:00
}
2008-10-16 09:01:40 +04:00
dir_utime ( ) ;
2008-04-29 11:59:43 +04:00
kfree ( name_buf ) ;
kfree ( symlink_buf ) ;
kfree ( header_buf ) ;
2005-04-17 02:20:36 +04:00
return message ;
}
2007-02-10 12:44:33 +03:00
static int __initdata do_retain_initrd ;
static int __init retain_initrd_param ( char * str )
{
if ( * str )
return 0 ;
do_retain_initrd = 1 ;
return 1 ;
}
__setup ( " retain_initrd " , retain_initrd_param ) ;
2005-04-17 02:20:36 +04:00
extern char __initramfs_start [ ] , __initramfs_end [ ] ;
# include <linux/initrd.h>
2006-02-10 12:51:05 +03:00
# include <linux/kexec.h>
2005-09-13 12:25:12 +04:00
static void __init free_initrd ( void )
{
2006-02-10 12:51:05 +03:00
# ifdef CONFIG_KEXEC
unsigned long crashk_start = ( unsigned long ) __va ( crashk_res . start ) ;
unsigned long crashk_end = ( unsigned long ) __va ( crashk_res . end ) ;
2007-02-10 12:44:33 +03:00
# endif
if ( do_retain_initrd )
goto skip ;
2006-02-10 12:51:05 +03:00
2007-02-10 12:44:33 +03:00
# ifdef CONFIG_KEXEC
2006-02-10 12:51:05 +03:00
/*
* If the initrd region is overlapped with crashkernel reserved region ,
* free only memory that is not part of crashkernel region .
*/
if ( initrd_start < crashk_end & & initrd_end > crashk_start ) {
/*
* Initialize initrd memory region since the kexec boot does
* not do .
*/
memset ( ( void * ) initrd_start , 0 , initrd_end - initrd_start ) ;
if ( initrd_start < crashk_start )
free_initrd_mem ( initrd_start , crashk_start ) ;
if ( initrd_end > crashk_end )
free_initrd_mem ( crashk_end , initrd_end ) ;
} else
# endif
free_initrd_mem ( initrd_start , initrd_end ) ;
2007-02-10 12:44:33 +03:00
skip :
2005-09-13 12:25:12 +04:00
initrd_start = 0 ;
initrd_end = 0 ;
}
2009-04-14 01:39:38 +04:00
# ifdef CONFIG_BLK_DEV_RAM
2008-08-13 13:26:01 +04:00
# define BUF_SIZE 1024
static void __init clean_rootfs ( void )
{
int fd ;
void * buf ;
struct linux_dirent64 * dirp ;
2010-03-06 00:42:39 +03:00
int num ;
2008-08-13 13:26:01 +04:00
fd = sys_open ( " / " , O_RDONLY , 0 ) ;
WARN_ON ( fd < 0 ) ;
if ( fd < 0 )
return ;
buf = kzalloc ( BUF_SIZE , GFP_KERNEL ) ;
WARN_ON ( ! buf ) ;
if ( ! buf ) {
sys_close ( fd ) ;
return ;
}
dirp = buf ;
2010-03-06 00:42:39 +03:00
num = sys_getdents64 ( fd , dirp , BUF_SIZE ) ;
while ( num > 0 ) {
while ( num > 0 ) {
2008-08-13 13:26:01 +04:00
struct stat st ;
int ret ;
ret = sys_newlstat ( dirp - > d_name , & st ) ;
WARN_ON_ONCE ( ret ) ;
if ( ! ret ) {
if ( S_ISDIR ( st . st_mode ) )
sys_rmdir ( dirp - > d_name ) ;
else
sys_unlink ( dirp - > d_name ) ;
}
2010-03-06 00:42:39 +03:00
num - = dirp - > d_reclen ;
2008-08-13 13:26:01 +04:00
dirp = ( void * ) dirp + dirp - > d_reclen ;
}
dirp = buf ;
memset ( buf , 0 , BUF_SIZE ) ;
2010-03-06 00:42:39 +03:00
num = sys_getdents64 ( fd , dirp , BUF_SIZE ) ;
2008-08-13 13:26:01 +04:00
}
sys_close ( fd ) ;
kfree ( buf ) ;
}
2009-04-14 01:39:38 +04:00
# endif
2008-08-13 13:26:01 +04:00
2008-03-15 21:53:32 +03:00
static int __init populate_rootfs ( void )
2005-04-17 02:20:36 +04:00
{
char * err = unpack_to_rootfs ( __initramfs_start ,
2008-08-13 13:26:01 +04:00
__initramfs_end - __initramfs_start ) ;
2005-04-17 02:20:36 +04:00
if ( err )
2009-01-14 22:28:35 +03:00
panic ( err ) ; /* Failed to decompress INTERNAL initramfs */
2005-04-17 02:20:36 +04:00
if ( initrd_start ) {
2006-03-25 14:07:49 +03:00
# ifdef CONFIG_BLK_DEV_RAM
2005-04-17 02:20:36 +04:00
int fd ;
2009-05-07 03:03:06 +04:00
printk ( KERN_INFO " Trying to unpack rootfs image as initramfs... \n " ) ;
2005-04-17 02:20:36 +04:00
err = unpack_to_rootfs ( ( char * ) initrd_start ,
2008-08-13 13:26:01 +04:00
initrd_end - initrd_start ) ;
2005-04-17 02:20:36 +04:00
if ( ! err ) {
2005-09-13 12:25:12 +04:00
free_initrd ( ) ;
2006-12-11 23:12:04 +03:00
return 0 ;
2008-08-13 13:26:01 +04:00
} else {
clean_rootfs ( ) ;
unpack_to_rootfs ( __initramfs_start ,
__initramfs_end - __initramfs_start ) ;
2005-04-17 02:20:36 +04:00
}
2009-04-03 03:57:00 +04:00
printk ( KERN_INFO " rootfs image is not initramfs (%s) "
" ; looks like an initrd \n " , err ) ;
2006-03-26 13:37:38 +04:00
fd = sys_open ( " /initrd.image " , O_WRONLY | O_CREAT , 0700 ) ;
2005-04-17 02:20:36 +04:00
if ( fd > = 0 ) {
sys_write ( fd , ( char * ) initrd_start ,
initrd_end - initrd_start ) ;
sys_close ( fd ) ;
2005-09-13 12:25:12 +04:00
free_initrd ( ) ;
2005-04-17 02:20:36 +04:00
}
2006-03-25 14:07:49 +03:00
# else
2009-05-07 03:03:06 +04:00
printk ( KERN_INFO " Unpacking initramfs... \n " ) ;
2006-03-25 14:07:49 +03:00
err = unpack_to_rootfs ( ( char * ) initrd_start ,
2008-08-13 13:26:01 +04:00
initrd_end - initrd_start ) ;
2009-05-07 03:03:06 +04:00
if ( err )
printk ( KERN_EMERG " Initramfs unpacking failed: %s \n " , err ) ;
2006-03-25 14:07:49 +03:00
free_initrd ( ) ;
# endif
2005-04-17 02:20:36 +04:00
}
2006-12-11 23:12:04 +03:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2006-12-11 23:12:04 +03:00
rootfs_initcall ( populate_rootfs ) ;