blob: 0dcc8c2fc1a38ed0d79011d4dc6fd6bb9d0cb37d [file] [log] [blame]
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <getopt.h>
#ifdef HAVE_ZLIB_H
#include <zlib.h>
#endif
#include "elf.h"
#include "elf_boot.h"
#include "mkelfImage.h"
static struct file_type file_type[] = {
{ "linux-i386", linux_i386_probe, linux_i386_mkelf, linux_i386_usage },
{ "bzImage-i386", bzImage_i386_probe, linux_i386_mkelf, linux_i386_usage },
{ "vmlinux-i386", vmlinux_i386_probe, linux_i386_mkelf, linux_i386_usage },
{ "linux-ia64", linux_ia64_probe, linux_ia64_mkelf, linux_ia64_usage },
};
static const int file_types = sizeof(file_type)/sizeof(file_type[0]);
void die(char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
exit(1);
}
/**************************************************************************
IPCHKSUM - Checksum IP Header
**************************************************************************/
uint16_t ipchksum(const void *data, unsigned long length)
{
unsigned long sum;
unsigned long i;
const uint8_t *ptr;
/* In the most straight forward way possible,
* compute an ip style checksum.
*/
sum = 0;
ptr = data;
for(i = 0; i < length; i++) {
unsigned long value;
value = ptr[i];
if (i & 1) {
value <<= 8;
}
/* Add the new value */
sum += value;
/* Wrap around the carry */
if (sum > 0xFFFF) {
sum = (sum + (sum >> 16)) & 0xFFFF;
}
}
return (~cpu_to_le16(sum)) & 0xFFFF;
}
uint16_t add_ipchksums(unsigned long offset, uint16_t sum, uint16_t new)
{
unsigned long checksum;
sum = ~sum & 0xFFFF;
new = ~new & 0xFFFF;
if (offset & 1) {
/* byte swap the sum if it came from an odd offset
* since the computation is endian independant this
* works.
*/
new = bswap_16(new);
}
checksum = sum + new;
if (checksum > 0xFFFF) {
checksum -= 0xFFFF;
}
return (~checksum) & 0xFFFF;
}
void *xmalloc(size_t size, const char *name)
{
void *buf;
buf = malloc(size);
if (!buf) {
die("Cannot malloc %ld bytes to hold %s: %s\n",
size + 0UL, name, strerror(errno));
}
return buf;
}
void *xrealloc(void *ptr, size_t size, const char *name)
{
void *buf;
buf = realloc(ptr, size);
if (!buf) {
die("Cannot realloc %ld bytes to hold %s: %s\n",
size + 0UL, name, strerror(errno));
}
return buf;
}
char *slurp_file(const char *filename, off_t *r_size)
{
int fd;
char *buf;
off_t size, progress;
ssize_t result;
struct stat stats;
if (!filename) {
*r_size = 0;
return 0;
}
fd = open(filename, O_RDONLY);
if (fd < 0) {
die("Cannot open `%s': %s\n",
filename, strerror(errno));
}
result = fstat(fd, &stats);
if (result < 0) {
die("Cannot stat: %s: %s\n",
filename, strerror(errno));
}
size = stats.st_size;
*r_size = size;
buf = xmalloc(size, filename);
progress = 0;
while(progress < size) {
result = read(fd, buf + progress, size - progress);
if (result < 0) {
if ((errno == EINTR) || (errno == EAGAIN))
continue;
die("read on %s of %ld bytes failed: %s\n",
filename, (size - progress)+ 0UL, strerror(errno));
}
progress += result;
}
result = close(fd);
if (result < 0) {
die("Close of %s failed: %s\n",
filename, strerror(errno));
}
return buf;
}
#if HAVE_ZLIB_H
char *slurp_decompress_file(const char *filename, off_t *r_size)
{
gzFile fp;
int errnum;
const char *msg;
char *buf;
off_t size, allocated;
ssize_t result;
if (!filename) {
*r_size = 0;
return 0;
}
fp = gzopen(filename, "rb");
if (fp == 0) {
msg = gzerror(fp, &errnum);
if (errnum == Z_ERRNO) {
msg = strerror(errno);
}
die("Cannot open `%s': %s\n", filename, msg);
}
size = 0;
allocated = 65536;
buf = xmalloc(allocated, filename);
do {
if (size == allocated) {
allocated <<= 1;
buf = xrealloc(buf, allocated, filename);
}
result = gzread(fp, buf + size, allocated - size);
if (result < 0) {
if ((errno == EINTR) || (errno == EAGAIN))
continue;
msg = gzerror(fp, &errnum);
if (errnum == Z_ERRNO) {
msg = strerror(errno);
}
die ("read on %s of %ld bytes failed: %s\n",
filename, (allocated - size) + 0UL, msg);
}
size += result;
} while(result > 0);
result = gzclose(fp);
if (result != Z_OK) {
msg = gzerror(fp, &errnum);
if (errnum == Z_ERRNO) {
msg = strerror(errno);
}
die ("Close of %s failed: %s\n", filename, msg);
}
*r_size = size;
return buf;
}
#else
char *slurp_decompress_file(const char *filename, off_t *r_size)
{
return slurp_file(filename, r_size);
}
#endif
struct memelfphdr *add_program_headers(struct memelfheader *ehdr, int count)
{
struct memelfphdr *phdr;
int i;
ehdr->e_phnum = count;
ehdr->e_phdr = phdr = xmalloc(count *sizeof(*phdr), "Program headers");
/* Set the default values */
for(i = 0; i < count; i++) {
phdr[i].p_type = PT_LOAD;
phdr[i].p_flags = PF_R | PF_W | PF_X;
phdr[i].p_vaddr = 0;
phdr[i].p_paddr = 0;
phdr[i].p_filesz = 0;
phdr[i].p_memsz = 0;
phdr[i].p_data = 0;
}
return phdr;
}
struct memelfnote *add_notes(struct memelfheader *ehdr, int count)
{
struct memelfnote *notes;
ehdr->e_notenum = count;
ehdr->e_notes = notes = xmalloc(count *sizeof(*notes), "Notes");
memset(notes, 0, count *sizeof(*notes));
return notes;
}
static int sizeof_notes(struct memelfnote *note, int notes)
{
int size;
int i;
size = 0;
for(i = 0; i < notes; i++) {
size += sizeof(Elf_Nhdr);
size += roundup(strlen(note[i].n_name)+1, 4);
size += roundup(note[i].n_descsz, 4);
}
return size;
}
static uint16_t cpu_to_elf16(struct memelfheader *ehdr, uint16_t val)
{
if (ehdr->ei_data == ELFDATA2LSB) {
return cpu_to_le16(val);
}
else if (ehdr->ei_data == ELFDATA2MSB) {
return cpu_to_be16(val);
}
die("Uknown elf layout in cpu_to_elf16");
return 0;
}
static uint32_t cpu_to_elf32(struct memelfheader *ehdr, uint32_t val)
{
if (ehdr->ei_data == ELFDATA2LSB) {
return cpu_to_le32(val);
}
else if (ehdr->ei_data == ELFDATA2MSB) {
return cpu_to_be32(val);
}
die("Uknown elf layout in cpu_to_elf32");
return 0;
}
static uint64_t cpu_to_elf64(struct memelfheader *ehdr, uint64_t val)
{
if (ehdr->ei_data == ELFDATA2LSB) {
return cpu_to_le64(val);
}
else if (ehdr->ei_data == ELFDATA2MSB) {
return cpu_to_be64(val);
}
die("Uknown elf layout in cpu_to_elf64");
return 0;
}
static void serialize_notes(char *buf, struct memelfheader *ehdr)
{
struct Elf_Nhdr hdr;
struct memelfnote *note;
int notes;
size_t size, offset;
int i;
/* Clear the buffer */
note = ehdr->e_notes;
notes = ehdr->e_notenum;
size = sizeof_notes(note, notes);
memset(buf, 0, size);
/* Write the Elf Notes */
offset = 0;
for(i = 0; i < notes; i++) {
/* Compute the note header */
size_t n_namesz;
n_namesz = strlen(note[i].n_name) +1;
hdr.n_namesz = cpu_to_elf32(ehdr, n_namesz);
hdr.n_descsz = cpu_to_elf32(ehdr, note[i].n_descsz);
hdr.n_type = cpu_to_elf32(ehdr, note[i].n_type);
/* Copy the note into the buffer */
memcpy(buf + offset, &hdr, sizeof(hdr));
offset += sizeof(hdr);
memcpy(buf + offset, note[i].n_name, n_namesz);
offset += roundup(n_namesz, 4);
memcpy(buf + offset, note[i].n_desc, note[i].n_descsz);
offset += roundup(note[i].n_descsz, 4);
}
}
static void serialize_ehdr(char *buf, struct memelfheader *ehdr)
{
if (ehdr->ei_class == ELFCLASS32) {
Elf32_Ehdr *hdr = (Elf32_Ehdr *)buf;
hdr->e_ident[EI_MAG0] = ELFMAG0;
hdr->e_ident[EI_MAG1] = ELFMAG1;
hdr->e_ident[EI_MAG2] = ELFMAG2;
hdr->e_ident[EI_MAG3] = ELFMAG3;
hdr->e_ident[EI_CLASS] = ehdr->ei_class;
hdr->e_ident[EI_DATA] = ehdr->ei_data;
hdr->e_ident[EI_VERSION] = EV_CURRENT;
hdr->e_type = cpu_to_elf16(ehdr, ehdr->e_type);
hdr->e_machine = cpu_to_elf16(ehdr, ehdr->e_machine);
hdr->e_version = cpu_to_elf32(ehdr, EV_CURRENT);
hdr->e_entry = cpu_to_elf32(ehdr, ehdr->e_entry);
hdr->e_phoff = cpu_to_elf32(ehdr, sizeof(*hdr));
hdr->e_shoff = cpu_to_elf32(ehdr, 0);
hdr->e_flags = cpu_to_elf32(ehdr, ehdr->e_flags);
hdr->e_ehsize = cpu_to_elf16(ehdr, sizeof(*hdr));
hdr->e_phentsize = cpu_to_elf16(ehdr, sizeof(Elf32_Phdr));
hdr->e_phnum = cpu_to_elf16(ehdr, ehdr->e_phnum);
hdr->e_shentsize = cpu_to_elf16(ehdr, 0);
hdr->e_shnum = cpu_to_elf16(ehdr, 0);
hdr->e_shstrndx = cpu_to_elf16(ehdr, 0);
}
else if (ehdr->ei_class == ELFCLASS64) {
Elf64_Ehdr *hdr = (Elf64_Ehdr *)buf;
hdr->e_ident[EI_MAG0] = ELFMAG0;
hdr->e_ident[EI_MAG1] = ELFMAG1;
hdr->e_ident[EI_MAG2] = ELFMAG2;
hdr->e_ident[EI_MAG3] = ELFMAG3;
hdr->e_ident[EI_CLASS] = ehdr->ei_class;
hdr->e_ident[EI_DATA] = ehdr->ei_data;
hdr->e_ident[EI_VERSION] = EV_CURRENT;
hdr->e_type = cpu_to_elf16(ehdr, ehdr->e_type);
hdr->e_machine = cpu_to_elf16(ehdr, ehdr->e_machine);
hdr->e_version = cpu_to_elf32(ehdr, EV_CURRENT);
hdr->e_entry = cpu_to_elf64(ehdr, ehdr->e_entry);
hdr->e_phoff = cpu_to_elf64(ehdr, sizeof(*hdr));
hdr->e_shoff = cpu_to_elf64(ehdr, 0);
hdr->e_flags = cpu_to_elf32(ehdr, ehdr->e_flags);
hdr->e_ehsize = cpu_to_elf16(ehdr, sizeof(*hdr));
hdr->e_phentsize = cpu_to_elf16(ehdr, sizeof(Elf64_Phdr));
hdr->e_phnum = cpu_to_elf16(ehdr, ehdr->e_phnum);
hdr->e_shentsize = cpu_to_elf16(ehdr, 0);
hdr->e_shnum = cpu_to_elf16(ehdr, 0);
hdr->e_shstrndx = cpu_to_elf16(ehdr, 0);
}
else die("Uknown elf class: %x\n", ehdr->ei_class);
}
static void serialize_phdrs(char *buf, struct memelfheader *ehdr, size_t note_size)
{
int i;
size_t offset, note_offset;
if (ehdr->ei_class == ELFCLASS32) {
Elf32_Phdr *phdr = (Elf32_Phdr *)buf;
note_offset =
sizeof(Elf32_Ehdr) + (sizeof(Elf32_Phdr)*ehdr->e_phnum);
offset = note_offset + note_size;
for(i = 0; i < ehdr->e_phnum; i++) {
struct memelfphdr *hdr = ehdr->e_phdr + i;
phdr[i].p_type = cpu_to_elf32(ehdr, hdr->p_type);
phdr[i].p_offset = cpu_to_elf32(ehdr, offset);
phdr[i].p_vaddr = cpu_to_elf32(ehdr, hdr->p_vaddr);
phdr[i].p_paddr = cpu_to_elf32(ehdr, hdr->p_paddr);
phdr[i].p_filesz = cpu_to_elf32(ehdr, hdr->p_filesz);
phdr[i].p_memsz = cpu_to_elf32(ehdr, hdr->p_memsz);
phdr[i].p_flags = cpu_to_elf32(ehdr, hdr->p_flags);
phdr[i].p_align = cpu_to_elf32(ehdr, 0);
if (phdr[i].p_type == PT_NOTE) {
phdr[i].p_filesz = cpu_to_elf32(ehdr, note_size);
phdr[i].p_memsz = cpu_to_elf32(ehdr, note_size);
phdr[i].p_offset = cpu_to_elf32(ehdr, note_offset);
} else {
offset += hdr->p_filesz;
}
}
}
else if (ehdr->ei_class == ELFCLASS64) {
Elf64_Phdr *phdr = (Elf64_Phdr *)buf;
note_offset =
sizeof(Elf64_Ehdr) + (sizeof(Elf64_Phdr)*ehdr->e_phnum);
offset = note_offset + note_size;
for(i = 0; i < ehdr->e_phnum; i++) {
struct memelfphdr *hdr = ehdr->e_phdr + i;
phdr[i].p_type = cpu_to_elf32(ehdr, hdr->p_type);
phdr[i].p_flags = cpu_to_elf32(ehdr, hdr->p_flags);
phdr[i].p_offset = cpu_to_elf64(ehdr, offset);
phdr[i].p_vaddr = cpu_to_elf64(ehdr, hdr->p_vaddr);
phdr[i].p_paddr = cpu_to_elf64(ehdr, hdr->p_paddr);
phdr[i].p_filesz = cpu_to_elf64(ehdr, hdr->p_filesz);
phdr[i].p_memsz = cpu_to_elf64(ehdr, hdr->p_memsz);
phdr[i].p_align = cpu_to_elf64(ehdr, 0);
if (phdr[i].p_type == PT_NOTE) {
phdr[i].p_filesz = cpu_to_elf64(ehdr, note_size);
phdr[i].p_memsz = cpu_to_elf64(ehdr, note_size);
phdr[i].p_offset = cpu_to_elf64(ehdr, note_offset);
} else {
offset += hdr->p_filesz;
}
}
}
else {
die("Unknwon elf class: %x\n", ehdr->ei_class);
}
}
static void write_buf(int fd, char *buf, size_t size)
{
size_t progress = 0;
ssize_t result;
while(progress < size) {
result = write(fd, buf + progress, size - progress);
if (result < 0) {
if ((errno == EAGAIN) || (errno == EINTR)) {
continue;
}
die ("write of %ld bytes failed: %s\n",
size - progress, strerror(errno));
}
progress += result;
}
}
static void write_elf(struct memelfheader *ehdr, char *output)
{
size_t ehdr_size;
size_t phdr_size;
size_t note_size;
size_t size;
uint16_t checksum;
size_t bytes;
char *buf;
int result, fd;
int i;
/* Prep for adding the checksum */
for(i = 0; i < ehdr->e_notenum; i++) {
if ((memcmp(ehdr->e_notes[i].n_name, "ELFBoot", 8) == 0) &&
(ehdr->e_notes[i].n_type == EIN_PROGRAM_CHECKSUM)) {
ehdr->e_notes[i].n_desc = &checksum;
ehdr->e_notes[i].n_descsz = 2;
}
}
/* Compute the sizes */
ehdr_size = 0;
phdr_size = 0;
note_size = 0;
if (ehdr->e_notenum) {
note_size = sizeof_notes(ehdr->e_notes, ehdr->e_notenum);
}
if (ehdr->ei_class == ELFCLASS32) {
ehdr_size = sizeof(Elf32_Ehdr);
phdr_size = sizeof(Elf32_Phdr) * ehdr->e_phnum;
}
else if (ehdr->ei_class == ELFCLASS64) {
ehdr_size = sizeof(Elf64_Ehdr);
phdr_size = sizeof(Elf64_Phdr) * ehdr->e_phnum;
}
else {
die("Unknown elf class: %x\n", ehdr->ei_class);
}
/* Allocate a buffer to temporarily hold the serialized forms */
size = ehdr_size + phdr_size + note_size;
buf = xmalloc(size, "Elf Headers");
memset(buf, 0, size);
serialize_ehdr(buf, ehdr);
serialize_phdrs(buf + ehdr_size, ehdr, note_size);
/* Compute the checksum... */
checksum = ipchksum(buf, ehdr_size + phdr_size);
bytes = ehdr_size + phdr_size;
for(i = 0; i < ehdr->e_phnum; i++) {
checksum = add_ipchksums(bytes, checksum,
ipchksum(ehdr->e_phdr[i].p_data, ehdr->e_phdr[i].p_filesz));
bytes += ehdr->e_phdr[i].p_memsz;
}
/* Compute the final form of the notes */
serialize_notes(buf + ehdr_size + phdr_size, ehdr);
/* Now write the elf image */
fd = open(output, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IRGRP | S_IROTH);
if (fd < 0) {
die("Cannot open ``%s'':%s\n",
output, strerror(errno));
}
write_buf(fd, buf, size);
for(i = 0; i < ehdr->e_phnum; i++) {
write_buf(fd, ehdr->e_phdr[i].p_data, ehdr->e_phdr[i].p_filesz);
}
result = close(fd);
if (result < 0) {
die("Close on %s failed: %s\n",
output, strerror(errno));
}
}
static void version(void)
{
printf("mkelfImage " VERSION " released " RELEASE_DATE "\n");
}
void usage(void)
{
int i;
version();
printf(
"Usage: mkelfImage [OPTION]... <kernel> <elf_kernel>\n"
"Build an ELF bootable kernel image from a normal kernel image\n"
"\n"
" -h, --help Print this help.\n"
" -v, --version Print the version of kexec.\n"
" --kernel=<filename> Set the kernel to <filename>\n"
" --output=<filename> Output to <filename>\n"
" -t, --type=TYPE Specify the new kernel is of <type>.\n"
"\n"
"Supported kernel types: \n"
);
for(i = 0; i < file_types; i++) {
printf("%s\n", file_type[i].name);
file_type[i].usage();
}
printf("\n");
}
void error(char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
usage();
exit(1);
}
int main(int argc, char **argv)
{
int opt;
int fileind;
char *type, *kernel, *output;
off_t kernel_size;
char *kernel_buf;
int result;
int i;
struct memelfheader hdr;
static const struct option options[] = {
MKELF_OPTIONS
{ 0, 0, 0, 0 },
};
static const char short_options[] = MKELF_OPT_STR;
memset(&hdr, 0, sizeof(hdr));
kernel = 0;
output = 0;
/* Get the default type from the program name */
type = strrchr(argv[0], '/');
if (!type) type = argv[0];
if (memcmp(type, "mkelf-", 6) == 0) {
type = type + 6;
} else {
type = 0;
}
opterr = 0; /* Don't complain about unrecognized options here */
while ((opt = getopt_long(argc, argv, short_options, options, 0)) != -1) {
switch(opt) {
case OPT_HELP:
usage();
return 0;
case OPT_VERSION:
version();
return 0;
case OPT_KERNEL:
kernel = optarg;
break;
case OPT_OUTPUT:
output = optarg;
break;
case OPT_TYPE:
type = optarg;
break;
default:
break;
}
}
fileind = optind;
/* Reset getopt for the next pass */
opterr = 1;
optind = 1;
if (argc - fileind > 0) {
kernel = argv[fileind++];
}
if (argc - fileind > 0) {
output = argv[fileind++];
}
if (!kernel) {
error("No kernel specified!\n");
}
if (!output) {
error("No output file specified!\n");
}
if (argc - fileind > 0) {
error("%d extra options specified!\n", argc - fileind);
}
/* slurp in the input kernel */
kernel_buf = slurp_decompress_file(kernel, &kernel_size);
/* Find/verify the kernel type */
for(i = 0; i < file_types; i++) {
char *reason;
if (type && (strcmp(type, file_type[i].name) != 0)) {
continue;
}
reason = file_type[i].probe(kernel_buf, kernel_size);
if (reason == 0) {
break;
}
if (type) {
die("Not %s: %s\n", type, reason);
}
}
if (i == file_types) {
die("Can not determine the file type of %s\n", kernel);
}
result = file_type[i].mkelf(argc, argv, &hdr, kernel_buf, kernel_size);
if (result < 0) {
die("Cannot create %s result: %d\n", output, result);
}
/* open the output file */
write_elf(&hdr, output);
return 0;
}