diff --git a/zipl/boot/stage3.h b/zipl/boot/stage3.h index a11257e8..9ec33dfa 100644 --- a/zipl/boot/stage3.h +++ b/zipl/boot/stage3.h @@ -36,8 +36,10 @@ struct stage3_parms { unsigned long long flags; /* flags (e.g. STAGE3_FLAG_KDUMP) */ unsigned long long image_len; /* length of kernel */ unsigned long long image_addr; /* target address of kernel */ + unsigned long long envblk_addr; /* address of environment block */ + unsigned long long envblk_len; /* size of environment block */ }; -STATIC_ASSERT(sizeof(struct stage3_parms) == 8 * 8) +STATIC_ASSERT(sizeof(struct stage3_parms) == 8 * 10) extern struct stage3_parms _stage3_parms; extern void kdump_stage3(); diff --git a/zipl/include/boot.h b/zipl/include/boot.h index 35cb981b..b5e3b7a2 100644 --- a/zipl/include/boot.h +++ b/zipl/include/boot.h @@ -296,7 +296,8 @@ int boot_get_eckd_stage2(void** data, size_t* size, struct job_data* job); int boot_get_stage3_parms(void **buffer, size_t *bytecount, address_t parm_addr, address_t initrd_addr, size_t initrd_len, address_t entry, int extra_parm, uint64_t flags, - address_t image_addr, size_t image_len); + address_t image_addr, size_t image_len, + address_t envblk_addr, size_t envblk_len); int boot_get_tape_ipl(void** data, size_t* size, address_t parm_addr, address_t initrd_addr, address_t image_addr); int boot_get_tape_dump(void** data, size_t* size, uint64_t mem); diff --git a/zipl/include/bootmap.h b/zipl/include/bootmap.h index f49e394d..e3ea3e10 100644 --- a/zipl/include/bootmap.h +++ b/zipl/include/bootmap.h @@ -17,10 +17,20 @@ #include "disk.h" #include "job.h" #include "zipl.h" +#include "stddef.h" + +#define BOOTMAP_HEADER_VERSION 1 #define SIGNATURE_MAGIC "~Module signature appended~\n" #define PKCS7_FORMAT 0x01 +struct bootmap_header { + char header_text[48]; + u64 version; + u64 envblk_offset; + char reserved[448]; +}; + struct signature_header { uint8_t format; uint8_t reserved[3]; @@ -50,6 +60,9 @@ struct file_signature { #define PKEY_ID_PKCS7 0x02 +int bootmap_header_init(int fd); +int bootmap_header_read(int fd, struct bootmap_header *bh); +int bootmap_header_write(int fd, struct bootmap_header *bh); int bootmap_create(struct job_data* job, disk_blockptr_t* program_table, disk_blockptr_t *scsi_dump_sb_blockptr, disk_blockptr_t** stage2_list, blocknum_t* stage2_count, diff --git a/zipl/include/envblk.h b/zipl/include/envblk.h new file mode 100644 index 00000000..4f552413 --- /dev/null +++ b/zipl/include/envblk.h @@ -0,0 +1,34 @@ +/* + * s390-tools/zipl/include/bootmap.h + * Functions to handle environment block. + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + * + */ +#include "boot/s390.h" /* for PAGE_SIZE */ + +#define ZIPL_ENVBLK_SIGNATURE "# zIPL Environment Block\n" +#define ENVBLK_DEFAULT_IMPORT_SOURCE "/etc/ziplenv" +/* + * The following limit can be increased. It requires modifications + * of the code, which is responsible for handling environment in + * stage3, which will increase its memory footprint. + */ +#define ENVBLK_MAX_LINES (PAGE_SIZE / 8 /* sizeof(struct env_hash_entry */) + +#define ENVBLK_MAX_IMPORT_SIZE(envblk_size) \ +(envblk_size - sizeof(ZIPL_ENVBLK_SIGNATURE) + 1) + +int envblk_offset_get(int fd, off_t *off); +int envblk_offset_set(int fd, off_t off); +int envblk_size_get(int fd, int *size); +int envblk_check_name(char *name, int len); +int envblk_import(char *from, char *to, int size); +char *envblk_next_line(char *s, const char *end); +int envblk_scan(char *envblk, unsigned int envblk_size, + void (*actor)(char *name)); +void envblk_print(char *envblk, unsigned int envblk_size); +void envblk_create_blank(char *envblk, int envblk_len); diff --git a/zipl/include/job.h b/zipl/include/job.h index e04a7c1e..7beea8d6 100644 --- a/zipl/include/job.h +++ b/zipl/include/job.h @@ -46,9 +46,15 @@ struct job_ipl_data { address_t image_addr; address_t parm_addr; address_t ramdisk_addr; + address_t envblk_addr; int is_kdump; }; +struct job_envblk_data { + int size; + char *buf; +}; + struct job_segment_data { char* segment; address_t segment_addr; @@ -108,6 +114,7 @@ struct job_menu_data { struct job_data { enum job_id id; struct job_target_data target; + struct job_envblk_data envblk; char* name; union { struct job_ipl_data ipl; diff --git a/zipl/man/zipl.8.in b/zipl/man/zipl.8.in index 70e61343..3a605248 100644 --- a/zipl/man/zipl.8.in +++ b/zipl/man/zipl.8.in @@ -138,6 +138,11 @@ the target directory. Supported devices are DASD and SCSI disks. It is not possible to specify both this parameter and the name of a menu or configuration section on the command line at the same time. +.TP +.BR "\-\-environment=" +Import zIPL environment from specified . If none is supplied, +it will be imported from "/etc/ziplenv". + .TP .BR "\-\-targetbase=" Install the actual boot loader on the device node specified by BASE DEVICE. diff --git a/zipl/src/Makefile b/zipl/src/Makefile index be3c1ef1..949e73e7 100644 --- a/zipl/src/Makefile +++ b/zipl/src/Makefile @@ -10,7 +10,7 @@ ALL_LDFLAGS += -Wl,-z,noexecstack $(NO_PIE_LDFLAGS) libs = $(rootdir)/libutil/libutil.a objects = misc.o error.o scan.o job.o boot.o bootmap.o disk.o \ - install.o zipl.o $(rootdir)/zipl/boot/data.o + bootmap_header.o envblk.o install.o zipl.o $(rootdir)/zipl/boot/data.o zipl_helpers = $(basename $(wildcard zipl_helper.*.c)) chreipl_helpers = $(subst zipl_,chreipl_, $(zipl_helpers)) diff --git a/zipl/src/boot.c b/zipl/src/boot.c index 281b5b2c..6947f8e6 100644 --- a/zipl/src/boot.c +++ b/zipl/src/boot.c @@ -82,7 +82,8 @@ int boot_get_stage3_parms(void **buffer, size_t *bytecount, address_t parm_addr, address_t initrd_addr, size_t initrd_len, address_t entry, int extra_parm, uint64_t flags, - address_t image_addr, size_t image_len) + address_t image_addr, size_t image_len, + address_t envblk_addr, size_t envblk_len) { struct stage3_parms params; void* data; @@ -106,6 +107,8 @@ boot_get_stage3_parms(void **buffer, size_t *bytecount, address_t parm_addr, params.flags = flags; params.image_len = (uint64_t) image_len; params.image_addr = (uint64_t) image_addr; + params.envblk_addr = (uint64_t) envblk_addr; + params.envblk_len = (uint64_t) envblk_len; /* Initialize buffer */ memcpy(data, ¶ms, sizeof(params)); *buffer = data; diff --git a/zipl/src/bootmap.c b/zipl/src/bootmap.c index 8eb31dca..14cecc6c 100644 --- a/zipl/src/bootmap.c +++ b/zipl/src/bootmap.c @@ -27,19 +27,15 @@ #include "boot.h" #include "bootmap.h" +#include "envblk.h" #include "disk.h" #include "error.h" #include "install.h" #include "misc.h" -/* Header text of the bootmap file */ -static const char header_text[] = "zSeries bootmap file\n" - "created by zIPL\n"; - /* Pointer to dedicated empty block in bootmap. */ disk_blockptr_t empty_block; - /* Get size of a bootmap block pointer for disk with given INFO. */ static int get_blockptr_size(struct disk_info* info) @@ -522,8 +518,9 @@ check_remaining_filesize(size_t filesize, size_t signature_size, } } -static int -add_ipl_program(int fd, struct job_ipl_data* ipl, disk_blockptr_t* program, +static int add_ipl_program(int fd, char *filename, + bool add_envblk, struct job_envblk_data *envblk, + struct job_ipl_data *ipl, disk_blockptr_t *program, int verbose, int add_files, component_header_type type, struct disk_info* info, struct job_target_data* target, int is_secure) @@ -533,12 +530,13 @@ add_ipl_program(int fd, struct job_ipl_data* ipl, disk_blockptr_t* program, size_t ramdisk_size, image_size; bool secure_boot_supported; size_t stage3_params_size; - const char *comp_name[10]; + const char *comp_name[11]; size_t signature_size; int offset; uint64_t flags = 0; void *stage3_params; struct stat stats; + off_t envblk_off; void *signature; int comp_nr = 0; void *table; @@ -676,7 +674,8 @@ add_ipl_program(int fd, struct job_ipl_data* ipl, disk_blockptr_t* program, ipl->is_kdump ? IMAGE_ENTRY_KDUMP : IMAGE_ENTRY, (info->type == disk_type_scsi) ? 0 : 1, - flags, ipl->image_addr, image_size); + flags, ipl->image_addr, image_size, + ipl->envblk_addr, envblk->size); if (rc) { free(table); return rc; @@ -769,8 +768,7 @@ add_ipl_program(int fd, struct job_ipl_data* ipl, disk_blockptr_t* program, comp_name[comp_nr] = "parmline"; comp_nr++; } - - /* finally add ramdisk */ + /* add ramdisk */ if (ipl->ramdisk != NULL) { signature_size = extract_signature(ipl->ramdisk, &signature, &sig_head); @@ -815,6 +813,65 @@ add_ipl_program(int fd, struct job_ipl_data* ipl, disk_blockptr_t* program, comp_name[comp_nr] = "initial ramdisk"; comp_nr++; } + if (add_envblk == true) { + /* + * finally add environment block + */ + rc = envblk_offset_get(fd, &envblk_off); + if (rc) { + free(table); + return rc; + } + if (envblk_off == 0) { + /* + * write with fs_block_size alignment to make sure that the + * logical environment block will get to single file system + * block + */ + rc = add_component_buffer_align(fd, + envblk->buf, envblk->size, + (component_data)ipl->envblk_addr, + VOID_ADD(table, offset), + info, &comp_loc[comp_nr], + component_load, + info->fs_block_size, + &envblk_off); + if (rc) { + error_text("Could not add environment block"); + free(table); + return rc; + } + assert(envblk_off % info->fs_block_size == 0); + /* + * store environment block location + * in the bootmap header + */ + rc = envblk_offset_set(fd, envblk_off); + if (rc) { + error_text("Could not store environment block location"); + free(table); + return rc; + } + } else { + struct file_range reg; + + reg.offset = envblk_off; + reg.len = envblk->size; + rc = add_component_file_range(fd, filename, ®, + ipl->envblk_addr, 0, + VOID_ADD(table, offset), + 0, info, target, + &comp_loc[comp_nr]); + if (rc) { + error_text("Could not add environment block"); + free(table); + return rc; + } + } + offset += sizeof(struct component_entry); + comp_name[comp_nr] = "environment blk"; + comp_nr++; + } if (verbose) print_components(comp_name, comp_loc, comp_nr); /* Terminate component table */ @@ -962,7 +1019,7 @@ add_dump_program(int fd, struct job_dump_data* dump, if (rc) return rc; ipl.parm_addr = dump->parm_addr; - return add_ipl_program(fd, &ipl, program, verbose, 1, + return add_ipl_program(fd, NULL, false, NULL, &ipl, program, verbose, 1, type, info, target, SECURE_BOOT_DISABLED); } @@ -970,8 +1027,8 @@ add_dump_program(int fd, struct job_dump_data* dump, /* Build a program table from job data and set pointer to program table * block upon success. */ static int -build_program_table(int fd, struct job_data* job, disk_blockptr_t* pointer, - struct disk_info* info) +build_program_table(int fd, char *filename, struct job_data *job, + disk_blockptr_t *pointer, struct disk_info *info) { disk_blockptr_t* table; int entries, component_header; @@ -985,6 +1042,7 @@ build_program_table(int fd, struct job_data* job, disk_blockptr_t* pointer, entries); if (table == NULL) return -1; + memset((void *) table, 0, sizeof(disk_blockptr_t) * entries); /* Add programs */ switch (job->id) { @@ -998,8 +1056,9 @@ build_program_table(int fd, struct job_data* job, disk_blockptr_t* pointer, component_header = component_header_dump; else component_header = component_header_ipl; - rc = add_ipl_program(fd, &job->data.ipl, &table[0], - verbose || job->command_line, + rc = add_ipl_program(fd, filename, + true, &job->envblk, &job->data.ipl, + &table[0], verbose || job->command_line, job->add_files, component_header, info, &job->target, job->is_secure); break; @@ -1052,7 +1111,8 @@ build_program_table(int fd, struct job_data* job, disk_blockptr_t* pointer, else is_secure = job->data.menu.entry[i].is_secure; - rc = add_ipl_program(fd, + rc = add_ipl_program(fd, filename, + true, &job->envblk, &job->data.menu.entry[i].data.ipl, &table[job->data.menu.entry[i].pos], verbose || job->command_line, @@ -1086,6 +1146,9 @@ build_program_table(int fd, struct job_data* job, disk_blockptr_t* pointer, rc = -1; break; } + if (job->envblk.buf && verbose) + envblk_print(job->envblk.buf, job->envblk.size); + if (rc == 0) { /* Add program table block */ rc = add_program_table(fd, table, entries, pointer, info); @@ -1244,9 +1307,9 @@ bootmap_create(struct job_data *job, disk_blockptr_t *program_table, scsi_sb.dump_size = unused_size; } - /* Write bootmap header */ - if (misc_write(fd, header_text, sizeof(header_text))) { - error_text("Could not write to file '%s'", filename); + /* Initialize bootmap header */ + if (bootmap_header_init(fd)) { + error_text("Could not init bootmap header at '%s'", filename); goto out_misc_free_temp_dev; } /* Write empty block to be read in place of holes in files */ @@ -1255,7 +1318,7 @@ bootmap_create(struct job_data *job, disk_blockptr_t *program_table, goto out_misc_free_temp_dev; } /* Build program table */ - if (build_program_table(fd, job, program_table, info)) + if (build_program_table(fd, filename, job, program_table, info)) goto out_misc_free_temp_dev; if (job->id == job_dump_partition) { scsi_sb.magic = SCSI_DUMP_SB_MAGIC; diff --git a/zipl/src/bootmap_header.c b/zipl/src/bootmap_header.c new file mode 100644 index 00000000..84dec4a9 --- /dev/null +++ b/zipl/src/bootmap_header.c @@ -0,0 +1,64 @@ +/* + * zipl - zSeries Initial Program Loader tool + * + * Functions to manipulate with bootmap header + * + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include + +#include "bootmap.h" +#include "error.h" +#include "misc.h" + +int bootmap_header_init(int fd) +{ + struct bootmap_header bh = { + .header_text = + "zSeries bootmap file\n" + "created by zIPL\n", + .version = BOOTMAP_HEADER_VERSION + }; + + return misc_write(fd, &bh, sizeof(bh)); +} + +static int bootmap_header_access(int fd, struct bootmap_header *bh, int read) +{ + off_t cur_off; + int ret; + + cur_off = lseek(fd, 0, SEEK_CUR); + if (cur_off == -1) { + error_reason(strerror(errno)); + return -1; + } + if (lseek(fd, 0, SEEK_SET) == -1) { + error_reason(strerror(errno)); + ret = -1; + goto out; + } + if (read) + ret = misc_read(fd, bh, sizeof(*bh)); + else + ret = misc_write(fd, bh, sizeof(*bh)); +out: + if (lseek(fd, cur_off, SEEK_SET) == -1) + return -1; + return ret; +} + +int bootmap_header_read(int fd, struct bootmap_header *bh) +{ + return bootmap_header_access(fd, bh, 1); +} + +int bootmap_header_write(int fd, struct bootmap_header *bh) +{ + return bootmap_header_access(fd, bh, 0); +} diff --git a/zipl/src/disk.c b/zipl/src/disk.c index 3d7b231d..dcfab84b 100644 --- a/zipl/src/disk.c +++ b/zipl/src/disk.c @@ -23,9 +23,10 @@ #include #include #include +#include + #include "lib/util_proc.h" #include "lib/util_sys.h" - #include "disk.h" #include "error.h" #include "install.h" @@ -1085,9 +1086,9 @@ disk_compact_blocklist(disk_blockptr_t* list, blocknum_t count, } /** - * Retrieve a list of pointers to the disk blocks that make up the whole - * file specified by FILENAME, or only its part specified by REG (if the - * last one is not NULL). + * Retrieve a list of pointers to the disk blocks that make up a continuous + * region REG in a file specified by FILENAME. If REG is NULL, then retrieve + * a list of pointers for the whole file. * Upon success, return the number of blocks and set BLOCKLIST to point to * the uncompacted list. INFO provides information about the device which * contains the file. Return zero otherwise @@ -1121,18 +1122,19 @@ disk_get_blocklist_from_file(const char *filename, struct file_range *reg, return 0; } if (reg) { + /* + * case of not block-aligned offsets is not implemented + */ + assert(reg->offset % info->phy_block_size == 0); + off = reg->offset; count = reg->len; } else { off = 0; count = stats.st_size; } - if (off >= stats.st_size) { - error_reason("Offset %llu is outside the file '%s'", - off, filename); - close(fd); - return 0; - } + assert(off < stats.st_size); + if (off + count > (size_t)stats.st_size) count = stats.st_size - off; diff --git a/zipl/src/envblk.c b/zipl/src/envblk.c new file mode 100644 index 00000000..39a02fc3 --- /dev/null +++ b/zipl/src/envblk.c @@ -0,0 +1,452 @@ +/* + * zipl - zSeries Initial Program Loader tool + * + * Functions to manipulate with environment block + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bootmap.h" +#include "envblk.h" +#include "error.h" +#include "misc.h" + +/* from linux/fs.h */ +#define FIGETBSZ _IO(0x00, 2) + +void envblk_create_blank(char *envblk, int envblk_len) +{ + memcpy(envblk, ZIPL_ENVBLK_SIGNATURE, + sizeof(ZIPL_ENVBLK_SIGNATURE) - 1); + memset(envblk + sizeof(ZIPL_ENVBLK_SIGNATURE) - 1, '\0', + envblk_len - sizeof(ZIPL_ENVBLK_SIGNATURE) + 1); +} + +/** + * Find out environment block location. + * FD: bootmap file descriptor. + * On success the searched location is saved in variable pointed out by OFF + */ +int envblk_offset_get(int fd, off_t *off) +{ + struct bootmap_header bh; + + if (bootmap_header_read(fd, &bh)) { + error_reason("Could not read bootmap_header"); + return -1; + } + *off = bh.envblk_offset; + return 0; +} + +/** + * Save environment block location specified by OFF + * FD: bootmap file descriptor. + * On success the location is saved in the bootmap header. + */ +int envblk_offset_set(int fd, off_t off) +{ + struct bootmap_header bh; + + if (bootmap_header_read(fd, &bh)) { + error_reason("Could not read bootmap header"); + return -1; + } + bh.envblk_offset = off; + + if (bootmap_header_write(fd, &bh)) { + error_reason("Could not write bootmap header"); + return -1; + } + return 0; +} + +/** + * Find out environment block size. + * FD: bootmap file descriptor. + * On success the searched size is stored in RESULT + */ +int envblk_size_get(int fd, int *result) +{ + if (ioctl(fd, FIGETBSZ, result) == -1) { + error_reason(strerror(errno)); + return -1; + } + return 0; +} + +#define LINE_HASH_SIZE (16) + +/** + * Calculate hash function of "environment line" + * + * LINE: pointer to a string, containing NANE=VALUE + * LEN: length of the NAME with trailing delimiter "=" + */ +static unsigned int linehash(char *line, int len, unsigned int hash_size) +{ + unsigned int sum = 0; + + while (len) { + sum += 5 * *(line++); + len--; + } + return sum % hash_size; +} + +struct line_hash_entry { + struct line_hash_entry *next; + char *line; /* pointer to an "environment line", + * a string containing NAME=VALUE with + * optional trailing end-of-line symbol + */ +}; + +/** + * Check if @name meets the requirements for shell environment variables + * (IEEE Std 1003.1-2001), mitigated with lowercase letter allowance + * + * Return 0, if it meets, and 1 otherwise + */ +int envblk_check_name(char *name, int len) +{ + int i; + + if (len == 0) + return 1; + if (!isalpha(name[0]) && name[0] != '_') + return 1; + for (i = 1; i < len; i++) + if (!isalpha(name[i]) && !isdigit(name[i]) && name[i] != '_') + return 1; + return 0; +} + +/** + * Add environment line to hash table + * + * LINE: pointer to a string containing NAME=VALUE + * LEN: length of NAME with trailing delimiter "=" + */ +static int hash_table_add(struct line_hash_entry **table, char *line, int len) +{ + unsigned int hash; + struct line_hash_entry *new; + + new = malloc(sizeof(*new)); + if (!new) + return -ENOMEM; + memset(new, 0, sizeof(*new)); + new->line = misc_strdup(line); + if (!new->line) { + free(new); + return -ENOMEM; + } + hash = linehash(new->line, len, LINE_HASH_SIZE); + new->next = table[hash]; + table[hash] = new; + return 0; +} + +/** + * Find environment line in the hash table by name + * + * LINE: string contailing NAME=VALUE + * LEN: length of the NAME with trailing delimiter "=" + */ +struct line_hash_entry *hash_table_find(struct line_hash_entry **table, + char *name, size_t len) +{ + struct line_hash_entry *item; + + item = table[linehash(name, len, LINE_HASH_SIZE)]; + while (item) { + if (!strncmp(item->line, name, len)) + return item; + item = item->next; + } + return NULL; +} + +static int hash_entry_replace(struct line_hash_entry *item, char *name, + int *offset) +{ + char *dup; + + dup = misc_strdup(name); + if (!dup) + return -ENOMEM; + *offset -= strlen(item->line); + free(item->line); + item->line = dup; + *offset += strlen(item->line); + return 1; +} + +/** + * Scan hash table TABLE, and call ACTOR for each its entry. + * The ACTOR is allowed even to release the entry. + */ +static void hash_table_scan(struct line_hash_entry **table, + void (*actor)(struct line_hash_entry *entry)) +{ + int i; + + for (i = 0; i < LINE_HASH_SIZE; i++) { + struct line_hash_entry *item = table[i]; + + while (item) { + struct line_hash_entry *next = item->next; + + actor(item); + item = next; + } + } +} + +static void hash_entry_free(struct line_hash_entry *this) +{ + free(this->line); + free(this); +} + +static void hash_table_free(struct line_hash_entry **table) +{ + hash_table_scan(table, hash_entry_free); +} + +/** + * Scan hash table TABLE, and for each its entry copy its line to the + * in-memory environment block specified by DST at the offset OFF. + * Insert end-of-line symbol, if needed. The caller should guarantee + * enough space in the buffer DST + */ +static void hash_table_flush(struct line_hash_entry **table, char *dst, + int off) +{ + int i; + + for (i = 0; i < LINE_HASH_SIZE; i++) { + struct line_hash_entry *item = table[i]; + + while (item) { + int str_len; + + str_len = strlen(item->line); + memcpy(dst + off, item->line, str_len); + off += str_len; + if (dst[off - 1] != '\n') { + dst[off] = '\n'; + off += 1; + } + item = item->next; + } + } +} + +/** + * Put a zIPL environment line into hash table. + * + * Identify a name in the line THIS which contains a pair NAME=VALUE, and + * look for an entry in the hash table TABLE with a similar name. If such + * entry exists, then replace the line in that entry with the new one + * specified by THIS. Othewise, insert a new entry to the hash table. + * + * THIS: pointer to a NULL-terminated string, which contains the line to + * be imported. If the line was successfully imported, and no line with the + * same NAME was added before, then return 0. + * If the line should be ignored, or it replaced previously added line with + * the same NAME, then return > 0. On errors return < 0. + */ +static int import_one_line(char *this, struct line_hash_entry **table, + int *offset) +{ + struct line_hash_entry *item; + char *p; + + if (*this == '#') + /* Ignore "commented" line */ + return 1; + + p = strchr(this, '='); + if (p == NULL || p == this) + /* Could not identify NAME in the line. Ignore */ + return 1; + if (envblk_check_name(this, p - this)) { + error_reason("Unacceptable name '%.*s'", p - this, this); + return -EINVAL; + } + /* + * Try to find an entry with similar NAME. + */ + item = hash_table_find(table, this, p - this + 1); + + return item != NULL ? hash_entry_replace(item, this, offset) : + hash_table_add(table, this, p - this + 1); +} + +/** + * Import environment line-by-line from a regular file specified + * by its name FROM_FILE to a pre-allocated buffer TO_BUF. Ignore + * malformed, or "commented" lines. If the file doesn't end with + * end-of-line symbol, then append that symbol in the destination + * buffer. Return 0 on success. + * + * ENVBLK_SIZE: size of the destination buffer TO_BUF. + */ +int envblk_import(char *from_file, char *to_buf, int envblk_size) +{ + const char *err_prefix = "Failed to import environment file"; + struct line_hash_entry *table[LINE_HASH_SIZE]; + unsigned int lines_imported = 0; + char *line = NULL; + size_t len = 0; + FILE *stream; + ssize_t read; + int ret = 0; + int offset; + + stream = fopen(from_file, "r"); + if (stream == NULL) + return 0; + + memset(table, 0, LINE_HASH_SIZE*sizeof(void *)); + offset = sizeof(ZIPL_ENVBLK_SIGNATURE) - 1; + + while ((read = getline(&line, &len, stream)) != -1) { + if (read > envblk_size - offset) { + error_reason("%s %s: maximal size (%d) exceeded", + err_prefix, from_file, + ENVBLK_MAX_IMPORT_SIZE(envblk_size)); + ret = -EINVAL; + break; + } + if (read < 2) + /* malformed line. Ignored */ + continue; + if (line[read - 1] != '\n' && read == envblk_size - offset) { + /* + * file's tail doesn't contain end-of-line symbol at + * the end, whereas there is no enough space in the + * destination buffer to insert that symbol, which is + * mandatory according to the environment block format + */ + error_reason("%s %s: maximal size (%d) exceeded", + err_prefix, from_file, + ENVBLK_MAX_IMPORT_SIZE(envblk_size)); + ret = -EINVAL; + break; + } + ret = import_one_line(line, table, &offset); + if (ret < 0) { + /* error */ + error_text("%s %s", err_prefix, from_file); + break; + } else if (ret > 0) { + /* + * either this line was ignored, or previously + * imported line was overwritten by it + */ + ret = 0; + continue; + } + if (lines_imported >= ENVBLK_MAX_LINES) { + error_reason("%s %s: maximum number of lines (%d) exceeded", + err_prefix, from_file, ENVBLK_MAX_LINES); + ret = -EINVAL; + break; + } + offset += read; + lines_imported++; + } + hash_table_flush(table, to_buf, sizeof(ZIPL_ENVBLK_SIGNATURE) - 1); + hash_table_free(table); + free(line); + fclose(stream); + return ret; +} + +char *envblk_next_line(char *s, const char *end) +{ + while (s < end) { + if (*s == '\n') + break; + s++; + } + return s + 1; +} + +/** + * Scan installed environment block and call ACTOR for each found NAME. + * Return -1 on corrupted environment blocks. Otherwise, return 0 + */ +int envblk_scan(char *envblk, unsigned int envblk_size, + void (*actor)(char *name)) +{ + unsigned int lines_scanned = 0; + const char *reason; + char *s, *end; + char *name; + + s = envblk + sizeof(ZIPL_ENVBLK_SIGNATURE) - 1; + end = envblk + envblk_size; + + while (s < end && *s != 0) { + if (lines_scanned > ENVBLK_MAX_LINES) { + reason = "maximum number of lines exceeded"; + goto corrupted; + } + name = s; + while (s < end && *s != '=') + s++; + if (s == end) { + /* delimiter "=" not found */ + reason = "missed delimiter"; + goto corrupted; + } + /* here @s points to "=" */ + s++; + + while (s < end && *s != '\n') + s++; + if (s == end) { + /* end of line not found */ + reason = "missed EOL"; + goto corrupted; + } + *s = '\0'; + actor(name); + *s = '\n'; + lines_scanned++; + s = envblk_next_line(s, end); + } + return 0; +corrupted: + error_reason("Found corrupted environment block (%s) - please run zipl", + reason); + return -1; +} + +static void print_name_value(char *this) +{ + printf(" %s\n", this); +} + +void envblk_print(char *envblk, unsigned int envblk_size) +{ + printf("Environment block content:\n"); + envblk_scan(envblk, envblk_size, print_name_value); +} diff --git a/zipl/src/job.c b/zipl/src/job.c index f28fac9b..70c1a7e2 100644 --- a/zipl/src/job.c +++ b/zipl/src/job.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,7 @@ #include "misc.h" #include "scan.h" #include "zipl.h" +#include "envblk.h" /* Command line options */ static struct option options[] = { @@ -37,6 +39,7 @@ static struct option options[] = { { "targetgeometry", required_argument, NULL, 0xac}, { "targetblocksize", required_argument, NULL, 0xad}, { "targetoffset", required_argument, NULL, 0xae}, + { "environment", required_argument, NULL, 0xaf}, { "image", required_argument, NULL, 'i'}, { "ramdisk", required_argument, NULL, 'r'}, { "parmfile", required_argument, NULL, 'p'}, @@ -73,6 +76,7 @@ static const char *zipl_conf[] = { struct command_line { char* data[SCAN_KEYWORD_NUM]; char* config; + char *envblk_import_hint; char *blsdir; char* menu; char* section; @@ -87,7 +91,6 @@ struct command_line { enum scan_section_type type; }; - static int store_option(struct command_line* cmdline, enum scan_keyword_id keyword, char* value) @@ -211,6 +214,13 @@ get_command_line(int argc, char* argv[], struct command_line* line) rc = store_option(&cmdline, scan_keyword_targetoffset, optarg); break; + case 0xaf: + if (cmdline.envblk_import_hint != NULL) { + error_reason("Option 'environment' specified more than once"); + rc = -1; + } else + cmdline.envblk_import_hint = optarg; + break; case 'T': is_keyword = 1; rc = store_option(&cmdline, scan_keyword_tape, @@ -351,6 +361,12 @@ free_target_data(struct job_target_data* data) free(data->targetbase); } +static void +free_envblk_data(struct job_envblk_data *data) +{ + free(data->buf); +} + static void free_ipl_data(struct job_ipl_data* data) { @@ -444,6 +460,7 @@ job_free(struct job_data* job) if (job->target.bootmap_dir != NULL) free(job->target.bootmap_dir); free_target_data(&job->target); + free_envblk_data(&job->envblk); if (job->name != NULL) free(job->name); switch (job->id) { @@ -480,7 +497,7 @@ struct component_loc { }; static int -set_cl_element(struct component_loc *cl, char *name, char *filename, +set_cl_element(struct component_loc *cl, char *name, const char *filename, address_t *addrp, size_t size, off_t off, off_t align) { struct stat stats; @@ -528,17 +545,16 @@ sort_cl_array(struct component_loc* cl, int elements) } } } - static int get_ipl_components(struct job_ipl_data *ipl, struct component_loc **clp, - int *nump) + int *nump, struct job_envblk_data *envblk) { struct component_loc *cl; int num; int rc; - /* Get memory for image, parmline, ramdisk */ - cl = misc_calloc(3, sizeof(struct component_loc)); + /* Get memory for image, parmline, ramdisk and environment block */ + cl = misc_calloc(4, sizeof(struct component_loc)); if (cl == NULL) return -1; /* Fill in component data */ @@ -561,6 +577,12 @@ get_ipl_components(struct job_ipl_data *ipl, struct component_loc **clp, if (rc) goto error; } + rc = set_cl_element(&cl[num++], "environment block", + NULL, + &ipl->envblk_addr, envblk->size, 0, + MAXIMUM_PHYSICAL_BLOCKSIZE); + if (rc) + goto error; *clp = cl; *nump = num; @@ -753,7 +775,8 @@ finalize_component_address_data(struct component_loc *cl, int num, static int -finalize_ipl_address_data(struct job_ipl_data* ipl, char *name) +finalize_ipl_address_data(struct job_ipl_data *ipl, char *name, + struct job_envblk_data *envblk) { unsigned long address_limit; struct component_loc *cl; @@ -762,7 +785,7 @@ finalize_ipl_address_data(struct job_ipl_data* ipl, char *name) address_limit = ipl->is_kdump ? MIN(util_arch_hsa_maxsize(), ADDRESS_LIMIT) : ADDRESS_LIMIT; - rc = get_ipl_components(ipl, &cl, &num); + rc = get_ipl_components(ipl, &cl, &num, envblk); if (rc) return rc; sort_cl_array(cl, num); @@ -828,7 +851,8 @@ out_free: static int -check_job_ipl_data(struct job_ipl_data *ipl, char* name) +check_job_ipl_data(struct job_ipl_data *ipl, char *name, + struct job_envblk_data *envblk) { int rc; @@ -856,7 +880,7 @@ check_job_ipl_data(struct job_ipl_data *ipl, char* name) return rc; } } - return finalize_ipl_address_data(ipl, name); + return finalize_ipl_address_data(ipl, name, envblk); } @@ -935,7 +959,7 @@ check_job_dump_images(struct job_dump_data* dump, char* name) static int -check_job_menu_data(struct job_menu_data* menu) +check_job_menu_data(struct job_menu_data *menu, struct job_envblk_data *envblk) { int rc; int i; @@ -944,7 +968,8 @@ check_job_menu_data(struct job_menu_data* menu) switch (menu->entry[i].id) { case job_ipl: rc = check_job_ipl_data(&menu->entry[i].data.ipl, - menu->entry[i].name); + menu->entry[i].name, + envblk); if (rc) return rc; break; @@ -1110,10 +1135,11 @@ check_job_data(struct job_data* job) rc = 0; break; case job_ipl: - rc = check_job_ipl_data(&job->data.ipl, job->name); + rc = check_job_ipl_data(&job->data.ipl, job->name, + &job->envblk); break; case job_menu: - rc = check_job_menu_data(&job->data.menu); + rc = check_job_menu_data(&job->data.menu, &job->envblk); break; case job_segment: rc = check_job_segment_data(&job->data.segment, job->name); @@ -1372,6 +1398,9 @@ get_job_from_section_data(char* data[], struct job_data* job, char* section) &job->data.ipl.parm_addr, section); if (rc) return rc; + /* Fill in environment block */ + job->data.ipl.envblk_addr = UNSPECIFIED_ADDRESS; + /* Fill in name and address of ramdisk file */ if (data[(int) scan_keyword_ramdisk] != NULL) { job->data.ipl.ramdisk = @@ -1898,6 +1927,45 @@ get_job_from_config_file(struct command_line* cmdline, struct job_data* job) return rc; } +static int get_job_envblk_data(struct job_data *job, char *import_hint) +{ + struct job_envblk_data *data = &job->envblk; + int fd; + + switch (job->id) { + case job_ipl: + case job_menu: + break; + default: + return 0; + } + fd = open(job->target.bootmap_dir, O_RDONLY); + if (fd < 0) { + error_reason(strerror(errno)); + error_text("Could not open bootmap dir"); + return -1; + } + if (envblk_size_get(fd, &data->size)) { + close(fd); + error_text("Could not get environment block size"); + return -1; + } + close(fd); + data->buf = misc_malloc(data->size); + if (data->buf == NULL) { + error_text("Could not allocate environment block"); + return -1; + } + envblk_create_blank(data->buf, data->size); + + if (envblk_import(import_hint ?: ENVBLK_DEFAULT_IMPORT_SOURCE, + data->buf, data->size)) { + free(data->buf); + data->buf = NULL; + return -1; + } + return 0; +} int job_get(int argc, char* argv[], struct job_data** data) @@ -1946,6 +2014,11 @@ job_get(int argc, char* argv[], struct job_data** data) else if (job->id != job_menu && job->is_secure == SECURE_BOOT_UNDEFINED) job->is_secure = SECURE_BOOT_AUTO; + rc = get_job_envblk_data(job, cmdline.envblk_import_hint); + if (rc) { + job_free(job); + return -1; + } /* Check job data for validity */ rc = check_job_data(job); if (rc) {