2020-01-10 19:03:56 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Boot config tool for initrd image
*/
# include <stdio.h>
# include <stdlib.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <unistd.h>
# include <string.h>
# include <errno.h>
# include <linux/kernel.h>
# include <linux/bootconfig.h>
2020-08-10 11:34:51 +03:00
static int xbc_show_value ( struct xbc_node * node , bool semicolon )
2020-01-10 19:03:56 +03:00
{
2020-08-10 11:34:51 +03:00
const char * val , * eol ;
2020-06-16 13:14:17 +03:00
char q ;
2020-01-10 19:03:56 +03:00
int i = 0 ;
2020-08-10 11:34:51 +03:00
eol = semicolon ? " ; \n " : " \n " ;
2020-01-10 19:03:56 +03:00
xbc_array_for_each_value ( node , val ) {
2020-06-16 13:14:17 +03:00
if ( strchr ( val , ' " ' ) )
q = ' \' ' ;
else
q = ' " ' ;
2020-08-10 11:34:51 +03:00
printf ( " %c%s%c%s " , q , val , q , node - > next ? " , " : eol ) ;
2020-01-10 19:03:56 +03:00
i + + ;
}
return i ;
}
static void xbc_show_compact_tree ( void )
{
struct xbc_node * node , * cnode ;
int depth = 0 , i ;
node = xbc_root_node ( ) ;
while ( node & & xbc_node_is_key ( node ) ) {
for ( i = 0 ; i < depth ; i + + )
printf ( " \t " ) ;
cnode = xbc_node_get_child ( node ) ;
while ( cnode & & xbc_node_is_key ( cnode ) & & ! cnode - > next ) {
printf ( " %s. " , xbc_node_get_data ( node ) ) ;
node = cnode ;
cnode = xbc_node_get_child ( node ) ;
}
if ( cnode & & xbc_node_is_key ( cnode ) ) {
printf ( " %s { \n " , xbc_node_get_data ( node ) ) ;
depth + + ;
node = cnode ;
continue ;
} else if ( cnode & & xbc_node_is_value ( cnode ) ) {
printf ( " %s = " , xbc_node_get_data ( node ) ) ;
2020-08-10 11:34:51 +03:00
xbc_show_value ( cnode , true ) ;
2020-01-10 19:03:56 +03:00
} else {
printf ( " %s; \n " , xbc_node_get_data ( node ) ) ;
}
if ( node - > next ) {
node = xbc_node_get_next ( node ) ;
continue ;
}
while ( ! node - > next ) {
node = xbc_node_get_parent ( node ) ;
if ( ! node )
return ;
if ( ! xbc_node_get_child ( node ) - > next )
continue ;
depth - - ;
for ( i = 0 ; i < depth ; i + + )
printf ( " \t " ) ;
printf ( " } \n " ) ;
}
node = xbc_node_get_next ( node ) ;
}
}
2020-08-10 11:34:51 +03:00
static void xbc_show_list ( void )
{
char key [ XBC_KEYLEN_MAX ] ;
struct xbc_node * leaf ;
const char * val ;
int ret = 0 ;
xbc_for_each_key_value ( leaf , val ) {
ret = xbc_node_compose_key ( leaf , key , XBC_KEYLEN_MAX ) ;
if ( ret < 0 )
break ;
printf ( " %s = " , key ) ;
if ( ! val | | val [ 0 ] = = ' \0 ' ) {
printf ( " \" \" \n " ) ;
continue ;
}
xbc_show_value ( xbc_node_get_child ( leaf ) , false ) ;
}
}
2020-01-10 19:03:56 +03:00
/* Simple real checksum */
2020-08-10 11:35:01 +03:00
static int checksum ( unsigned char * buf , int len )
2020-01-10 19:03:56 +03:00
{
int i , sum = 0 ;
for ( i = 0 ; i < len ; i + + )
sum + = buf [ i ] ;
return sum ;
}
# define PAGE_SIZE 4096
2020-08-10 11:35:01 +03:00
static int load_xbc_fd ( int fd , char * * buf , int size )
2020-01-10 19:03:56 +03:00
{
int ret ;
* buf = malloc ( size + 1 ) ;
if ( ! * buf )
return - ENOMEM ;
ret = read ( fd , * buf , size ) ;
if ( ret < 0 )
return - errno ;
( * buf ) [ size ] = ' \0 ' ;
return ret ;
}
/* Return the read size or -errno */
2020-08-10 11:35:01 +03:00
static int load_xbc_file ( const char * path , char * * buf )
2020-01-10 19:03:56 +03:00
{
struct stat stat ;
int fd , ret ;
fd = open ( path , O_RDONLY ) ;
if ( fd < 0 )
return - errno ;
ret = fstat ( fd , & stat ) ;
if ( ret < 0 )
return - errno ;
ret = load_xbc_fd ( fd , buf , stat . st_size ) ;
close ( fd ) ;
return ret ;
}
2020-11-19 08:53:22 +03:00
static int pr_errno ( const char * msg , int err )
{
pr_err ( " %s: %d \n " , msg , err ) ;
return err ;
}
2020-08-10 11:35:01 +03:00
static int load_xbc_from_initrd ( int fd , char * * buf )
2020-01-10 19:03:56 +03:00
{
struct stat stat ;
int ret ;
u32 size = 0 , csum = 0 , rcsum ;
2020-02-20 15:18:42 +03:00
char magic [ BOOTCONFIG_MAGIC_LEN ] ;
2020-03-03 14:24:50 +03:00
const char * msg ;
2020-01-10 19:03:56 +03:00
ret = fstat ( fd , & stat ) ;
if ( ret < 0 )
return - errno ;
2020-02-20 15:18:42 +03:00
if ( stat . st_size < 8 + BOOTCONFIG_MAGIC_LEN )
2020-01-10 19:03:56 +03:00
return 0 ;
2020-11-19 08:53:22 +03:00
if ( lseek ( fd , - BOOTCONFIG_MAGIC_LEN , SEEK_END ) < 0 )
return pr_errno ( " Failed to lseek for magic " , - errno ) ;
2020-02-20 15:18:42 +03:00
if ( read ( fd , magic , BOOTCONFIG_MAGIC_LEN ) < 0 )
2020-11-19 08:53:22 +03:00
return pr_errno ( " Failed to read " , - errno ) ;
2020-02-20 15:18:42 +03:00
/* Check the bootconfig magic bytes */
if ( memcmp ( magic , BOOTCONFIG_MAGIC , BOOTCONFIG_MAGIC_LEN ) ! = 0 )
return 0 ;
2020-11-19 08:53:22 +03:00
if ( lseek ( fd , - ( 8 + BOOTCONFIG_MAGIC_LEN ) , SEEK_END ) < 0 )
return pr_errno ( " Failed to lseek for size " , - errno ) ;
2020-01-10 19:03:56 +03:00
if ( read ( fd , & size , sizeof ( u32 ) ) < 0 )
2020-11-19 08:53:22 +03:00
return pr_errno ( " Failed to read size " , - errno ) ;
2020-01-10 19:03:56 +03:00
if ( read ( fd , & csum , sizeof ( u32 ) ) < 0 )
2020-11-19 08:53:22 +03:00
return pr_errno ( " Failed to read checksum " , - errno ) ;
2020-01-10 19:03:56 +03:00
2020-02-20 15:18:42 +03:00
/* Wrong size error */
if ( stat . st_size < size + 8 + BOOTCONFIG_MAGIC_LEN ) {
pr_err ( " bootconfig size is too big \n " ) ;
return - E2BIG ;
}
2020-01-10 19:03:56 +03:00
2020-02-20 15:18:42 +03:00
if ( lseek ( fd , stat . st_size - ( size + 8 + BOOTCONFIG_MAGIC_LEN ) ,
2020-11-19 08:53:22 +03:00
SEEK_SET ) < 0 )
return pr_errno ( " Failed to lseek " , - errno ) ;
2020-01-10 19:03:56 +03:00
ret = load_xbc_fd ( fd , buf , size ) ;
if ( ret < 0 )
return ret ;
2020-02-20 15:18:42 +03:00
/* Wrong Checksum */
2020-01-10 19:03:56 +03:00
rcsum = checksum ( ( unsigned char * ) * buf , size ) ;
if ( csum ! = rcsum ) {
2020-02-09 16:05:13 +03:00
pr_err ( " checksum error: %d != %d \n " , csum , rcsum ) ;
2020-02-20 15:18:42 +03:00
return - EINVAL ;
2020-01-10 19:03:56 +03:00
}
2020-03-03 14:24:50 +03:00
ret = xbc_init ( * buf , & msg , NULL ) ;
2020-02-20 15:18:42 +03:00
/* Wrong data */
2020-03-03 14:24:50 +03:00
if ( ret < 0 ) {
pr_err ( " parse error: %s. \n " , msg ) ;
2020-02-20 15:18:42 +03:00
return ret ;
2020-03-03 14:24:50 +03:00
}
2020-01-10 19:03:56 +03:00
return size ;
}
2020-08-10 11:34:41 +03:00
static void show_xbc_error ( const char * data , const char * msg , int pos )
{
int lin = 1 , col , i ;
if ( pos < 0 ) {
pr_err ( " Error: %s. \n " , msg ) ;
return ;
}
/* Note that pos starts from 0 but lin and col should start from 1. */
col = pos + 1 ;
for ( i = 0 ; i < pos ; i + + ) {
if ( data [ i ] = = ' \n ' ) {
lin + + ;
col = pos - i ;
}
}
pr_err ( " Parse Error: %s at %d:%d \n " , msg , lin , col ) ;
}
static int init_xbc_with_error ( char * buf , int len )
{
char * copy = strdup ( buf ) ;
const char * msg ;
int ret , pos ;
if ( ! copy )
return - ENOMEM ;
ret = xbc_init ( buf , & msg , & pos ) ;
if ( ret < 0 )
show_xbc_error ( copy , msg , pos ) ;
free ( copy ) ;
return ret ;
}
2020-08-10 11:35:01 +03:00
static int show_xbc ( const char * path , bool list )
2020-01-10 19:03:56 +03:00
{
int ret , fd ;
char * buf = NULL ;
2020-08-10 11:34:41 +03:00
struct stat st ;
ret = stat ( path , & st ) ;
if ( ret < 0 ) {
2020-11-19 08:53:22 +03:00
ret = - errno ;
pr_err ( " Failed to stat %s: %d \n " , path , ret ) ;
return ret ;
2020-08-10 11:34:41 +03:00
}
2020-01-10 19:03:56 +03:00
fd = open ( path , O_RDONLY ) ;
if ( fd < 0 ) {
2020-11-19 08:53:22 +03:00
ret = - errno ;
pr_err ( " Failed to open initrd %s: %d \n " , path , ret ) ;
return ret ;
2020-01-10 19:03:56 +03:00
}
ret = load_xbc_from_initrd ( fd , & buf ) ;
2020-08-10 11:34:41 +03:00
close ( fd ) ;
2020-06-16 13:14:25 +03:00
if ( ret < 0 ) {
2020-02-09 16:05:13 +03:00
pr_err ( " Failed to load a boot config from initrd: %d \n " , ret ) ;
2020-06-16 13:14:25 +03:00
goto out ;
}
2020-08-10 11:34:41 +03:00
/* Assume a bootconfig file if it is enough small */
if ( ret = = 0 & & st . st_size < = XBC_DATA_MAX ) {
ret = load_xbc_file ( path , & buf ) ;
if ( ret < 0 ) {
pr_err ( " Failed to load a boot config: %d \n " , ret ) ;
goto out ;
}
if ( init_xbc_with_error ( buf , ret ) < 0 )
goto out ;
}
2020-08-10 11:34:51 +03:00
if ( list )
xbc_show_list ( ) ;
else
xbc_show_compact_tree ( ) ;
2020-06-16 13:14:25 +03:00
ret = 0 ;
out :
2020-01-10 19:03:56 +03:00
free ( buf ) ;
return ret ;
}
2020-08-10 11:35:01 +03:00
static int delete_xbc ( const char * path )
2020-01-10 19:03:56 +03:00
{
struct stat stat ;
int ret = 0 , fd , size ;
char * buf = NULL ;
fd = open ( path , O_RDWR ) ;
if ( fd < 0 ) {
2020-11-19 08:53:22 +03:00
ret = - errno ;
pr_err ( " Failed to open initrd %s: %d \n " , path , ret ) ;
return ret ;
2020-01-10 19:03:56 +03:00
}
size = load_xbc_from_initrd ( fd , & buf ) ;
if ( size < 0 ) {
ret = size ;
2020-02-09 16:05:13 +03:00
pr_err ( " Failed to load a boot config from initrd: %d \n " , ret ) ;
2020-01-10 19:03:56 +03:00
} else if ( size > 0 ) {
ret = fstat ( fd , & stat ) ;
if ( ! ret )
2020-02-20 15:18:42 +03:00
ret = ftruncate ( fd , stat . st_size
- size - 8 - BOOTCONFIG_MAGIC_LEN ) ;
2020-01-10 19:03:56 +03:00
if ( ret )
ret = - errno ;
} /* Ignore if there is no boot config in initrd */
close ( fd ) ;
free ( buf ) ;
return ret ;
}
2020-08-10 11:35:01 +03:00
static int apply_xbc ( const char * path , const char * xbc_path )
2020-01-10 19:03:56 +03:00
{
u32 size , csum ;
char * buf , * data ;
int ret , fd ;
2020-03-03 14:24:50 +03:00
const char * msg ;
int pos ;
2020-01-10 19:03:56 +03:00
ret = load_xbc_file ( xbc_path , & buf ) ;
if ( ret < 0 ) {
2020-02-09 16:05:13 +03:00
pr_err ( " Failed to load %s : %d \n " , xbc_path , ret ) ;
2020-01-10 19:03:56 +03:00
return ret ;
}
size = strlen ( buf ) + 1 ;
csum = checksum ( ( unsigned char * ) buf , size ) ;
/* Prepare xbc_path data */
data = malloc ( size + 8 ) ;
if ( ! data )
return - ENOMEM ;
strcpy ( data , buf ) ;
* ( u32 * ) ( data + size ) = size ;
* ( u32 * ) ( data + size + 4 ) = csum ;
/* Check the data format */
2020-03-03 14:24:50 +03:00
ret = xbc_init ( buf , & msg , & pos ) ;
2020-01-10 19:03:56 +03:00
if ( ret < 0 ) {
2020-03-03 14:24:50 +03:00
show_xbc_error ( data , msg , pos ) ;
2020-01-10 19:03:56 +03:00
free ( data ) ;
free ( buf ) ;
2020-03-03 14:24:50 +03:00
2020-01-10 19:03:56 +03:00
return ret ;
}
printf ( " Apply %s to %s \n " , xbc_path , path ) ;
2020-02-05 16:50:13 +03:00
printf ( " \t Number of nodes: %d \n " , ret ) ;
2020-01-10 19:03:56 +03:00
printf ( " \t Size: %u bytes \n " , ( unsigned int ) size ) ;
printf ( " \t Checksum: %d \n " , ( unsigned int ) csum ) ;
/* TODO: Check the options by schema */
xbc_destroy_all ( ) ;
free ( buf ) ;
/* Remove old boot config if exists */
ret = delete_xbc ( path ) ;
if ( ret < 0 ) {
2020-02-09 16:05:13 +03:00
pr_err ( " Failed to delete previous boot config: %d \n " , ret ) ;
2020-05-07 12:23:36 +03:00
free ( data ) ;
2020-01-10 19:03:56 +03:00
return ret ;
}
/* Apply new one */
fd = open ( path , O_RDWR | O_APPEND ) ;
if ( fd < 0 ) {
2020-11-19 08:53:22 +03:00
ret = - errno ;
pr_err ( " Failed to open %s: %d \n " , path , ret ) ;
2020-05-07 12:23:36 +03:00
free ( data ) ;
2020-11-19 08:53:22 +03:00
return ret ;
2020-01-10 19:03:56 +03:00
}
/* TODO: Ensure the @path is initramfs/initrd image */
ret = write ( fd , data , size + 8 ) ;
if ( ret < 0 ) {
2020-02-09 16:05:13 +03:00
pr_err ( " Failed to apply a boot config: %d \n " , ret ) ;
2020-05-07 12:23:36 +03:00
goto out ;
2020-02-20 15:18:42 +03:00
}
/* Write a magic word of the bootconfig */
ret = write ( fd , BOOTCONFIG_MAGIC , BOOTCONFIG_MAGIC_LEN ) ;
if ( ret < 0 ) {
pr_err ( " Failed to apply a boot config magic: %d \n " , ret ) ;
2020-05-07 12:23:36 +03:00
goto out ;
2020-01-10 19:03:56 +03:00
}
2020-05-08 18:07:56 +03:00
ret = 0 ;
2020-05-07 12:23:36 +03:00
out :
2020-01-10 19:03:56 +03:00
close ( fd ) ;
free ( data ) ;
2020-05-07 12:23:36 +03:00
return ret ;
2020-01-10 19:03:56 +03:00
}
2020-08-10 11:35:01 +03:00
static int usage ( void )
2020-01-10 19:03:56 +03:00
{
printf ( " Usage: bootconfig [OPTIONS] <INITRD> \n "
2020-08-10 11:34:41 +03:00
" Or bootconfig <CONFIG> \n "
2020-01-10 19:03:56 +03:00
" Apply, delete or show boot config to initrd. \n "
" Options: \n "
" -a <config>: Apply boot config to initrd \n "
2020-08-10 11:34:51 +03:00
" -d : Delete boot config file from initrd \n "
" -l : list boot config in initrd or file \n \n "
2020-08-10 11:34:41 +03:00
" If no option is given, show the bootconfig in the given file. \n " ) ;
2020-01-10 19:03:56 +03:00
return - 1 ;
}
int main ( int argc , char * * argv )
{
char * path = NULL ;
char * apply = NULL ;
2020-08-10 11:34:51 +03:00
bool delete = false , list = false ;
2020-01-10 19:03:56 +03:00
int opt ;
2020-08-10 11:34:51 +03:00
while ( ( opt = getopt ( argc , argv , " hda:l " ) ) ! = - 1 ) {
2020-01-10 19:03:56 +03:00
switch ( opt ) {
case ' d ' :
delete = true ;
break ;
case ' a ' :
apply = optarg ;
break ;
2020-08-10 11:34:51 +03:00
case ' l ' :
list = true ;
break ;
2020-01-10 19:03:56 +03:00
case ' h ' :
default :
return usage ( ) ;
}
}
2020-08-10 11:34:51 +03:00
if ( ( apply & & delete ) | | ( delete & & list ) | | ( apply & & list ) ) {
pr_err ( " Error: You can give one of -a, -d or -l at once. \n " ) ;
2020-01-10 19:03:56 +03:00
return usage ( ) ;
}
if ( optind > = argc ) {
2020-02-09 16:05:13 +03:00
pr_err ( " Error: No initrd is specified. \n " ) ;
2020-01-10 19:03:56 +03:00
return usage ( ) ;
}
path = argv [ optind ] ;
if ( apply )
return apply_xbc ( path , apply ) ;
else if ( delete )
return delete_xbc ( path ) ;
2020-08-10 11:34:51 +03:00
return show_xbc ( path , list ) ;
2020-01-10 19:03:56 +03:00
}