summaryrefslogtreecommitdiffstats
path: root/src/arch/x86/image/bzimage.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/arch/x86/image/bzimage.c')
-rw-r--r--src/arch/x86/image/bzimage.c360
1 files changed, 122 insertions, 238 deletions
diff --git a/src/arch/x86/image/bzimage.c b/src/arch/x86/image/bzimage.c
index 2c776147d..16a47fc57 100644
--- a/src/arch/x86/image/bzimage.c
+++ b/src/arch/x86/image/bzimage.c
@@ -32,12 +32,13 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <stdint.h>
#include <stdlib.h>
+#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <realmode.h>
#include <bzimage.h>
-#include <initrd.h>
+#include <ipxe/initrd.h>
#include <ipxe/uaccess.h>
#include <ipxe/image.h>
#include <ipxe/segment.h>
@@ -56,7 +57,7 @@ struct bzimage_context {
/** Real-mode kernel portion load segment address */
unsigned int rm_kernel_seg;
/** Real-mode kernel portion load address */
- userptr_t rm_kernel;
+ void *rm_kernel;
/** Real-mode kernel portion file size */
size_t rm_filesz;
/** Real-mode heap top (offset from rm_kernel) */
@@ -68,7 +69,7 @@ struct bzimage_context {
/** Real-mode kernel portion total memory size */
size_t rm_memsz;
/** Non-real-mode kernel portion load address */
- userptr_t pm_kernel;
+ void *pm_kernel;
/** Non-real-mode kernel portion file and memory size */
size_t pm_sz;
/** Video mode */
@@ -76,14 +77,9 @@ struct bzimage_context {
/** Memory limit */
uint64_t mem_limit;
/** Initrd address */
- physaddr_t ramdisk_image;
+ void *initrd;
/** Initrd size */
- physaddr_t ramdisk_size;
-
- /** Command line magic block */
- struct bzimage_cmdline cmdline_magic;
- /** bzImage header */
- struct bzimage_header bzhdr;
+ physaddr_t initrd_size;
};
/**
@@ -91,35 +87,31 @@ struct bzimage_context {
*
* @v image bzImage file
* @v bzimg bzImage context
- * @v src bzImage to parse
* @ret rc Return status code
*/
static int bzimage_parse_header ( struct image *image,
- struct bzimage_context *bzimg,
- userptr_t src ) {
+ struct bzimage_context *bzimg ) {
+ const struct bzimage_header *bzhdr;
unsigned int syssize;
int is_bzimage;
+ /* Initialise context */
+ memset ( bzimg, 0, sizeof ( *bzimg ) );
+
/* Sanity check */
- if ( image->len < ( BZI_HDR_OFFSET + sizeof ( bzimg->bzhdr ) ) ) {
- DBGC ( image, "bzImage %p too short for kernel header\n",
- image );
+ if ( image->len < ( BZI_HDR_OFFSET + sizeof ( *bzhdr ) ) ) {
+ DBGC ( image, "bzImage %s too short for kernel header\n",
+ image->name );
return -ENOEXEC;
}
-
- /* Read in header structures */
- memset ( bzimg, 0, sizeof ( *bzimg ) );
- copy_from_user ( &bzimg->cmdline_magic, src, BZI_CMDLINE_OFFSET,
- sizeof ( bzimg->cmdline_magic ) );
- copy_from_user ( &bzimg->bzhdr, src, BZI_HDR_OFFSET,
- sizeof ( bzimg->bzhdr ) );
+ bzhdr = ( image->data + BZI_HDR_OFFSET );
/* Calculate size of real-mode portion */
- bzimg->rm_filesz = ( ( ( bzimg->bzhdr.setup_sects ?
- bzimg->bzhdr.setup_sects : 4 ) + 1 ) << 9 );
+ bzimg->rm_filesz = ( ( ( bzhdr->setup_sects ?
+ bzhdr->setup_sects : 4 ) + 1 ) << 9 );
if ( bzimg->rm_filesz > image->len ) {
- DBGC ( image, "bzImage %p too short for %zd byte of setup\n",
- image, bzimg->rm_filesz );
+ DBGC ( image, "bzImage %s too short for %zd byte of setup\n",
+ image->name, bzimg->rm_filesz );
return -ENOEXEC;
}
bzimg->rm_memsz = BZI_ASSUMED_RM_SIZE;
@@ -129,13 +121,14 @@ static int bzimage_parse_header ( struct image *image,
syssize = ( ( bzimg->pm_sz + 15 ) / 16 );
/* Check for signatures and determine version */
- if ( bzimg->bzhdr.boot_flag != BZI_BOOT_FLAG ) {
- DBGC ( image, "bzImage %p missing 55AA signature\n", image );
+ if ( bzhdr->boot_flag != BZI_BOOT_FLAG ) {
+ DBGC ( image, "bzImage %s missing 55AA signature\n",
+ image->name );
return -ENOEXEC;
}
- if ( bzimg->bzhdr.header == BZI_SIGNATURE ) {
+ if ( bzhdr->header == BZI_SIGNATURE ) {
/* 2.00+ */
- bzimg->version = bzimg->bzhdr.version;
+ bzimg->version = bzhdr->version;
} else {
/* Pre-2.00. Check that the syssize field is correct,
* as a guard against accepting arbitrary binary data,
@@ -145,20 +138,21 @@ static int bzimage_parse_header ( struct image *image,
* check this field.
*/
bzimg->version = 0x0100;
- if ( bzimg->bzhdr.syssize != syssize ) {
- DBGC ( image, "bzImage %p bad syssize %x (expected "
- "%x)\n", image, bzimg->bzhdr.syssize, syssize );
+ if ( bzhdr->syssize != syssize ) {
+ DBGC ( image, "bzImage %s bad syssize %x (expected "
+ "%x)\n", image->name, bzhdr->syssize,
+ syssize );
return -ENOEXEC;
}
}
/* Determine image type */
is_bzimage = ( ( bzimg->version >= 0x0200 ) ?
- ( bzimg->bzhdr.loadflags & BZI_LOAD_HIGH ) : 0 );
+ ( bzhdr->loadflags & BZI_LOAD_HIGH ) : 0 );
/* Calculate load address of real-mode portion */
bzimg->rm_kernel_seg = ( is_bzimage ? 0x1000 : 0x9000 );
- bzimg->rm_kernel = real_to_user ( bzimg->rm_kernel_seg, 0 );
+ bzimg->rm_kernel = real_to_virt ( bzimg->rm_kernel_seg, 0 );
/* Allow space for the stack and heap */
bzimg->rm_memsz += BZI_STACK_SIZE;
@@ -169,24 +163,24 @@ static int bzimage_parse_header ( struct image *image,
bzimg->rm_memsz += BZI_CMDLINE_SIZE;
/* Calculate load address of protected-mode portion */
- bzimg->pm_kernel = phys_to_user ( is_bzimage ? BZI_LOAD_HIGH_ADDR
+ bzimg->pm_kernel = phys_to_virt ( is_bzimage ? BZI_LOAD_HIGH_ADDR
: BZI_LOAD_LOW_ADDR );
/* Extract video mode */
- bzimg->vid_mode = bzimg->bzhdr.vid_mode;
+ bzimg->vid_mode = bzhdr->vid_mode;
/* Extract memory limit */
bzimg->mem_limit = ( ( bzimg->version >= 0x0203 ) ?
- bzimg->bzhdr.initrd_addr_max : BZI_INITRD_MAX );
+ bzhdr->initrd_addr_max : BZI_INITRD_MAX );
/* Extract command line size */
bzimg->cmdline_size = ( ( bzimg->version >= 0x0206 ) ?
- bzimg->bzhdr.cmdline_size : BZI_CMDLINE_SIZE );
+ bzhdr->cmdline_size : BZI_CMDLINE_SIZE );
- DBGC ( image, "bzImage %p version %04x RM %#lx+%#zx PM %#lx+%#zx "
- "cmdlen %zd\n", image, bzimg->version,
- user_to_phys ( bzimg->rm_kernel, 0 ), bzimg->rm_filesz,
- user_to_phys ( bzimg->pm_kernel, 0 ), bzimg->pm_sz,
+ DBGC ( image, "bzImage %s version %04x RM %#lx+%#zx PM %#lx+%#zx "
+ "cmdlen %zd\n", image->name, bzimg->version,
+ virt_to_phys ( bzimg->rm_kernel ), bzimg->rm_filesz,
+ virt_to_phys ( bzimg->pm_kernel ), bzimg->pm_sz,
bzimg->cmdline_size );
return 0;
@@ -197,49 +191,44 @@ static int bzimage_parse_header ( struct image *image,
*
* @v image bzImage file
* @v bzimg bzImage context
- * @v dst bzImage to update
*/
static void bzimage_update_header ( struct image *image,
- struct bzimage_context *bzimg,
- userptr_t dst ) {
+ struct bzimage_context *bzimg ) {
+ struct bzimage_header *bzhdr = ( bzimg->rm_kernel + BZI_HDR_OFFSET );
+ struct bzimage_cmdline *cmdline;
/* Set loader type */
if ( bzimg->version >= 0x0200 )
- bzimg->bzhdr.type_of_loader = BZI_LOADER_TYPE_IPXE;
+ bzhdr->type_of_loader = BZI_LOADER_TYPE_IPXE;
/* Set heap end pointer */
if ( bzimg->version >= 0x0201 ) {
- bzimg->bzhdr.heap_end_ptr = ( bzimg->rm_heap - 0x200 );
- bzimg->bzhdr.loadflags |= BZI_CAN_USE_HEAP;
+ bzhdr->heap_end_ptr = ( bzimg->rm_heap - 0x200 );
+ bzhdr->loadflags |= BZI_CAN_USE_HEAP;
}
/* Set command line */
if ( bzimg->version >= 0x0202 ) {
- bzimg->bzhdr.cmd_line_ptr = user_to_phys ( bzimg->rm_kernel,
- bzimg->rm_cmdline );
+ bzhdr->cmd_line_ptr = ( virt_to_phys ( bzimg->rm_kernel )
+ + bzimg->rm_cmdline );
} else {
- bzimg->cmdline_magic.magic = BZI_CMDLINE_MAGIC;
- bzimg->cmdline_magic.offset = bzimg->rm_cmdline;
+ cmdline = ( bzimg->rm_kernel + BZI_CMDLINE_OFFSET );
+ cmdline->magic = BZI_CMDLINE_MAGIC;
+ cmdline->offset = bzimg->rm_cmdline;
if ( bzimg->version >= 0x0200 )
- bzimg->bzhdr.setup_move_size = bzimg->rm_memsz;
+ bzhdr->setup_move_size = bzimg->rm_memsz;
}
/* Set video mode */
- bzimg->bzhdr.vid_mode = bzimg->vid_mode;
+ bzhdr->vid_mode = bzimg->vid_mode;
+ DBGC ( image, "bzImage %s vidmode %d\n",
+ image->name, bzhdr->vid_mode );
/* Set initrd address */
if ( bzimg->version >= 0x0200 ) {
- bzimg->bzhdr.ramdisk_image = bzimg->ramdisk_image;
- bzimg->bzhdr.ramdisk_size = bzimg->ramdisk_size;
+ bzhdr->ramdisk_image = virt_to_phys ( bzimg->initrd );
+ bzhdr->ramdisk_size = bzimg->initrd_size;
}
-
- /* Write out header structures */
- copy_to_user ( dst, BZI_CMDLINE_OFFSET, &bzimg->cmdline_magic,
- sizeof ( bzimg->cmdline_magic ) );
- copy_to_user ( dst, BZI_HDR_OFFSET, &bzimg->bzhdr,
- sizeof ( bzimg->bzhdr ) );
-
- DBGC ( image, "bzImage %p vidmode %d\n", image, bzimg->vid_mode );
}
/**
@@ -270,8 +259,9 @@ static int bzimage_parse_cmdline ( struct image *image,
} else {
bzimg->vid_mode = strtoul ( vga, &end, 0 );
if ( *end ) {
- DBGC ( image, "bzImage %p strange \"vga=\" "
- "terminator '%c'\n", image, *end );
+ DBGC ( image, "bzImage %s strange \"vga=\" "
+ "terminator '%c'\n",
+ image->name, *end );
}
}
if ( sep )
@@ -298,8 +288,8 @@ static int bzimage_parse_cmdline ( struct image *image,
case ' ':
break;
default:
- DBGC ( image, "bzImage %p strange \"mem=\" "
- "terminator '%c'\n", image, *end );
+ DBGC ( image, "bzImage %s strange \"mem=\" "
+ "terminator '%c'\n", image->name, *end );
break;
}
bzimg->mem_limit -= 1;
@@ -317,76 +307,13 @@ static int bzimage_parse_cmdline ( struct image *image,
static void bzimage_set_cmdline ( struct image *image,
struct bzimage_context *bzimg ) {
const char *cmdline = ( image->cmdline ? image->cmdline : "" );
- size_t cmdline_len;
+ char *rm_cmdline;
/* Copy command line down to real-mode portion */
- cmdline_len = ( strlen ( cmdline ) + 1 );
- if ( cmdline_len > bzimg->cmdline_size )
- cmdline_len = bzimg->cmdline_size;
- copy_to_user ( bzimg->rm_kernel, bzimg->rm_cmdline,
- cmdline, cmdline_len );
- DBGC ( image, "bzImage %p command line \"%s\"\n", image, cmdline );
-}
-
-/**
- * Align initrd length
- *
- * @v len Length
- * @ret len Length rounded up to INITRD_ALIGN
- */
-static inline size_t bzimage_align ( size_t len ) {
-
- return ( ( len + INITRD_ALIGN - 1 ) & ~( INITRD_ALIGN - 1 ) );
-}
-
-/**
- * Load initrd
- *
- * @v image bzImage image
- * @v initrd initrd image
- * @v address Address at which to load, or UNULL
- * @ret len Length of loaded image, excluding zero-padding
- */
-static size_t bzimage_load_initrd ( struct image *image,
- struct image *initrd,
- userptr_t address ) {
- const char *filename = cpio_name ( initrd );
- struct cpio_header cpio;
- size_t offset;
- size_t pad_len;
-
- /* Skip hidden images */
- if ( initrd->flags & IMAGE_HIDDEN )
- return 0;
-
- /* Create cpio header for non-prebuilt images */
- offset = cpio_header ( initrd, &cpio );
-
- /* Copy in initrd image body (and cpio header if applicable) */
- 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 ) );
- }
- DBGC ( image, "bzImage %p initrd %p [%#08lx,%#08lx,%#08lx)"
- "%s%s\n", image, initrd, user_to_phys ( address, 0 ),
- user_to_phys ( address, offset ),
- user_to_phys ( address, ( offset + initrd->len ) ),
- ( filename ? " " : "" ), ( filename ? filename : "" ) );
- DBGC2_MD5A ( image, user_to_phys ( address, offset ),
- user_to_virt ( address, offset ), initrd->len );
- }
- offset += initrd->len;
-
- /* Zero-pad to next INITRD_ALIGN boundary */
- pad_len = ( ( -offset ) & ( INITRD_ALIGN - 1 ) );
- if ( address )
- memset_user ( address, offset, 0, pad_len );
-
- return offset;
+ rm_cmdline = ( bzimg->rm_kernel + bzimg->rm_cmdline );
+ snprintf ( rm_cmdline, bzimg->cmdline_size, "%s", cmdline );
+ DBGC ( image, "bzImage %s command line \"%s\"\n",
+ image->name, rm_cmdline );
}
/**
@@ -398,48 +325,52 @@ static size_t bzimage_load_initrd ( struct image *image,
*/
static int bzimage_check_initrds ( struct image *image,
struct bzimage_context *bzimg ) {
- struct image *initrd;
- userptr_t bottom;
- size_t len = 0;
+ struct memmap_region region;
+ physaddr_t min;
+ physaddr_t max;
+ physaddr_t dest;
int rc;
/* Calculate total loaded length of initrds */
- for_each_image ( initrd ) {
-
- /* Calculate length */
- len += bzimage_load_initrd ( image, initrd, UNULL );
- len = bzimage_align ( len );
-
- DBGC ( image, "bzImage %p initrd %p from [%#08lx,%#08lx)%s%s\n",
- image, initrd, user_to_phys ( initrd->data, 0 ),
- user_to_phys ( initrd->data, initrd->len ),
- ( initrd->cmdline ? " " : "" ),
- ( initrd->cmdline ? initrd->cmdline : "" ) );
- DBGC2_MD5A ( image, user_to_phys ( initrd->data, 0 ),
- user_to_virt ( initrd->data, 0 ), initrd->len );
- }
+ bzimg->initrd_size = initrd_len();
- /* Calculate lowest usable address */
- bottom = userptr_add ( bzimg->pm_kernel, bzimg->pm_sz );
+ /* Succeed if there are no initrds */
+ if ( ! bzimg->initrd_size )
+ return 0;
- /* Check that total length fits within space available for
- * reshuffling. This is a conservative check, since CPIO
- * headers are not present during reshuffling, but this
- * doesn't hurt and keeps the code simple.
- */
- if ( ( rc = initrd_reshuffle_check ( len, bottom ) ) != 0 ) {
- DBGC ( image, "bzImage %p failed reshuffle check: %s\n",
- image, strerror ( rc ) );
+ /* Calculate available load region after reshuffling */
+ if ( ( rc = initrd_region ( bzimg->initrd_size, &region ) ) != 0 ) {
+ DBGC ( image, "bzImage %s no region for initrds: %s\n",
+ image->name, strerror ( rc ) );
return rc;
}
- /* Check that total length fits within kernel's memory limit */
- if ( user_to_phys ( bottom, len ) > bzimg->mem_limit ) {
- DBGC ( image, "bzImage %p not enough space for initrds\n",
- image );
+ /* Limit region to avoiding kernel itself */
+ min = virt_to_phys ( bzimg->pm_kernel + bzimg->pm_sz );
+ if ( min < region.min )
+ min = region.min;
+
+ /* Limit region to kernel's memory limit */
+ max = region.max;
+ if ( max > bzimg->mem_limit )
+ max = bzimg->mem_limit;
+
+ /* Calculate installation address */
+ if ( max < ( bzimg->initrd_size - 1 ) ) {
+ DBGC ( image, "bzImage %s not enough space for initrds\n",
+ image->name );
+ return -ENOBUFS;
+ }
+ dest = ( ( max + 1 - bzimg->initrd_size ) & ~( INITRD_ALIGN - 1 ) );
+ if ( dest < min ) {
+ DBGC ( image, "bzImage %s not enough space for initrds\n",
+ image->name );
return -ENOBUFS;
}
+ bzimg->initrd = phys_to_virt ( dest );
+ DBGC ( image, "bzImage %s loading initrds from %#08lx downwards\n",
+ image->name, max );
return 0;
}
@@ -451,65 +382,21 @@ static int bzimage_check_initrds ( struct image *image,
*/
static void bzimage_load_initrds ( struct image *image,
struct bzimage_context *bzimg ) {
- struct image *initrd;
- struct image *highest = NULL;
- struct image *other;
- userptr_t top;
- userptr_t dest;
- size_t offset;
size_t len;
- /* Reshuffle initrds into desired order */
- initrd_reshuffle ( userptr_add ( bzimg->pm_kernel, bzimg->pm_sz ) );
-
- /* Find highest initrd */
- for_each_image ( initrd ) {
- if ( ( highest == NULL ) ||
- ( userptr_sub ( initrd->data, highest->data ) > 0 ) ) {
- highest = initrd;
- }
- }
-
/* Do nothing if there are no initrds */
- if ( ! highest )
+ if ( ! bzimg->initrd )
return;
- /* Find highest usable address */
- top = userptr_add ( highest->data, bzimage_align ( highest->len ) );
- if ( user_to_phys ( top, -1 ) > bzimg->mem_limit ) {
- top = phys_to_user ( ( bzimg->mem_limit + 1 ) &
- ~( INITRD_ALIGN - 1 ) );
- }
- DBGC ( image, "bzImage %p loading initrds from %#08lx downwards\n",
- image, user_to_phys ( top, -1 ) );
-
- /* Load initrds in order */
- for_each_image ( initrd ) {
-
- /* Calculate cumulative length of following
- * initrds (including padding).
- */
- offset = 0;
- for_each_image ( other ) {
- if ( other == initrd )
- offset = 0;
- offset += bzimage_load_initrd ( image, other, UNULL );
- offset = bzimage_align ( offset );
- }
-
- /* Load initrd at this address */
- dest = userptr_add ( top, -offset );
- len = bzimage_load_initrd ( image, initrd, dest );
-
- /* Record initrd location */
- if ( ! bzimg->ramdisk_image )
- bzimg->ramdisk_image = user_to_phys ( dest, 0 );
- bzimg->ramdisk_size = ( user_to_phys ( dest, len ) -
- bzimg->ramdisk_image );
- }
- DBGC ( image, "bzImage %p initrds at [%#08lx,%#08lx)\n",
- image, bzimg->ramdisk_image,
- ( bzimg->ramdisk_image + bzimg->ramdisk_size ) );
+ /* Reshuffle initrds into desired order */
+ initrd_reshuffle();
+
+ /* Load initrds */
+ DBGC ( image, "bzImage %s initrds at [%#08lx,%#08lx)\n",
+ image->name, virt_to_phys ( bzimg->initrd ),
+ ( virt_to_phys ( bzimg->initrd ) + bzimg->initrd_size ) );
+ len = initrd_load_all ( bzimg->initrd );
+ assert ( len == bzimg->initrd_size );
}
/**
@@ -523,21 +410,20 @@ static int bzimage_exec ( struct image *image ) {
int rc;
/* Read and parse header from image */
- if ( ( rc = bzimage_parse_header ( image, &bzimg,
- image->data ) ) != 0 )
+ if ( ( rc = bzimage_parse_header ( image, &bzimg ) ) != 0 )
return rc;
/* Prepare segments */
if ( ( rc = prep_segment ( bzimg.rm_kernel, bzimg.rm_filesz,
bzimg.rm_memsz ) ) != 0 ) {
- DBGC ( image, "bzImage %p could not prepare RM segment: %s\n",
- image, strerror ( rc ) );
+ DBGC ( image, "bzImage %s could not prepare RM segment: %s\n",
+ image->name, strerror ( rc ) );
return rc;
}
if ( ( rc = prep_segment ( bzimg.pm_kernel, bzimg.pm_sz,
bzimg.pm_sz ) ) != 0 ) {
- DBGC ( image, "bzImage %p could not prepare PM segment: %s\n",
- image, strerror ( rc ) );
+ DBGC ( image, "bzImage %s could not prepare PM segment: %s\n",
+ image->name, strerror ( rc ) );
return rc;
}
@@ -553,10 +439,9 @@ static int bzimage_exec ( struct image *image ) {
unregister_image ( image_get ( image ) );
/* Load segments */
- memcpy_user ( bzimg.rm_kernel, 0, image->data,
- 0, bzimg.rm_filesz );
- memcpy_user ( bzimg.pm_kernel, 0, image->data,
- bzimg.rm_filesz, bzimg.pm_sz );
+ memcpy ( bzimg.rm_kernel, image->data, bzimg.rm_filesz );
+ memcpy ( bzimg.pm_kernel, ( image->data + bzimg.rm_filesz ),
+ bzimg.pm_sz );
/* Store command line */
bzimage_set_cmdline ( image, &bzimg );
@@ -570,10 +455,10 @@ static int bzimage_exec ( struct image *image ) {
bzimage_load_initrds ( image, &bzimg );
/* Update kernel header */
- bzimage_update_header ( image, &bzimg, bzimg.rm_kernel );
+ bzimage_update_header ( image, &bzimg );
- DBGC ( image, "bzImage %p jumping to RM kernel at %04x:0000 "
- "(stack %04x:%04zx)\n", image, ( bzimg.rm_kernel_seg + 0x20 ),
+ DBGC ( image, "bzImage %s jumping to RM kernel at %04x:0000 (stack "
+ "%04x:%04zx)\n", image->name, ( bzimg.rm_kernel_seg + 0x20 ),
bzimg.rm_kernel_seg, bzimg.rm_heap );
/* Jump to the kernel */
@@ -609,8 +494,7 @@ int bzimage_probe ( struct image *image ) {
int rc;
/* Read and parse header from image */
- if ( ( rc = bzimage_parse_header ( image, &bzimg,
- image->data ) ) != 0 )
+ if ( ( rc = bzimage_parse_header ( image, &bzimg ) ) != 0 )
return rc;
return 0;