mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Closes: #20 Signed-off-by: Rafael Fonseca <r4f4rfs@gmail.com> Acked-by: Peter Oberparleiter <oberpar@linux.vnet.ibm.com> Signed-off-by: Michael Holzheu <holzheu@linux.vnet.ibm.com>
427 lines
9.6 KiB
C
427 lines
9.6 KiB
C
/*
|
|
* zdev - Modify and display the persistent configuration of devices
|
|
*
|
|
* Copyright IBM Corp. 2016, 2017
|
|
*
|
|
* 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 <ctype.h>
|
|
#include <errno.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "lib/util_path.h"
|
|
|
|
#include "attrib.h"
|
|
#include "misc.h"
|
|
#include "modprobe.h"
|
|
#include "path.h"
|
|
#include "setting.h"
|
|
|
|
/**
|
|
* struct modprobe_line - Representation of a line in a modprobe.conf file
|
|
* @node: List node for adding to list
|
|
* @line: Original line contents or NULL if unavailable
|
|
* @argv: Array of command arguments
|
|
* @argc: Count of command arguments
|
|
*/
|
|
struct modprobe_line {
|
|
struct util_list_node node;
|
|
char *line;
|
|
char **argv;
|
|
int argc;
|
|
};
|
|
|
|
/**
|
|
* struct modprobe_file - Representation of a modprobe.conf file
|
|
* @path: Full path and filename of modprobe.conf file
|
|
* @list: List of lines in file
|
|
*/
|
|
struct modprobe_file {
|
|
char *path;
|
|
struct util_list lines;
|
|
};
|
|
|
|
/* Used for debugging. */
|
|
void modprobe_file_print(struct modprobe_file *mf)
|
|
{
|
|
struct modprobe_line *l;
|
|
int i;
|
|
|
|
printf("modprobe_file(%p)\n", (void *) mf);
|
|
printf(" path='%s'\n", mf->path);
|
|
util_list_iterate(&mf->lines, l) {
|
|
printf(" line(%p)\n", (void *) l);
|
|
printf(" line='%s'\n", l->line);
|
|
printf(" argc=%d\n", l->argc);
|
|
for (i = 0; i < l->argc; i++)
|
|
printf(" argv[%d]='%s'\n", i, l->argv[i]);
|
|
}
|
|
}
|
|
|
|
/* Remove the line continuation character \\ at end of line. */
|
|
static void remove_linecont(char *line)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; line[i]; i++) {
|
|
if (line[i] == '\\' && line[i + 1] == '\n')
|
|
line[i++] = ' ';
|
|
}
|
|
}
|
|
|
|
/* Allocate and initialize a struct modprobe_line from a line of text. */
|
|
static void set_line_content(struct modprobe_line *m, char *line)
|
|
{
|
|
char c, *copy;
|
|
|
|
m->line = misc_strdup(line);
|
|
if (sscanf(line, " %c", &c) != 1) {
|
|
/* Empty line. */
|
|
m->argv = misc_malloc(sizeof(char *));
|
|
m->argv[0] = misc_strdup("");
|
|
m->argc = 1;
|
|
} else if (c == '#') {
|
|
/* Comment line. */
|
|
m->argv = misc_malloc(sizeof(char *));
|
|
m->argv[0] = misc_strdup("#");
|
|
m->argc = 1;
|
|
} else {
|
|
/* Line containing a command. */
|
|
copy = misc_strdup(line);
|
|
remove_linecont(copy);
|
|
line_split(copy, &m->argc, &m->argv);
|
|
free(copy);
|
|
}
|
|
}
|
|
|
|
/* Allocate and initialize a struct modprobe_line from a line of text. */
|
|
static struct modprobe_line *modprobe_line_new(char *line)
|
|
{
|
|
struct modprobe_line *m;
|
|
|
|
m = misc_malloc(sizeof(struct modprobe_line));
|
|
set_line_content(m, line);
|
|
|
|
return m;
|
|
}
|
|
|
|
/* Release all resources associated with a modprobe_line. */
|
|
static void modprobe_line_free(struct modprobe_line *m)
|
|
{
|
|
int i;
|
|
|
|
if (!m)
|
|
return;
|
|
free(m->line);
|
|
for (i = 0; i < m->argc; i++)
|
|
free(m->argv[i]);
|
|
free(m->argv);
|
|
free(m);
|
|
}
|
|
|
|
/* Create and initialize a new modprobe_file. */
|
|
static struct modprobe_file *modprobe_file_new(const char *path)
|
|
{
|
|
struct modprobe_file *file;
|
|
|
|
file = misc_malloc(sizeof(struct modprobe_file));
|
|
file->path = misc_strdup(path);
|
|
util_list_init(&file->lines, struct modprobe_line, node);
|
|
|
|
return file;
|
|
}
|
|
|
|
/* Release resources used by file. */
|
|
static void modprobe_file_free(struct modprobe_file *file)
|
|
{
|
|
struct modprobe_line *l, *n;
|
|
|
|
if (!file)
|
|
return;
|
|
free(file->path);
|
|
util_list_iterate_safe(&file->lines, l, n) {
|
|
util_list_remove(&file->lines, l);
|
|
modprobe_line_free(l);
|
|
}
|
|
|
|
free(file);
|
|
}
|
|
|
|
/* Add a new line to the file. */
|
|
static void modprobe_file_add(struct modprobe_file *file,
|
|
struct modprobe_line *line)
|
|
{
|
|
util_list_add_tail(&file->lines, line);
|
|
}
|
|
|
|
#define MODPROBE_MAX_LINE 2048
|
|
|
|
/* Read a modprobe.conf file and return a modprobe_file. */
|
|
static exit_code_t modprobe_read(const char *path, struct modprobe_file **mf)
|
|
{
|
|
char line[MODPROBE_MAX_LINE];
|
|
char *l, *last;
|
|
int len;
|
|
FILE *fd;
|
|
struct modprobe_file *mfile;
|
|
|
|
debug("Reading udev file %s\n", path);
|
|
mfile = modprobe_file_new(path);
|
|
fd = misc_fopen(path, "r");
|
|
if (!fd) {
|
|
error("Could not read file %s: %s\n", path, strerror(errno));
|
|
modprobe_file_free(mfile);
|
|
return EXIT_RUNTIME_ERROR;
|
|
}
|
|
last = NULL;
|
|
while (fgets(line, sizeof(line), fd)) {
|
|
if (last) {
|
|
/* The previous line had a continuation mark. */
|
|
l = misc_asprintf("%s%s", last, line);
|
|
free(last);
|
|
last = NULL;
|
|
} else
|
|
l = misc_strdup(line);
|
|
len = strlen(l);
|
|
if (len > 2 && line[len - 1] == '\n' && line[len - 2] == '\\') {
|
|
/* This line is continued on the next one. */
|
|
last = l;
|
|
continue;
|
|
}
|
|
modprobe_file_add(mfile, modprobe_line_new(l));
|
|
free(l);
|
|
}
|
|
if (last) {
|
|
/* Handle last line with a broken continuation mark
|
|
* gracefully. */
|
|
modprobe_file_add(mfile, modprobe_line_new(last));
|
|
free(last);
|
|
}
|
|
if (misc_fclose(fd))
|
|
warn("Could not close file %s: %s\n", path, strerror(errno));
|
|
*mf = mfile;
|
|
|
|
return EXIT_OK;
|
|
}
|
|
|
|
/* Check modprobe file for a leading chzdev comment. */
|
|
static bool find_chzdev_comment(struct modprobe_file *mf)
|
|
{
|
|
struct modprobe_line *l;
|
|
|
|
util_list_iterate(&mf->lines, l) {
|
|
/* Newly created line at start. */
|
|
if (!l->line)
|
|
return false;
|
|
/* Skip empty line. */
|
|
if (strlen(l->argv[0]) == 0)
|
|
continue;
|
|
/* Non-comment line. */
|
|
if (strcmp(l->argv[0], "#") != 0)
|
|
return false;
|
|
/* Check for chzdev comment. */
|
|
if (strstr(l->line, "chzdev"))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Write a modprobe.conf file. */
|
|
static exit_code_t modprobe_write(struct modprobe_file *mf)
|
|
{
|
|
FILE *fd;
|
|
struct modprobe_line *l;
|
|
int i;
|
|
|
|
debug("Writing udev file %s\n", mf->path);
|
|
fd = misc_fopen(mf->path, "w");
|
|
if (!fd) {
|
|
error("Could not write to file %s: %s\n", mf->path,
|
|
strerror(errno));
|
|
return EXIT_RUNTIME_ERROR;
|
|
}
|
|
|
|
/* Add leading comment. */
|
|
if (!find_chzdev_comment(mf))
|
|
fprintf(fd, "# Generated by chzdev\n");
|
|
util_list_iterate(&mf->lines, l) {
|
|
if (l->line) {
|
|
/* Use existing line. */
|
|
fprintf(fd, "%s", l->line);
|
|
} else {
|
|
/* Create line by concatenating arguments. */
|
|
for (i = 0; i < l->argc; i++) {
|
|
fprintf(fd, "%s%s", i == 0 ? "" : " ",
|
|
l->argv[i]);
|
|
}
|
|
fprintf(fd, "\n");
|
|
}
|
|
}
|
|
if (misc_fclose(fd)) {
|
|
warn("Could not close file %s: %s\n", mf->path,
|
|
strerror(errno));
|
|
}
|
|
|
|
return EXIT_OK;
|
|
}
|
|
|
|
/* Convert a single modprobe.conf option argument into a newly allocated
|
|
* struct setting. */
|
|
static struct setting *arg_to_setting(struct attrib **attribs, char *arg)
|
|
{
|
|
struct setting *s;
|
|
struct attrib *a;
|
|
char *key;
|
|
char *val;
|
|
|
|
key = misc_strdup(arg);
|
|
val = strchr(key, '=');
|
|
if (val) {
|
|
*val = 0;
|
|
val++;
|
|
} else {
|
|
/* Assume boolean parameter. */
|
|
val = "1";
|
|
}
|
|
|
|
/* Get attribute pointer for known attributes. */
|
|
a = attrib_find(attribs, key);
|
|
s = setting_new(a, key, val);
|
|
free(key);
|
|
|
|
return s;
|
|
}
|
|
|
|
/* Return a newly allocated struct setting_list for module options found
|
|
* in modprobe.conf file for specified module name. */
|
|
static struct setting_list *modprobe_get_settings(struct modprobe_file *mf,
|
|
const char *mod,
|
|
struct attrib **attribs)
|
|
{
|
|
struct setting_list *sl;
|
|
struct modprobe_line *l;
|
|
struct setting *s;
|
|
int i;
|
|
|
|
sl = setting_list_new();
|
|
util_list_iterate(&mf->lines, l) {
|
|
/* argv[]="option", "<mod>", "<key>=<value>", ... */
|
|
if (l->argc < 3)
|
|
continue;
|
|
if (strcmp(l->argv[0], "options") != 0)
|
|
continue;
|
|
if (strcmp(l->argv[1], mod) != 0)
|
|
continue;
|
|
for (i = 2; i < l->argc; i++) {
|
|
s = arg_to_setting(attribs, l->argv[i]);
|
|
setting_list_add(sl, s);
|
|
}
|
|
}
|
|
|
|
return sl;
|
|
}
|
|
|
|
/* Apply settings list as module parameters to modprobe file. */
|
|
static void modprobe_apply_settings(struct modprobe_file *mf, const char *mod,
|
|
struct setting_list *sl)
|
|
{
|
|
struct modprobe_line *l, *n;
|
|
struct setting *s;
|
|
unsigned long num;
|
|
|
|
/* Remove all lines containing parameters for this module. */
|
|
util_list_iterate_safe(&mf->lines, l, n) {
|
|
if (l->argc < 2)
|
|
continue;
|
|
if (strcmp(l->argv[0], "options") != 0)
|
|
continue;
|
|
if (strcmp(l->argv[1], mod) != 0)
|
|
continue;
|
|
util_list_remove(&mf->lines, l);
|
|
modprobe_line_free(l);
|
|
}
|
|
|
|
/* Add new line with specified settings. */
|
|
num = 0;
|
|
util_list_iterate(&sl->list, s) {
|
|
if (setting_is_set(s))
|
|
num++;
|
|
}
|
|
if (num == 0)
|
|
return;
|
|
l = misc_malloc(sizeof(struct modprobe_line));
|
|
l->argv = misc_malloc(sizeof(char *) * (num + 2));
|
|
l->argv[0] = misc_strdup("options");
|
|
l->argv[1] = misc_strdup(mod);
|
|
l->argc = 2;
|
|
util_list_iterate(&sl->list, s) {
|
|
if (!setting_is_set(s))
|
|
continue;
|
|
l->argv[(l->argc)++] = misc_asprintf("%s=%s", s->name,
|
|
s->value);
|
|
}
|
|
modprobe_file_add(mf, l);
|
|
}
|
|
|
|
/* Read attribute settings from a modprobe.conf file into a newly
|
|
* allocated struct setting_list. */
|
|
exit_code_t modprobe_read_settings(const char *path, const char *mod,
|
|
struct attrib **attribs,
|
|
struct setting_list **settings)
|
|
{
|
|
struct modprobe_file *mf;
|
|
exit_code_t rc;
|
|
|
|
if (!util_path_is_reg_file(path)) {
|
|
*settings = NULL;
|
|
return EXIT_OK;
|
|
}
|
|
rc = modprobe_read(path, &mf);
|
|
if (rc)
|
|
return rc;
|
|
|
|
*settings = modprobe_get_settings(mf, mod, attribs);
|
|
modprobe_file_free(mf);
|
|
|
|
return EXIT_OK;
|
|
}
|
|
|
|
/* Write attribute settings to a modprobe.conf file.*/
|
|
exit_code_t modprobe_write_settings(const char *path, const char *mod,
|
|
struct setting_list *settings)
|
|
{
|
|
struct modprobe_file *mf;
|
|
exit_code_t rc;
|
|
unsigned long lines;
|
|
|
|
if (util_path_is_reg_file(path)) {
|
|
rc = modprobe_read(path, &mf);
|
|
if (rc)
|
|
return rc;
|
|
} else {
|
|
rc = path_create(path);
|
|
if (rc)
|
|
return rc;
|
|
mf = modprobe_file_new(path);
|
|
}
|
|
|
|
modprobe_apply_settings(mf, mod, settings);
|
|
|
|
lines = util_list_len(&mf->lines);
|
|
if (lines == 0 || (lines == 1 && find_chzdev_comment(mf))) {
|
|
/* Do not write empty files. */
|
|
if (util_path_is_reg_file(path))
|
|
rc = remove_file(path);
|
|
} else
|
|
rc = modprobe_write(mf);
|
|
modprobe_file_free(mf);
|
|
|
|
return rc;
|
|
}
|