Files
s390-tools/zdev/src/modprobe.c
Rafael Fonseca d542138868 zdev: use libutil provided path functions
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>
2017-11-27 16:59:31 +01:00

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;
}