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>
2020-11-20 05:29:13 +03:00
# include <endian.h>
2020-01-10 19:03:56 +03:00
# include <linux/bootconfig.h>
2021-09-17 13:03:01 +03:00
# define pr_err(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)
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 = ' " ' ;
2021-06-02 11:18:58 +03:00
printf ( " %c%s%c%s " , q , val , q , xbc_node_is_array ( node ) ? " , " : eol ) ;
2020-01-10 19:03:56 +03:00
i + + ;
}
return i ;
}
static void xbc_show_compact_tree ( void )
{
2021-06-02 11:19:07 +03:00
struct xbc_node * node , * cnode = NULL , * vnode ;
2020-01-10 19:03:56 +03:00
int depth = 0 , i ;
node = xbc_root_node ( ) ;
while ( node & & xbc_node_is_key ( node ) ) {
for ( i = 0 ; i < depth ; i + + )
printf ( " \t " ) ;
2021-06-02 11:19:07 +03:00
if ( ! cnode )
cnode = xbc_node_get_child ( node ) ;
2020-01-10 19:03:56 +03:00
while ( cnode & & xbc_node_is_key ( cnode ) & & ! cnode - > next ) {
2021-06-02 11:19:07 +03:00
vnode = xbc_node_get_child ( cnode ) ;
/*
* If @ cnode has value and subkeys , this
* should show it as below .
*
* key ( @ node ) {
* key ( @ cnode ) = value ;
* key ( @ cnode ) {
* subkeys ;
* }
* }
*/
if ( vnode & & xbc_node_is_value ( vnode ) & & vnode - > next )
break ;
2020-01-10 19:03:56 +03:00
printf ( " %s. " , xbc_node_get_data ( node ) ) ;
node = cnode ;
2021-06-02 11:19:07 +03:00
cnode = vnode ;
2020-01-10 19:03:56 +03:00
}
if ( cnode & & xbc_node_is_key ( cnode ) ) {
printf ( " %s { \n " , xbc_node_get_data ( node ) ) ;
depth + + ;
node = cnode ;
2021-06-02 11:19:07 +03:00
cnode = NULL ;
2020-01-10 19:03:56 +03:00
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 ) ;
2021-06-02 11:19:07 +03:00
/*
* If @ node has value and subkeys , continue
* looping on subkeys with same node .
*/
if ( cnode - > next ) {
cnode = xbc_node_get_next ( cnode ) ;
continue ;
}
2020-01-10 19:03:56 +03:00
} else {
printf ( " %s; \n " , xbc_node_get_data ( node ) ) ;
}
2021-06-02 11:19:07 +03:00
cnode = NULL ;
2020-01-10 19:03:56 +03:00
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 ;
2021-06-02 11:19:07 +03:00
if ( depth ) {
depth - - ;
for ( i = 0 ; i < depth ; i + + )
printf ( " \t " ) ;
printf ( " } \n " ) ;
}
2020-01-10 19:03:56 +03:00
}
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 ;
2021-09-04 18:54:38 +03:00
int ret ;
2020-08-10 11:34:51 +03:00
xbc_for_each_key_value ( leaf , val ) {
2021-09-04 18:54:38 +03:00
ret = xbc_node_compose_key ( leaf , key , XBC_KEYLEN_MAX ) ;
if ( ret < 0 ) {
2021-06-02 11:19:07 +03:00
fprintf ( stderr , " Failed to compose key %d \n " , ret ) ;
2020-08-10 11:34:51 +03:00
break ;
2021-06-02 11:19:07 +03:00
}
2020-08-10 11:34:51 +03:00
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
# 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 ;
2021-09-17 13:03:08 +03:00
uint32_t 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
2021-09-17 13:03:08 +03:00
if ( read ( fd , & size , sizeof ( uint32_t ) ) < 0 )
2020-11-19 08:53:22 +03:00
return pr_errno ( " Failed to read size " , - errno ) ;
2020-11-20 05:29:13 +03:00
size = le32toh ( size ) ;
2020-01-10 19:03:56 +03:00
2021-09-17 13:03:08 +03:00
if ( read ( fd , & csum , sizeof ( uint32_t ) ) < 0 )
2020-11-19 08:53:22 +03:00
return pr_errno ( " Failed to read checksum " , - errno ) ;
2020-11-20 05:29:13 +03:00
csum = le32toh ( csum ) ;
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 */
2021-06-02 11:19:34 +03:00
rcsum = xbc_calc_checksum ( * buf , size ) ;
2020-01-10 19:03:56 +03:00
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
}
2021-09-16 09:23:20 +03:00
ret = xbc_init ( * buf , size , & 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 ;
2021-09-16 09:23:20 +03:00
ret = xbc_init ( buf , len , & msg , & pos ) ;
2020-08-10 11:34:41 +03:00
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
{
2020-11-19 08:53:40 +03:00
char * buf , * data , * p ;
size_t total_size ;
2020-11-19 08:53:31 +03:00
struct stat stat ;
2020-11-19 08:53:40 +03:00
const char * msg ;
2021-09-17 13:03:08 +03:00
uint32_t size , csum ;
2020-11-19 08:53:40 +03:00
int pos , pad ;
2020-01-10 19:03:56 +03:00
int ret , fd ;
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 ;
2021-06-02 11:19:34 +03:00
csum = xbc_calc_checksum ( buf , size ) ;
2020-01-10 19:03:56 +03:00
2020-11-19 08:53:40 +03:00
/* Backup the bootconfig data */
data = calloc ( size + BOOTCONFIG_ALIGN +
2021-09-17 13:03:08 +03:00
sizeof ( uint32_t ) + sizeof ( uint32_t ) + BOOTCONFIG_MAGIC_LEN , 1 ) ;
2020-01-10 19:03:56 +03:00
if ( ! data )
return - ENOMEM ;
2020-11-19 08:53:40 +03:00
memcpy ( data , buf , size ) ;
2020-01-10 19:03:56 +03:00
/* Check the data format */
2021-09-16 09:23:20 +03:00
ret = xbc_init ( buf , size , & 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 ) ;
2021-09-16 09:23:29 +03:00
xbc_get_info ( & ret , NULL ) ;
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 */
2021-09-17 13:02:39 +03:00
xbc_exit ( ) ;
2020-01-10 19:03:56 +03:00
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 */
2020-11-19 08:53:31 +03:00
if ( fstat ( fd , & stat ) < 0 ) {
2021-05-08 06:42:16 +03:00
ret = - errno ;
2020-11-19 08:53:31 +03:00
pr_err ( " Failed to get the size of %s \n " , path ) ;
goto out ;
}
2020-11-19 08:53:40 +03:00
/* To align up the total size to BOOTCONFIG_ALIGN, get padding size */
2021-09-17 13:03:08 +03:00
total_size = stat . st_size + size + sizeof ( uint32_t ) * 2 + BOOTCONFIG_MAGIC_LEN ;
2020-11-19 08:53:40 +03:00
pad = ( ( total_size + BOOTCONFIG_ALIGN - 1 ) & ( ~ BOOTCONFIG_ALIGN_MASK ) ) - total_size ;
size + = pad ;
/* Add a footer */
p = data + size ;
2021-09-17 13:03:08 +03:00
* ( uint32_t * ) p = htole32 ( size ) ;
p + = sizeof ( uint32_t ) ;
2020-11-19 08:53:40 +03:00
2021-09-17 13:03:08 +03:00
* ( uint32_t * ) p = htole32 ( csum ) ;
p + = sizeof ( uint32_t ) ;
2020-11-19 08:53:40 +03:00
memcpy ( p , BOOTCONFIG_MAGIC , BOOTCONFIG_MAGIC_LEN ) ;
p + = BOOTCONFIG_MAGIC_LEN ;
total_size = p - data ;
ret = write ( fd , data , total_size ) ;
if ( ret < total_size ) {
2020-11-19 08:53:31 +03:00
if ( ret < 0 )
ret = - errno ;
2020-02-09 16:05:13 +03:00
pr_err ( " Failed to apply a boot config: %d \n " , ret ) ;
2020-11-19 08:53:40 +03:00
if ( ret > = 0 )
goto out_rollback ;
} else
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-11-19 08:53:31 +03:00
out_rollback :
/* Map the partial write to -ENOSPC */
if ( ret > = 0 )
ret = - ENOSPC ;
if ( ftruncate ( fd , stat . st_size ) < 0 ) {
ret = - errno ;
pr_err ( " Failed to rollback the write error: %d \n " , ret ) ;
pr_err ( " The initrd %s may be corrupted. Recommend to rebuild. \n " , path ) ;
}
goto out ;
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
}