mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
. Support multiple namespaces in the installed environment block; . Add option --site (-S) to specify a particular namespace when operating on the installed environment block; . Add option --effective-site (-E) to specify a particular namespace. When using in a combination with the option -l (--list), it displays zIPL environment that would take place if the specified namespace was activated at boot time; . Modify set/unset/reset/list operations over the installed environment block in the case when the option --site is specified Reviewed-by: Stefan Haberland <sth@linux.ibm.com> Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
1049 lines
24 KiB
C
1049 lines
24 KiB
C
/*
|
|
* 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 <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "lib/util_libc.h"
|
|
#include "bootmap.h"
|
|
#include "envblk.h"
|
|
#include "error.h"
|
|
#include "misc.h"
|
|
#include <assert.h>
|
|
|
|
/* from linux/fs.h */
|
|
#define FIGETBSZ _IO(0x00, 2)
|
|
#define KEYWORD_SIZE strlen("[site X]\n")
|
|
#define MAX_NR_SITES 10
|
|
#define LINE_HASH_SIZE 16
|
|
#define PARAM_SIZE 8
|
|
|
|
struct iter_info {
|
|
struct line_hash_entry **table;
|
|
const char *site_id;
|
|
char *envblk;
|
|
int envblk_len;
|
|
int indent;
|
|
};
|
|
|
|
void envblk_blank(char *envblk, int envblk_len)
|
|
{
|
|
memset(envblk + sizeof(ZIPL_ENVBLK_SIGNATURE) - 1, '\0',
|
|
envblk_len - sizeof(ZIPL_ENVBLK_SIGNATURE) + 1);
|
|
}
|
|
|
|
void envblk_create_blank(char *envblk, int envblk_len)
|
|
{
|
|
memcpy(envblk, ZIPL_ENVBLK_SIGNATURE,
|
|
sizeof(ZIPL_ENVBLK_SIGNATURE) - 1);
|
|
envblk_blank(envblk, envblk_len);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* This defines "extended string", an object consisting of
|
|
* an optional logical prefix and a substring
|
|
*/
|
|
struct estr {
|
|
char *prefix; /* pointer to the logical prefix */
|
|
char *str; /* pointer to the substring */
|
|
int hashed_len; /* number of bytes in the substring,
|
|
* participating in hash calculation
|
|
*/
|
|
};
|
|
|
|
/**
|
|
* Hash function on "extended strings"
|
|
*/
|
|
static unsigned int hash_fn(struct estr *estr)
|
|
{
|
|
int len = estr->hashed_len;
|
|
char *str = estr->str;
|
|
unsigned int sum;
|
|
|
|
sum = 0;
|
|
if (estr->prefix)
|
|
sum += 5 * *estr->prefix;
|
|
while (len) {
|
|
sum += 5 * *(str++);
|
|
len--;
|
|
}
|
|
return sum % LINE_HASH_SIZE;
|
|
}
|
|
|
|
/**
|
|
* Compare a string STR with an "extended string" ESTR
|
|
*/
|
|
static int cmp_fn(char *str, struct estr *estr)
|
|
{
|
|
if (!estr->prefix)
|
|
return strncmp(str, estr->str, estr->hashed_len);
|
|
if (str[0] != *estr->prefix)
|
|
return -1;
|
|
return strncmp(str + 1, estr->str, estr->hashed_len);
|
|
}
|
|
|
|
struct line_hash_entry {
|
|
struct line_hash_entry *next;
|
|
char *line; /* pointer to a string containing a pair 'KEY=VALUE'
|
|
* with an optional prefix and an optional trailing
|
|
* new line character
|
|
*/
|
|
};
|
|
|
|
/**
|
|
* Populate a hash TABLE with an item built by an "extended string" ESTR
|
|
*/
|
|
static int hash_table_add(struct line_hash_entry **table, struct estr *estr)
|
|
{
|
|
struct line_hash_entry *new;
|
|
unsigned int hash;
|
|
int len;
|
|
|
|
new = malloc(sizeof(*new));
|
|
if (!new)
|
|
return -ENOMEM;
|
|
memset(new, 0, sizeof(*new));
|
|
|
|
len = strlen(estr->str);
|
|
if (estr->prefix) {
|
|
new->line = misc_malloc(len + 2);
|
|
if (!new->line) {
|
|
free(new);
|
|
return -ENOMEM;
|
|
}
|
|
new->line[0] = *estr->prefix;
|
|
memcpy(new->line + 1, estr->str, len);
|
|
new->line[len + 1] = '\0';
|
|
} else {
|
|
new->line = misc_strdup(estr->str);
|
|
if (!new->line) {
|
|
free(new);
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
hash = hash_fn(estr);
|
|
new->next = table[hash];
|
|
table[hash] = new;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Search an item in the hash table by an "extended string" ESTR
|
|
*/
|
|
static struct line_hash_entry *hash_table_find(struct line_hash_entry **table,
|
|
struct estr *estr)
|
|
{
|
|
struct line_hash_entry *item;
|
|
|
|
if (!table)
|
|
return NULL;
|
|
item = table[hash_fn(estr)];
|
|
while (item) {
|
|
if (!cmp_fn(item->line, estr))
|
|
return item;
|
|
item = item->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Replace an ITEM with another one built by an "extended string" ESTR
|
|
*/
|
|
static int hash_entry_replace(struct line_hash_entry *item, struct estr *estr,
|
|
int *offset)
|
|
{
|
|
int len;
|
|
char *new;
|
|
|
|
len = strlen(estr->str);
|
|
if (estr->prefix) {
|
|
len++;
|
|
new = misc_malloc(len + 1);
|
|
if (!new)
|
|
return -ENOMEM;
|
|
new[0] = *estr->prefix;
|
|
memcpy(new + 1, estr->str, len - 1);
|
|
new[len] = '\0';
|
|
} else {
|
|
new = misc_strdup(estr->str);
|
|
if (!new)
|
|
return -ENOMEM;
|
|
}
|
|
*offset -= strlen(item->line);
|
|
free(item->line);
|
|
item->line = new;
|
|
*offset += len;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Scan hash table TABLE, and call ACTOR for each its entry.
|
|
* The ACTOR is allowed even to release the entry.
|
|
*/
|
|
static int hash_table_scan(struct line_hash_entry **table,
|
|
int (*actor)(struct line_hash_entry *entry,
|
|
void *data), void *data)
|
|
{
|
|
int ret;
|
|
int i;
|
|
|
|
if (!table)
|
|
return 0;
|
|
for (i = 0; i < LINE_HASH_SIZE; i++) {
|
|
struct line_hash_entry *item = table[i];
|
|
|
|
while (item) {
|
|
struct line_hash_entry *next = item->next;
|
|
|
|
ret = actor(item, data);
|
|
if (ret)
|
|
return ret;
|
|
item = next;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int hash_entry_free(struct line_hash_entry *this,
|
|
__attribute__ ((unused))void *data)
|
|
{
|
|
free(this->line);
|
|
free(this);
|
|
return 0;
|
|
}
|
|
|
|
static void hash_table_free(struct line_hash_entry **table)
|
|
{
|
|
hash_table_scan(table, hash_entry_free, NULL);
|
|
}
|
|
|
|
/**
|
|
* 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 a new line character, 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
enum keyword_status {
|
|
KW_UNKNOWN,
|
|
KW_VALID,
|
|
KW_MALFORMED,
|
|
KW_UNSUPP_ID
|
|
};
|
|
|
|
/**
|
|
* Return 1, if the line's TAIL is empty, or is a whitespace
|
|
* (may be empty) followed by a comment. Otherwise, return 0.
|
|
*/
|
|
static int ignore_tail(const char *tail)
|
|
{
|
|
if (*tail == '\n')
|
|
return 1;
|
|
do {
|
|
if (*tail == '#')
|
|
return 1;
|
|
if (*tail == '\t' || *tail == ' ') {
|
|
tail++;
|
|
continue;
|
|
}
|
|
return 0;
|
|
} while (*tail != '\n');
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Check if LINE represents a keyword to identify a section in the
|
|
* environment file. Return the result of the check. If the LINE
|
|
* represents a valid keyword (KW_VALID is returned), then SITE_ID
|
|
* contains ID of the namespace represented by that section.
|
|
*
|
|
* LINE: a null-terminated string with optional new line character
|
|
* LEN: number of characters in the LINE excluding NULL-termination
|
|
*/
|
|
static enum keyword_status check_keyword(char *line, size_t len, char **site_id)
|
|
{
|
|
char *endptr;
|
|
long val;
|
|
|
|
if (len < KEYWORD_SIZE - 1 || line[len - 1] != '\n')
|
|
return KW_UNKNOWN;
|
|
|
|
if ((line[0] == '[') &&
|
|
(line[1] == 's' || line[1] == 'S') &&
|
|
(line[2] == 'i' || line[2] == 'I') &&
|
|
(line[3] == 't' || line[3] == 'T') &&
|
|
(line[4] == 'e' || line[4] == 'E')) {
|
|
if (line[5] != ' ')
|
|
return KW_MALFORMED;
|
|
|
|
*site_id = &line[6];
|
|
val = strtol(&line[6], &endptr, 10);
|
|
if (endptr == &line[6])
|
|
/* not a number */
|
|
return KW_MALFORMED;
|
|
if (val < 0) {
|
|
/* negative number */
|
|
*endptr = '\0';
|
|
return KW_UNSUPP_ID;
|
|
}
|
|
if (*endptr == ']') {
|
|
if (line[6] == '0' && endptr - &line[6] > 1) {
|
|
/* leading zeros are not allowed */
|
|
*endptr = '\0';
|
|
return KW_UNSUPP_ID;
|
|
}
|
|
if (!ignore_tail(endptr + 1))
|
|
/* unacceptable trailing characters */
|
|
return KW_MALFORMED;
|
|
*endptr = '\0';
|
|
return val < MAX_NR_SITES ? KW_VALID : KW_UNSUPP_ID;
|
|
}
|
|
return KW_MALFORMED;
|
|
}
|
|
return KW_UNKNOWN;
|
|
}
|
|
|
|
/**
|
|
* 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(const 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;
|
|
}
|
|
|
|
/**
|
|
* Acceps a string STR, containing a pair 'KEY=VALUE'.
|
|
* Returns length of the KEY.
|
|
*/
|
|
static int get_key_len(const char *str)
|
|
{
|
|
return strchr(str, '=') - str;
|
|
}
|
|
|
|
/**
|
|
* Acceps a string STR, containing a pair 'KEY=VALUE'.
|
|
* Returns number of characters in the string, participating in hash
|
|
* calculation.
|
|
*/
|
|
static int get_hashed_len(const char *str)
|
|
{
|
|
/* hash is calculated by the key with a trailing '=' */
|
|
return get_key_len(str) + 1;
|
|
}
|
|
|
|
/**
|
|
* Put a line containing a pair 'NAME=VALUE' to a namespace referenced
|
|
* by NS_ID
|
|
*
|
|
* If the line was successfully imported without replacement a line with
|
|
* the same NAME, that 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.
|
|
*
|
|
* THIS: pointer to a NULL-terminated string, which contains the line to
|
|
* be imported.
|
|
* NS_ID: Pointer to a numerical character, indicating namespace ID
|
|
*/
|
|
static int import_one_line(char *this, struct line_hash_entry **table,
|
|
int *offset, char *ns_id)
|
|
{
|
|
struct line_hash_entry *item;
|
|
struct estr estr;
|
|
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;
|
|
}
|
|
estr.prefix = ns_id;
|
|
estr.str = this;
|
|
estr.hashed_len = get_hashed_len(this);
|
|
|
|
item = hash_table_find(table, &estr);
|
|
return item != NULL ? hash_entry_replace(item, &estr, offset) :
|
|
hash_table_add(table, &estr);
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* new line character, then append that character 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;
|
|
char site_id = 0;
|
|
char *site_idp;
|
|
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, sizeof(table));
|
|
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;
|
|
|
|
switch (check_keyword(line, read, &site_idp)) {
|
|
case KW_VALID:
|
|
site_id = *site_idp;
|
|
continue;
|
|
case KW_MALFORMED:
|
|
/*
|
|
* any line not ended with a new line character
|
|
* has to be evaluated as KW_UNKNOWN
|
|
*/
|
|
assert(line[read - 1] == '\n');
|
|
line[read - 1] = '\0';
|
|
error_reason("%s %s: bad section title '%s'",
|
|
err_prefix, from_file, line);
|
|
ret = -EINVAL;
|
|
goto error;
|
|
case KW_UNSUPP_ID:
|
|
error_reason("%s %s: unsupported site ID '%s'",
|
|
err_prefix, from_file, site_idp);
|
|
ret = -EINVAL;
|
|
goto error;
|
|
case KW_UNKNOWN:
|
|
break;
|
|
}
|
|
/* LINE is not a secton title */
|
|
|
|
if (line[read - 1] != '\n' && read == envblk_size - offset) {
|
|
/*
|
|
* file's tail doesn't contain a new line charactere at
|
|
* the end, whereas there is no enough space in the
|
|
* destination buffer to insert that character, 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,
|
|
site_id ? &site_id : NULL);
|
|
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);
|
|
error:
|
|
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,
|
|
int (*actor)(char *name, void *data), void *data)
|
|
{
|
|
unsigned int lines_scanned = 0;
|
|
const char *reason;
|
|
char *s, *end;
|
|
char *name;
|
|
int ret;
|
|
|
|
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';
|
|
ret = actor(name, data);
|
|
*s = '\n';
|
|
if (ret)
|
|
return ret;
|
|
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 const char *indent_by_size(int size)
|
|
{
|
|
switch (size) {
|
|
case 1:
|
|
return " ";
|
|
case 2:
|
|
return " ";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Put a line containing a pair 'NAME=VALUE' to a respective namespace.
|
|
*
|
|
* THIS: line containing a pair 'NAME=VALUE', optionally prefixed
|
|
* with one numerical character indicating namespace ID
|
|
*/
|
|
static int add_line_namespace(char *this, void *data)
|
|
{
|
|
struct line_hash_entry ***tables = data;
|
|
struct estr estr;
|
|
char *endptr;
|
|
int val;
|
|
|
|
val = strtol(this, &endptr, 10);
|
|
if (endptr == this)
|
|
val = MAX_NR_SITES;
|
|
if (!tables[val]) {
|
|
tables[val] =
|
|
util_zalloc(LINE_HASH_SIZE * sizeof(*tables));
|
|
if (!tables[val])
|
|
return -ENOMEM;
|
|
}
|
|
estr.prefix = NULL;
|
|
estr.str = endptr;
|
|
estr.hashed_len = get_hashed_len(endptr);
|
|
|
|
return hash_table_add(tables[val], &estr);
|
|
}
|
|
|
|
static int hash_table_print_line(struct line_hash_entry *item, void *data)
|
|
{
|
|
struct iter_info *ii = data;
|
|
|
|
printf("%s%s\n", indent_by_size(ii->indent), item->line);
|
|
return 0;
|
|
}
|
|
|
|
static int hash_table_print_line_cond(struct line_hash_entry *item, void *data)
|
|
{
|
|
struct iter_info *ii = data;
|
|
struct estr estr;
|
|
|
|
estr.prefix = NULL;
|
|
estr.str = item->line;
|
|
estr.hashed_len = get_hashed_len(item->line);
|
|
|
|
if (!hash_table_find(ii->table, &estr))
|
|
hash_table_print_line(item, data);
|
|
return 0;
|
|
}
|
|
|
|
static int print_namespace(struct line_hash_entry **table, const char *title,
|
|
int indent)
|
|
{
|
|
struct iter_info ii;
|
|
|
|
if (!table)
|
|
return 0;
|
|
printf("%s%s", indent_by_size(indent), title);
|
|
ii.table = table;
|
|
ii.indent = indent + 1;
|
|
hash_table_scan(table, hash_table_print_line, &ii);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Dump content of the environment block in human-readable form,
|
|
* sorting it by "sections"
|
|
*/
|
|
int envblk_list_all(char *envblk, unsigned int envblk_size, int indent)
|
|
{
|
|
struct line_hash_entry **tables[MAX_NR_SITES + 1];
|
|
int nr_sites = 0;
|
|
int ret = 0;
|
|
int i;
|
|
|
|
memset(tables, 0, sizeof(tables));
|
|
/*
|
|
* Scan environment block and sort the pairs 'NAME=VALUE'
|
|
* by namespaces
|
|
*/
|
|
ret = envblk_scan(envblk, envblk_size, add_line_namespace, tables);
|
|
if (ret)
|
|
goto out;
|
|
for (i = 0; i < MAX_NR_SITES; i++) {
|
|
if (tables[i])
|
|
nr_sites++;
|
|
}
|
|
/* print common namespace */
|
|
print_namespace(tables[MAX_NR_SITES],
|
|
nr_sites ? "Common variables:\n" : "",
|
|
nr_sites ? indent : indent - 1);
|
|
/* print site-specific namespaces */
|
|
for (i = 0; i < MAX_NR_SITES; i++) {
|
|
char title[10];
|
|
|
|
sprintf(title, "Site %d:\n", i);
|
|
print_namespace(tables[i], title, indent);
|
|
}
|
|
out:
|
|
for (i = 0; i <= MAX_NR_SITES; i++) {
|
|
hash_table_free(tables[i]);
|
|
free(tables[i]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Print environment determined by effective SITE_ID
|
|
*/
|
|
int envblk_list_effective_site(char *envblk, unsigned int envblk_size, int site_id)
|
|
{
|
|
struct line_hash_entry **tables[MAX_NR_SITES + 1];
|
|
struct iter_info ii;
|
|
int ret = 0;
|
|
int i;
|
|
|
|
memset(tables, 0, sizeof(tables));
|
|
/*
|
|
* Scan environment block and sort out the pairs 'NAME=VALUE'
|
|
* by namespaces
|
|
*/
|
|
ret = envblk_scan(envblk, envblk_size, add_line_namespace, tables);
|
|
if (ret)
|
|
goto out;
|
|
/*
|
|
* print all variables of the common namespace, which are not
|
|
* defined in the SITE_ID namespace
|
|
*/
|
|
ii.table = tables[site_id];
|
|
ii.indent = 0;
|
|
hash_table_scan(tables[MAX_NR_SITES], hash_table_print_line_cond,
|
|
&ii);
|
|
/*
|
|
* print all variables of the SITE_ID namespace
|
|
*/
|
|
ii.table = NULL;
|
|
ii.indent = 0;
|
|
hash_table_scan(tables[site_id], hash_table_print_line, &ii);
|
|
out:
|
|
for (i = 0; i <= MAX_NR_SITES; i++) {
|
|
hash_table_free(tables[i]);
|
|
free(tables[i]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* List all variables defined by the namespace with SITE_ID
|
|
*/
|
|
int envblk_list_site(char *envblk, unsigned int envblk_size, int site_id)
|
|
{
|
|
struct line_hash_entry **tables[MAX_NR_SITES + 1];
|
|
struct iter_info ii;
|
|
int ret = 0;
|
|
int i;
|
|
|
|
memset(tables, 0, sizeof(tables));
|
|
/*
|
|
* Scan environment block and sort out the pairs 'NAME=VALUE'
|
|
* by namespaces
|
|
*/
|
|
ret = envblk_scan(envblk, envblk_size, add_line_namespace, tables);
|
|
if (ret)
|
|
goto out;
|
|
ii.table = NULL;
|
|
ii.indent = 0;
|
|
hash_table_scan(tables[site_id], hash_table_print_line, &ii);
|
|
out:
|
|
for (i = 0; i <= MAX_NR_SITES; i++) {
|
|
hash_table_free(tables[i]);
|
|
free(tables[i]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void envblk_print(char *envblk, unsigned int envblk_size)
|
|
{
|
|
printf("zIPL environment block content:\n");
|
|
envblk_list_all(envblk, envblk_size, 1 /* indent */);
|
|
}
|
|
|
|
int envblk_set(char *envblk, unsigned int envblk_size, const char *name,
|
|
const char *new_val)
|
|
{
|
|
unsigned int name_len, new_val_len;
|
|
char *fss; /* free space start */
|
|
unsigned int lines_scanned = 0;
|
|
int name_found = 0;
|
|
char *s, *end;
|
|
|
|
name_len = strlen(name);
|
|
new_val_len = strlen(new_val);
|
|
|
|
s = envblk + sizeof(ZIPL_ENVBLK_SIGNATURE) - 1;
|
|
end = envblk + envblk_size;
|
|
|
|
/*
|
|
* find the start of free space
|
|
*/
|
|
for (fss = end - 1; *fss == '\0'; fss--)
|
|
;
|
|
if (*fss != '\n') {
|
|
error_reason("Found corrupted environment block - please run zipl");
|
|
return -1;
|
|
}
|
|
fss++;
|
|
|
|
while (fss - s > name_len) {
|
|
if (lines_scanned >= ENVBLK_MAX_LINES) {
|
|
error_reason("Found corrupted environment block - please run zipl");
|
|
return -1;
|
|
}
|
|
if (memcmp(s, name, name_len) == 0 && s[name_len] == '=') {
|
|
unsigned int cur_val_len;
|
|
/*
|
|
* such name exists, replace its current value
|
|
*/
|
|
s += (name_len + 1);
|
|
|
|
cur_val_len = 0;
|
|
while (s + cur_val_len < end && s[cur_val_len] != '\n')
|
|
cur_val_len++;
|
|
if (s + cur_val_len >= end) {
|
|
error_reason("Found corrupted environment block - please run zipl");
|
|
return -1;
|
|
}
|
|
if (new_val_len > cur_val_len &&
|
|
end - fss < new_val_len - cur_val_len) {
|
|
error_reason("Not enough space for new value");
|
|
return -1;
|
|
}
|
|
/*
|
|
* make a precise-sized room for the new value
|
|
*/
|
|
if (new_val_len < cur_val_len) {
|
|
memmove(s + new_val_len, s + cur_val_len,
|
|
end - (s + cur_val_len));
|
|
|
|
memset(fss + cur_val_len - new_val_len, '\0',
|
|
cur_val_len - new_val_len);
|
|
} else {
|
|
memmove(s + new_val_len, s + cur_val_len,
|
|
end - (s + new_val_len));
|
|
}
|
|
name_found = 1;
|
|
break;
|
|
}
|
|
lines_scanned++;
|
|
s = envblk_next_line(s, end);
|
|
}
|
|
assert(lines_scanned <= ENVBLK_MAX_LINES);
|
|
|
|
if (!name_found) {
|
|
if (lines_scanned == ENVBLK_MAX_LINES) {
|
|
error_reason("Maximum number of lines reached");
|
|
return -1;
|
|
}
|
|
/*
|
|
* append a new variable
|
|
*/
|
|
if (end - fss < name_len + new_val_len + 2) {
|
|
error_reason("Not enough space in environment block");
|
|
return -1;
|
|
}
|
|
memcpy(fss, name, name_len);
|
|
s = fss + name_len;
|
|
*s++ = '=';
|
|
}
|
|
/*
|
|
* copy the new value and terminate it with a new line character
|
|
*/
|
|
memcpy(s, new_val, new_val_len);
|
|
s[new_val_len] = '\n';
|
|
return 0;
|
|
}
|
|
|
|
int envblk_unset(char *envblk, int envblk_len,
|
|
const char *pname, const char *site_id)
|
|
{
|
|
unsigned int pname_len;
|
|
char *s, *end;
|
|
|
|
pname_len = strlen(pname);
|
|
s = envblk + sizeof(ZIPL_ENVBLK_SIGNATURE) - 1;
|
|
end = envblk + envblk_len;
|
|
|
|
while (end - s >= pname_len + 2 /* minimal length of
|
|
* pattern "name=foo"
|
|
*/) {
|
|
if (memcmp(s, pname, pname_len) == 0 && s[pname_len] == '=') {
|
|
/*
|
|
* prefixed name was found. Locate the whole
|
|
* "named" line and cut it including
|
|
* the trailing "\n"
|
|
*/
|
|
unsigned int cut_len = pname_len + 1;
|
|
|
|
while (s + cut_len < end) {
|
|
if (s[cut_len] == '\n')
|
|
break;
|
|
cut_len++;
|
|
}
|
|
if (s + cut_len >= end) {
|
|
/*
|
|
* trailing "\n" not found
|
|
*/
|
|
error_reason("Found corrupted environment block - please run zipl");
|
|
return -1;
|
|
}
|
|
cut_len++;
|
|
memmove(s, s + cut_len, end - (s + cut_len));
|
|
memset(end - cut_len, '\0', cut_len);
|
|
return 0;
|
|
}
|
|
s = envblk_next_line(s, end);
|
|
}
|
|
error_reason("Name '%s' not found in %s%s namespace",
|
|
get_name(site_id, pname),
|
|
site_id ? "site " : "common",
|
|
site_id ?: "");
|
|
return -1;
|
|
}
|
|
|
|
static int remove_name_value(struct line_hash_entry *item, void *data)
|
|
{
|
|
struct iter_info *ii = data;
|
|
char *pname;
|
|
int len;
|
|
int ret;
|
|
|
|
/*
|
|
* construct a prefixed null-terminated name by a line 'KEY=VALUE'
|
|
*/
|
|
len = get_key_len(item->line);
|
|
pname = misc_malloc(len + 2);
|
|
if (!pname) {
|
|
error_reason(strerror(errno));
|
|
return -ENOMEM;
|
|
}
|
|
pname[0] = *ii->site_id;
|
|
memcpy(pname + 1, item->line, len);
|
|
pname[len + 1] = '\0';
|
|
|
|
ret = envblk_unset(ii->envblk,
|
|
strnlen(ii->envblk, ii->envblk_len),
|
|
pname, ii->site_id);
|
|
free(pname);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Remove all lines prefixed with SITE_ID from environment block
|
|
*/
|
|
int envblk_remove_namespace(char *envblk, unsigned int envblk_size,
|
|
const char *site_id)
|
|
{
|
|
struct line_hash_entry **tables[MAX_NR_SITES + 1];
|
|
struct iter_info ii;
|
|
int ret = 0;
|
|
int i;
|
|
|
|
memset(tables, 0, sizeof(tables));
|
|
/*
|
|
* Scan environment block and populate hash tables
|
|
* with the pairs 'NAME=VALUE'
|
|
*/
|
|
ret = envblk_scan(envblk, envblk_size, add_line_namespace, tables);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ii.table = tables[atoi(site_id)];
|
|
ii.envblk_len = envblk_size;
|
|
ii.envblk = envblk;
|
|
ii.site_id = site_id;
|
|
ii.indent = 0;
|
|
|
|
ret = hash_table_scan(tables[atoi(site_id)], remove_name_value, &ii);
|
|
out:
|
|
for (i = 0; i <= MAX_NR_SITES; i++) {
|
|
hash_table_free(tables[i]);
|
|
free(tables[i]);
|
|
}
|
|
return ret;
|
|
}
|