[cpio] Allow for construction of parent directories as needed

iPXE allows individual raw files to be automatically wrapped with
suitable CPIO headers and injected into the magic initrd image as
exposed to a booted Linux kernel.  This feature is currently limited
to placing files within directories that already exist in the initrd
filesystem.

Remove this limitation by adding the ability for iPXE to construct
CPIO headers for parent directories as needed, under control of the
"mkdir=<n>" command-line argument.  For example:

  initrd config.ign /usr/share/oem/config.ign mkdir=1

will create CPIO headers for the "/usr/share/oem" directory as well as
for the "/usr/share/oem/config.ign" file itself.

This simplifies the process of booting operating systems such as
Flatcar Linux, which otherwise require the single "config.ign" file to
be manually wrapped up as a CPIO archive solely in order to create the
relevant parent directory entries.

The value <n> may be used to control the number of parent directory
entries that are created.  For example, "mkdir=2" would cause up to
two parent directories to be created (i.e. "/usr/share" and
"/usr/share/oem" in the above example).  A negative value such as
"mkdir=-1" may be used to create all parent directories up to the root
of the tree.

Do not create any parent directory entries by default, since doing so
would potentially cause the modes and ownership information for
existing directories to be overwritten.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
pull/1418/head
Michael Brown 2025-02-24 14:04:06 +00:00
parent e7595fe88d
commit 12ea8c4074
4 changed files with 161 additions and 52 deletions

View File

@ -353,24 +353,35 @@ static size_t bzimage_load_initrd ( struct image *image,
const char *filename = cpio_name ( initrd );
struct cpio_header cpio;
size_t offset;
size_t cpio_len;
size_t pad_len;
size_t len;
unsigned int i;
/* Skip hidden images */
if ( initrd->flags & IMAGE_HIDDEN )
return 0;
/* Create cpio header for non-prebuilt images */
offset = cpio_header ( initrd, &cpio );
/* Determine length of cpio headers for non-prebuilt images */
len = 0;
for ( i = 0 ; ( cpio_len = cpio_header ( initrd, i, &cpio ) ) ; i++ )
len += ( cpio_len + cpio_pad_len ( cpio_len ) );
/* Copy in initrd image body (and cpio header if applicable) */
/* Copy in initrd image body and construct any cpio headers */
if ( address ) {
memmove_user ( address, offset, initrd->data, 0, initrd->len );
if ( offset ) {
memset_user ( address, 0, 0, offset );
copy_to_user ( address, 0, &cpio, sizeof ( cpio ) );
copy_to_user ( address, sizeof ( cpio ), filename,
cpio_name_len ( initrd ) );
memmove_user ( address, len, initrd->data, 0, initrd->len );
memset_user ( address, 0, 0, len );
offset = 0;
for ( i = 0 ; ( cpio_len = cpio_header ( initrd, i, &cpio ) ) ;
i++ ) {
copy_to_user ( address, offset, &cpio,
sizeof ( cpio ) );
copy_to_user ( address, ( offset + sizeof ( cpio ) ),
filename,
( cpio_len - sizeof ( cpio ) ) );
offset += ( cpio_len + cpio_pad_len ( cpio_len ) );
}
assert ( offset == len );
DBGC ( image, "bzImage %p initrd %p [%#08lx,%#08lx,%#08lx)"
"%s%s\n", image, initrd, user_to_phys ( address, 0 ),
user_to_phys ( address, offset ),
@ -379,14 +390,14 @@ static size_t bzimage_load_initrd ( struct image *image,
DBGC2_MD5A ( image, user_to_phys ( address, offset ),
user_to_virt ( address, offset ), initrd->len );
}
offset += initrd->len;
len += initrd->len;
/* Zero-pad to next INITRD_ALIGN boundary */
pad_len = ( ( -offset ) & ( INITRD_ALIGN - 1 ) );
pad_len = ( ( -len ) & ( INITRD_ALIGN - 1 ) );
if ( address )
memset_user ( address, offset, 0, pad_len );
memset_user ( address, len, 0, pad_len );
return offset;
return len;
}
/**

View File

@ -34,13 +34,19 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <string.h>
#include <ipxe/cpio.h>
/** CPIO default file mode */
#define CPIO_DEFAULT_MODE 0644
/** CPIO directory mode */
#define CPIO_DEFAULT_DIR_MODE 0755
/**
* Set field within a CPIO header
*
* @v field Field within CPIO header
* @v value Value to set
*/
void cpio_set_field ( char *field, unsigned long value ) {
static void cpio_set_field ( char *field, unsigned long value ) {
char buf[9];
snprintf ( buf, sizeof ( buf ), "%08lx", value );
@ -48,23 +54,49 @@ void cpio_set_field ( char *field, unsigned long value ) {
}
/**
* Get CPIO image filename
* Get maximum number of CPIO headers (i.e. number of path components)
*
* @v image Image
* @ret len CPIO filename length (0 for no filename)
* @ret max Maximum number of CPIO headers
*/
size_t cpio_name_len ( struct image *image ) {
static unsigned int cpio_max ( struct image *image ) {
const char *name = cpio_name ( image );
char *sep;
size_t len;
unsigned int max = 0;
char c;
/* Check for existence of CPIO filename */
if ( ! name )
return 0;
/* Locate separator (if any) */
sep = strchr ( name, ' ' );
len = ( sep ? ( ( size_t ) ( sep - name ) ) : strlen ( name ) );
/* Count number of path separators */
while ( ( ( c = *(name++) ) ) && ( c != ' ' ) ) {
if ( c == '/' )
max++;
}
return max;
}
/**
* Get CPIO image filename
*
* @v image Image
* @v depth Path depth
* @ret len Filename length
*/
static size_t cpio_name_len ( struct image *image, unsigned int depth ) {
const char *name = cpio_name ( image );
size_t len;
char c;
/* Sanity check */
assert ( name != NULL );
/* Calculate length up to specified path depth */
for ( len = 0 ; ( ( ( c = name[len] ) ) && ( c != ' ' ) ) ; len++ ) {
if ( ( c == '/' ) && ( depth-- == 0 ) )
break;
}
return len;
}
@ -73,55 +105,100 @@ size_t cpio_name_len ( struct image *image ) {
* Parse CPIO image parameters
*
* @v image Image
* @v cpio CPIO header to fill in
* @v mode Mode to fill in
* @v count Number of CPIO headers to fill in
*/
static void cpio_parse_cmdline ( struct image *image,
struct cpio_header *cpio ) {
static void cpio_parse_cmdline ( struct image *image, unsigned int *mode,
unsigned int *count ) {
const char *arg;
char *end;
unsigned int mode;
/* Look for "mode=" */
/* Set default values */
*mode = CPIO_DEFAULT_MODE;
*count = 1;
/* Parse "mode=...", if present */
if ( ( arg = image_argument ( image, "mode=" ) ) ) {
mode = strtoul ( arg, &end, 8 /* Octal for file mode */ );
*mode = strtoul ( arg, &end, 8 /* Octal for file mode */ );
if ( *end && ( *end != ' ' ) ) {
DBGC ( image, "CPIO %p strange \"mode=\" "
"terminator '%c'\n", image, *end );
}
cpio_set_field ( cpio->c_mode, ( 0100000 | mode ) );
}
/* Parse "mkdir=...", if present */
if ( ( arg = image_argument ( image, "mkdir=" ) ) ) {
*count += strtoul ( arg, &end, 10 );
if ( *end && ( *end != ' ' ) ) {
DBGC ( image, "CPIO %p strange \"mkdir=\" "
"terminator '%c'\n", image, *end );
}
}
/* Allow "mkdir=-1" to request creation of full directory tree */
if ( ! *count )
*count = -1U;
}
/**
* Construct CPIO header for image, if applicable
*
* @v image Image
* @v index CPIO header index
* @v cpio CPIO header to fill in
* @ret len Length of magic CPIO header (including filename)
* @ret len Length of CPIO header (including name, excluding NUL)
*/
size_t cpio_header ( struct image *image, struct cpio_header *cpio ) {
size_t cpio_header ( struct image *image, unsigned int index,
struct cpio_header *cpio ) {
const char *name = cpio_name ( image );
unsigned int mode;
unsigned int count;
unsigned int max;
unsigned int depth;
unsigned int i;
size_t name_len;
size_t len;
/* Get filename length */
name_len = cpio_name_len ( image );
/* Parse command line arguments */
cpio_parse_cmdline ( image, &mode, &count );
/* Images with no filename are assumed to already be CPIO archives */
if ( ! name_len )
/* Determine number of CPIO headers to be constructed */
max = cpio_max ( image );
if ( count > max )
count = max;
/* Determine path depth of this CPIO header */
if ( index >= count )
return 0;
depth = ( max - count + index + 1 );
/* Get filename length */
name_len = cpio_name_len ( image, depth );
/* Calculate mode and length */
if ( depth < max ) {
/* Directory */
mode = ( CPIO_MODE_DIR | CPIO_DEFAULT_DIR_MODE );
len = 0;
} else {
/* File */
mode |= CPIO_MODE_FILE;
len = image->len;
}
/* Construct CPIO header */
memset ( cpio, '0', sizeof ( *cpio ) );
memcpy ( cpio->c_magic, CPIO_MAGIC, sizeof ( cpio->c_magic ) );
cpio_set_field ( cpio->c_mode, 0100644 );
cpio_set_field ( cpio->c_mode, mode );
cpio_set_field ( cpio->c_nlink, 1 );
cpio_set_field ( cpio->c_filesize, image->len );
cpio_set_field ( cpio->c_filesize, len );
cpio_set_field ( cpio->c_namesize, ( name_len + 1 /* NUL */ ) );
cpio_parse_cmdline ( image, cpio );
DBGC ( image, "CPIO %s %d/%d \"", image->name, depth, max );
for ( i = 0 ; i < name_len ; i++ )
DBGC ( image, "%c", name[i] );
DBGC ( image, "\"\n" );
DBGC2_HDA ( image, 0, cpio, sizeof ( *cpio ) );
/* Calculate total length */
len = ( ( sizeof ( *cpio ) + name_len + 1 /* NUL */ + CPIO_ALIGN - 1 )
& ~( CPIO_ALIGN - 1 ) );
return len;
return ( sizeof ( *cpio ) + name_len );
}

View File

@ -9,6 +9,7 @@
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <stdint.h>
#include <ipxe/image.h>
/** A CPIO archive header
@ -50,6 +51,12 @@ struct cpio_header {
/** CPIO magic */
#define CPIO_MAGIC "070701"
/** CPIO type for regular files */
#define CPIO_MODE_FILE 0100000
/** CPIO type for directories */
#define CPIO_MODE_DIR 0040000
/** CPIO header length alignment */
#define CPIO_ALIGN 4
@ -67,8 +74,20 @@ cpio_name ( struct image *image ) {
return image->cmdline;
}
extern void cpio_set_field ( char *field, unsigned long value );
extern size_t cpio_name_len ( struct image *image );
extern size_t cpio_header ( struct image *image, struct cpio_header *cpio );
/**
* Get CPIO header zero-padding length
*
* @v len Length of CPIO header (including name, excluding NUL)
* @ret pad_len Padding length
*/
static inline __attribute__ (( always_inline )) size_t
cpio_pad_len ( size_t len ) {
/* Pad by at least one byte (for name's terminating NUL) */
return ( CPIO_ALIGN - ( len % CPIO_ALIGN ) );
}
extern size_t cpio_header ( struct image *image, unsigned int index,
struct cpio_header *cpio );
#endif /* _IPXE_CPIO_H */

View File

@ -245,6 +245,7 @@ static size_t efi_file_read_initrd ( struct efi_file_reader *reader ) {
size_t cpio_len;
size_t name_len;
size_t len;
unsigned int i;
/* Read from file */
len = 0;
@ -263,15 +264,16 @@ static size_t efi_file_read_initrd ( struct efi_file_reader *reader ) {
}
len += efi_file_read_chunk ( reader, UNULL, pad_len );
/* Read CPIO header, if applicable */
cpio_len = cpio_header ( image, &cpio );
if ( cpio_len ) {
name = cpio_name ( image );
name_len = cpio_name_len ( image );
pad_len = ( cpio_len - sizeof ( cpio ) - name_len );
/* Read CPIO header(s), if applicable */
name = cpio_name ( image );
for ( i = 0 ; ( cpio_len = cpio_header ( image, i, &cpio ) );
i++ ) {
name_len = ( cpio_len - sizeof ( cpio ) );
pad_len = cpio_pad_len ( cpio_len );
DBGC ( file, "EFIFILE %s [%#08zx,%#08zx) %s header\n",
efi_file_name ( file ), reader->pos,
( reader->pos + cpio_len ), image->name );
( reader->pos + cpio_len + pad_len ),
image->name );
len += efi_file_read_chunk ( reader,
virt_to_user ( &cpio ),
sizeof ( cpio ) );