mirror of
https://github.com/alsa-project/alsa-utils
synced 2024-12-23 00:36:31 +01:00
c68a3d02d1
When alsactl init is invoked and a directory path is passed to INCLUDE command in the config file, read only *.conf files in that directory. This will avoid reading backup files or invalid files that have been created accidentally. Also by using scandir() with alphasort(), alsactl reads the files in alphabetical order. Thus it's highly recommended to use some number prefix to the file name for assuring the order. Signed-off-by: Takashi Iwai <tiwai@suse.de>
1792 lines
46 KiB
C
1792 lines
46 KiB
C
/*
|
|
* Advanced Linux Sound Architecture Control Program - Parse initialization files
|
|
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
|
|
* Greg Kroah-Hartman <greg@kroah.com>,
|
|
* Kay Sievers <kay.sievers@vrfy.org>
|
|
*
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stddef.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <fnmatch.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/un.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/select.h>
|
|
#include <sys/types.h>
|
|
#include <dirent.h>
|
|
#include <math.h>
|
|
#include <alsa/asoundlib.h>
|
|
#include "aconfig.h"
|
|
#include "alsactl.h"
|
|
#include "list.h"
|
|
|
|
#define PATH_SIZE 512
|
|
#define NAME_SIZE 128
|
|
#define EJUSTRETURN 0x7fffffff
|
|
|
|
enum key_op {
|
|
KEY_OP_UNSET,
|
|
KEY_OP_MATCH,
|
|
KEY_OP_NOMATCH,
|
|
KEY_OP_ADD,
|
|
KEY_OP_ASSIGN,
|
|
KEY_OP_ASSIGN_FINAL
|
|
};
|
|
|
|
struct pair {
|
|
char *key;
|
|
char *value;
|
|
struct pair *next;
|
|
};
|
|
|
|
struct space {
|
|
struct pair *pairs;
|
|
char *rootdir;
|
|
char *go_to;
|
|
char *program_result;
|
|
const char *filename;
|
|
int linenum;
|
|
int log_run;
|
|
int exit_code;
|
|
int quit;
|
|
unsigned int ctl_id_changed;
|
|
snd_hctl_t *ctl_handle;
|
|
snd_ctl_card_info_t *ctl_card_info;
|
|
snd_ctl_elem_id_t *ctl_id;
|
|
snd_ctl_elem_info_t *ctl_info;
|
|
snd_ctl_elem_value_t *ctl_value;
|
|
};
|
|
|
|
static void Perror(struct space *space, const char *fmt, ...)
|
|
{
|
|
va_list arg;
|
|
va_start(arg, fmt);
|
|
fprintf(stderr, "%s:%i: ", space->filename, space->linenum);
|
|
vfprintf(stderr, fmt, arg);
|
|
putc('\n', stderr);
|
|
va_end(arg);
|
|
}
|
|
|
|
#include "init_sysdeps.c"
|
|
#include "init_utils_string.c"
|
|
#include "init_utils_run.c"
|
|
#include "init_sysfs.c"
|
|
|
|
static void free_space(struct space *space)
|
|
{
|
|
struct pair *pair = space->pairs;
|
|
struct pair *next = pair;
|
|
|
|
while (next) {
|
|
pair = next;
|
|
next = pair->next;
|
|
free(pair->value);
|
|
free(pair->key);
|
|
free(pair);
|
|
}
|
|
space->pairs = NULL;
|
|
if (space->ctl_value) {
|
|
snd_ctl_elem_value_free(space->ctl_value);
|
|
space->ctl_value = NULL;
|
|
}
|
|
if (space->ctl_info) {
|
|
snd_ctl_elem_info_free(space->ctl_info);
|
|
space->ctl_info = NULL;
|
|
}
|
|
if (space->ctl_id) {
|
|
snd_ctl_elem_id_free(space->ctl_id);
|
|
space->ctl_id = NULL;
|
|
}
|
|
if (space->ctl_card_info) {
|
|
snd_ctl_card_info_free(space->ctl_card_info);
|
|
space->ctl_card_info = NULL;
|
|
}
|
|
if (space->ctl_handle) {
|
|
snd_hctl_close(space->ctl_handle);
|
|
space->ctl_handle = NULL;
|
|
}
|
|
if (space->rootdir)
|
|
free(space->rootdir);
|
|
if (space->program_result)
|
|
free(space->program_result);
|
|
if (space->go_to)
|
|
free(space->go_to);
|
|
free(space);
|
|
}
|
|
|
|
static struct pair *value_find(struct space *space, const char *key)
|
|
{
|
|
struct pair *pair = space->pairs;
|
|
|
|
while (pair && strcmp(pair->key, key) != 0)
|
|
pair = pair->next;
|
|
return pair;
|
|
}
|
|
|
|
static int value_set(struct space *space, const char *key, const char *value)
|
|
{
|
|
struct pair *pair;
|
|
|
|
pair = value_find(space, key);
|
|
if (pair) {
|
|
free(pair->value);
|
|
pair->value = strdup(value);
|
|
if (pair->value == NULL)
|
|
return -ENOMEM;
|
|
} else {
|
|
pair = malloc(sizeof(struct pair));
|
|
if (pair == NULL)
|
|
return -ENOMEM;
|
|
pair->key = strdup(key);
|
|
if (pair->key == NULL) {
|
|
free(pair);
|
|
return -ENOMEM;
|
|
}
|
|
pair->value = strdup(value);
|
|
if (pair->value == NULL) {
|
|
free(pair->key);
|
|
free(pair);
|
|
return -ENOMEM;
|
|
}
|
|
pair->next = space->pairs;
|
|
space->pairs = pair;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int init_space(struct space **space, int card)
|
|
{
|
|
struct space *res;
|
|
char device[16];
|
|
int err;
|
|
|
|
res = calloc(1, sizeof(struct space));
|
|
if (res == NULL)
|
|
return -ENOMEM;
|
|
res->ctl_id_changed = ~0;
|
|
res->linenum = -1;
|
|
sprintf(device, "hw:%u", card);
|
|
err = snd_hctl_open(&res->ctl_handle, device, 0);
|
|
if (err < 0)
|
|
goto error;
|
|
err = snd_hctl_load(res->ctl_handle);
|
|
if (err < 0)
|
|
goto error;
|
|
err = snd_ctl_card_info_malloc(&res->ctl_card_info);
|
|
if (err < 0)
|
|
goto error;
|
|
err = snd_ctl_card_info(snd_hctl_ctl(res->ctl_handle), res->ctl_card_info);
|
|
if (err < 0)
|
|
goto error;
|
|
err = snd_ctl_elem_id_malloc(&res->ctl_id);
|
|
if (err < 0)
|
|
goto error;
|
|
err = snd_ctl_elem_info_malloc(&res->ctl_info);
|
|
if (err < 0)
|
|
goto error;
|
|
err = snd_ctl_elem_value_malloc(&res->ctl_value);
|
|
if (err < 0)
|
|
goto error;
|
|
*space = res;
|
|
return 0;
|
|
error:
|
|
free_space(res);
|
|
return err;
|
|
}
|
|
|
|
static const char *cardinfo_get(struct space *space, const char *attr)
|
|
{
|
|
if (strncasecmp(attr, "CARD", 4) == 0) {
|
|
static char res[16];
|
|
sprintf(res, "%u", snd_ctl_card_info_get_card(space->ctl_card_info));
|
|
return res;
|
|
}
|
|
if (strncasecmp(attr, "ID", 2) == 0)
|
|
return snd_ctl_card_info_get_id(space->ctl_card_info);
|
|
if (strncasecmp(attr, "DRIVER", 6) == 0)
|
|
return snd_ctl_card_info_get_driver(space->ctl_card_info);
|
|
if (strncasecmp(attr, "NAME", 4) == 0)
|
|
return snd_ctl_card_info_get_name(space->ctl_card_info);
|
|
if (strncasecmp(attr, "LONGNAME", 8) == 0)
|
|
return snd_ctl_card_info_get_longname(space->ctl_card_info);
|
|
if (strncasecmp(attr, "MIXERNAME", 9) == 0)
|
|
return snd_ctl_card_info_get_mixername(space->ctl_card_info);
|
|
if (strncasecmp(attr, "COMPONENTS", 10) == 0)
|
|
return snd_ctl_card_info_get_components(space->ctl_card_info);
|
|
Perror(space, "unknown cardinfo{} attribute '%s'", attr);
|
|
return NULL;
|
|
}
|
|
|
|
static int check_id_changed(struct space *space, unsigned int what)
|
|
{
|
|
snd_hctl_elem_t *elem;
|
|
int err;
|
|
|
|
if ((space->ctl_id_changed & what & 1) != 0) {
|
|
snd_ctl_elem_id_set_numid(space->ctl_id, 0);
|
|
elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
|
|
if (!elem)
|
|
return -ENOENT;
|
|
err = snd_hctl_elem_info(elem, space->ctl_info);
|
|
if (err == 0)
|
|
space->ctl_id_changed &= ~1;
|
|
return err;
|
|
}
|
|
if ((space->ctl_id_changed & what & 2) != 0) {
|
|
snd_ctl_elem_id_set_numid(space->ctl_id, 0);
|
|
elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
|
|
if (!elem)
|
|
return -ENOENT;
|
|
err = snd_hctl_elem_read(elem, space->ctl_value);
|
|
if (err == 0)
|
|
space->ctl_id_changed &= ~2;
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const char *get_ctl_value(struct space *space)
|
|
{
|
|
snd_ctl_elem_type_t type;
|
|
unsigned int idx, count;
|
|
static char res[1024], tmp[16];
|
|
static const char hex[] = "0123456789abcdef";
|
|
char *pos;
|
|
const char *pos1;
|
|
|
|
type = snd_ctl_elem_info_get_type(space->ctl_info);
|
|
count = snd_ctl_elem_info_get_count(space->ctl_info);
|
|
res[0] = '\0';
|
|
switch (type) {
|
|
case SND_CTL_ELEM_TYPE_BOOLEAN:
|
|
for (idx = 0; idx < count; idx++) {
|
|
if (idx > 0)
|
|
strlcat(res, ",", sizeof(res));
|
|
strlcat(res, snd_ctl_elem_value_get_boolean(space->ctl_value, idx) ? "on" : "off", sizeof(res));
|
|
}
|
|
break;
|
|
case SND_CTL_ELEM_TYPE_INTEGER:
|
|
for (idx = 0; idx < count; idx++) {
|
|
if (idx > 0)
|
|
strlcat(res, ",", sizeof(res));
|
|
snprintf(tmp, sizeof(tmp), "%li", snd_ctl_elem_value_get_integer(space->ctl_value, idx));
|
|
strlcat(res, tmp, sizeof(res));
|
|
}
|
|
break;
|
|
case SND_CTL_ELEM_TYPE_INTEGER64:
|
|
for (idx = 0; idx < count; idx++) {
|
|
if (idx > 0)
|
|
strlcat(res, ",", sizeof(res));
|
|
snprintf(tmp, sizeof(tmp), "%lli", snd_ctl_elem_value_get_integer64(space->ctl_value, idx));
|
|
strlcat(res, tmp, sizeof(res));
|
|
}
|
|
break;
|
|
case SND_CTL_ELEM_TYPE_ENUMERATED:
|
|
for (idx = 0; idx < count; idx++) {
|
|
if (idx > 0)
|
|
strlcat(res, ",", sizeof(res));
|
|
snprintf(tmp, sizeof(tmp), "%u", snd_ctl_elem_value_get_enumerated(space->ctl_value, idx));
|
|
strlcat(res, tmp, sizeof(res));
|
|
}
|
|
break;
|
|
case SND_CTL_ELEM_TYPE_BYTES:
|
|
case SND_CTL_ELEM_TYPE_IEC958:
|
|
if (type == SND_CTL_ELEM_TYPE_IEC958)
|
|
count = sizeof(snd_aes_iec958_t);
|
|
if (count > (sizeof(res)-1)/2)
|
|
count = (sizeof(res)-1/2);
|
|
pos = res;
|
|
pos1 = snd_ctl_elem_value_get_bytes(space->ctl_value);
|
|
while (count > 0) {
|
|
idx = *pos1++;
|
|
*pos++ = hex[idx >> 4];
|
|
*pos++ = hex[idx & 0x0f];
|
|
count++;
|
|
}
|
|
*pos++ = '\0';
|
|
break;
|
|
default:
|
|
Perror(space, "unknown element type '%i'", type);
|
|
return NULL;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/* Function to convert from percentage to volume. val = percentage */
|
|
#define convert_prange1(val, min, max) \
|
|
ceil((val) * ((max) - (min)) * 0.01 + (min))
|
|
|
|
static int set_ctl_value(struct space *space, const char *value, int all)
|
|
{
|
|
snd_ctl_elem_type_t type;
|
|
unsigned int idx, idx2, count, items;
|
|
const char *pos, *pos2;
|
|
snd_hctl_elem_t *elem;
|
|
int val;
|
|
long lval;
|
|
|
|
type = snd_ctl_elem_info_get_type(space->ctl_info);
|
|
count = snd_ctl_elem_info_get_count(space->ctl_info);
|
|
switch (type) {
|
|
case SND_CTL_ELEM_TYPE_BOOLEAN:
|
|
for (idx = 0; idx < count; idx++) {
|
|
while (*value == ' ')
|
|
value++;
|
|
if (*value == '\0')
|
|
goto missing;
|
|
val = strncasecmp(value, "true", 4) == 0 ||
|
|
strncasecmp(value, "yes", 3) == 0 ||
|
|
strncasecmp(value, "on", 2) == 0 ||
|
|
strncasecmp(value, "1", 1) == 0;
|
|
snd_ctl_elem_value_set_boolean(space->ctl_value, idx, val);
|
|
if (all)
|
|
continue;
|
|
pos = strchr(value, ',');
|
|
value = pos ? pos + 1 : value + strlen(value) - 1;
|
|
}
|
|
break;
|
|
case SND_CTL_ELEM_TYPE_INTEGER:
|
|
for (idx = 0; idx < count; idx++) {
|
|
while (*value == ' ')
|
|
value++;
|
|
pos = strchr(value, ',');
|
|
if (pos)
|
|
*(char *)pos = '\0';
|
|
remove_trailing_chars((char *)value, ' ');
|
|
items = pos ? pos - value : strlen(value);
|
|
if (items > 1 && value[items-1] == '%') {
|
|
val = convert_prange1(strtol(value, NULL, 0), snd_ctl_elem_info_get_min(space->ctl_info), snd_ctl_elem_info_get_max(space->ctl_info));
|
|
snd_ctl_elem_value_set_integer(space->ctl_value, idx, val);
|
|
} else if (items > 2 && value[items-2] == 'd' && value[items-1] == 'B') {
|
|
val = strtol(value, NULL, 0) * 100;
|
|
if ((pos2 = strchr(value, '.')) != NULL) {
|
|
if (isdigit(*(pos2-1)) && isdigit(*(pos2-2))) {
|
|
if (val < 0)
|
|
val -= strtol(pos2 + 1, NULL, 0);
|
|
else
|
|
val += strtol(pos2 + 1, NULL, 0);
|
|
} else if (isdigit(*(pos2-1))) {
|
|
if (val < 0)
|
|
val -= strtol(pos2 + 1, NULL, 0) * 10;
|
|
else
|
|
val += strtol(pos2 + 1, NULL, 0) * 10;
|
|
}
|
|
}
|
|
val = snd_ctl_convert_from_dB(snd_hctl_ctl(space->ctl_handle), space->ctl_id, val, &lval, -1);
|
|
if (val < 0) {
|
|
dbg("unable to convert dB value '%s' to internal integer range", value);
|
|
return val;
|
|
}
|
|
snd_ctl_elem_value_set_integer(space->ctl_value, idx, lval);
|
|
} else {
|
|
snd_ctl_elem_value_set_integer(space->ctl_value, idx, strtol(value, NULL, 0));
|
|
}
|
|
if (all)
|
|
continue;
|
|
value = pos ? pos + 1 : value + strlen(value) - 1;
|
|
}
|
|
break;
|
|
case SND_CTL_ELEM_TYPE_INTEGER64:
|
|
for (idx = 0; idx < count; idx++) {
|
|
while (*value == ' ')
|
|
value++;
|
|
snd_ctl_elem_value_set_integer64(space->ctl_value, idx, strtoll(value, NULL, 0));
|
|
if (all)
|
|
continue;
|
|
pos = strchr(value, ',');
|
|
value = pos ? pos + 1 : value + strlen(value) - 1;
|
|
}
|
|
break;
|
|
case SND_CTL_ELEM_TYPE_ENUMERATED:
|
|
for (idx = 0; idx < count; idx++) {
|
|
while (*value == ' ')
|
|
value++;
|
|
pos = strchr(value, ',');
|
|
if (isdigit(value[0]) || value[0] == '-') {
|
|
snd_ctl_elem_value_set_enumerated(space->ctl_value, idx, strtol(value, NULL, 0));
|
|
} else {
|
|
if (pos)
|
|
*(char *)pos = '\0';
|
|
remove_trailing_chars((char *)value, ' ');
|
|
items = snd_ctl_elem_info_get_items(space->ctl_info);
|
|
for (idx2 = 0; idx2 < items; idx2++) {
|
|
snd_ctl_elem_info_set_item(space->ctl_info, idx2);
|
|
elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
|
|
if (elem == NULL)
|
|
return -ENOENT;
|
|
val = snd_hctl_elem_info(elem, space->ctl_info);
|
|
if (val < 0)
|
|
return val;
|
|
if (strcasecmp(snd_ctl_elem_info_get_item_name(space->ctl_info), value) == 0) {
|
|
snd_ctl_elem_value_set_enumerated(space->ctl_value, idx, idx2);
|
|
break;
|
|
}
|
|
}
|
|
if (idx2 >= items) {
|
|
Perror(space, "wrong enum identifier '%s'", value);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
if (all)
|
|
continue;
|
|
value = pos ? pos + 1 : value + strlen(value) - 1;
|
|
}
|
|
break;
|
|
case SND_CTL_ELEM_TYPE_BYTES:
|
|
case SND_CTL_ELEM_TYPE_IEC958:
|
|
if (type == SND_CTL_ELEM_TYPE_IEC958)
|
|
count = sizeof(snd_aes_iec958_t);
|
|
while (*value == ' ')
|
|
value++;
|
|
if (strlen(value) != count * 2) {
|
|
Perror(space, "bad ctl value hexa length (should be %u bytes)", count);
|
|
return -EINVAL;
|
|
}
|
|
for (idx = 0; idx < count; idx += 2) {
|
|
val = hextodigit(*(value++)) << 4;
|
|
val |= hextodigit(*(value++));
|
|
if (val > 255) {
|
|
Perror(space, "bad ctl hexa value");
|
|
return -EINVAL;
|
|
}
|
|
snd_ctl_elem_value_set_byte(space->ctl_value, idx, val);
|
|
}
|
|
break;
|
|
default:
|
|
Perror(space, "unknown element type '%i'", type);
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
missing:
|
|
printf("%i %i\n", type, count);
|
|
Perror(space, "missing some ctl values (line %i)", space->linenum);
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int do_match(const char *key, enum key_op op,
|
|
const char *key_value, const char *value)
|
|
{
|
|
int match;
|
|
|
|
if (value == NULL)
|
|
return 0;
|
|
dbg("match %s '%s' <-> '%s'", key, key_value, value);
|
|
match = fnmatch(key_value, value, 0) == 0;
|
|
if (match && op == KEY_OP_MATCH) {
|
|
dbg("%s is true (matching value)", key);
|
|
return 1;
|
|
}
|
|
if (!match && op == KEY_OP_NOMATCH) {
|
|
dbg("%s is true (non-matching value)", key);
|
|
return 1;
|
|
}
|
|
dbg("%s is false", key);
|
|
return 0;
|
|
}
|
|
|
|
static int ctl_match(snd_ctl_elem_id_t *pattern, snd_ctl_elem_id_t *id)
|
|
{
|
|
if (snd_ctl_elem_id_get_interface(pattern) != -1 &&
|
|
snd_ctl_elem_id_get_interface(pattern) != snd_ctl_elem_id_get_interface(id))
|
|
return 0;
|
|
if (snd_ctl_elem_id_get_device(pattern) != -1 &&
|
|
snd_ctl_elem_id_get_device(pattern) != snd_ctl_elem_id_get_device(id))
|
|
return 0;
|
|
if (snd_ctl_elem_id_get_subdevice(pattern) != -1 &&
|
|
snd_ctl_elem_id_get_subdevice(pattern) != snd_ctl_elem_id_get_subdevice(id))
|
|
return 0;
|
|
if (snd_ctl_elem_id_get_index(pattern) != -1 &&
|
|
snd_ctl_elem_id_get_index(pattern) != snd_ctl_elem_id_get_index(id))
|
|
return 0;
|
|
if (fnmatch(snd_ctl_elem_id_get_name(pattern), snd_ctl_elem_id_get_name(id), 0) != 0)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static const char *elemid_get(struct space *space, const char *attr)
|
|
{
|
|
long long val;
|
|
snd_ctl_elem_type_t type;
|
|
static char res[256];
|
|
|
|
if (strncasecmp(attr, "numid", 5) == 0) {
|
|
val = snd_ctl_elem_id_get_numid(space->ctl_id);
|
|
goto value;
|
|
}
|
|
if (strncasecmp(attr, "iface", 5) == 0 ||
|
|
strncasecmp(attr, "interface", 9) == 0)
|
|
return snd_ctl_elem_iface_name(snd_ctl_elem_id_get_interface(space->ctl_id));
|
|
if (strncasecmp(attr, "device", 6) == 0) {
|
|
val = snd_ctl_elem_id_get_device(space->ctl_id);
|
|
goto value;
|
|
}
|
|
if (strncasecmp(attr, "subdev", 6) == 0) {
|
|
val = snd_ctl_elem_id_get_subdevice(space->ctl_id);
|
|
goto value;
|
|
}
|
|
if (strncasecmp(attr, "name", 4) == 0)
|
|
return snd_ctl_elem_id_get_name(space->ctl_id);
|
|
if (strncasecmp(attr, "index", 5) == 0) {
|
|
val = snd_ctl_elem_id_get_index(space->ctl_id);
|
|
goto value;
|
|
}
|
|
if (strncasecmp(attr, "type", 4) == 0) {
|
|
if (check_id_changed(space, 1))
|
|
return NULL;
|
|
return snd_ctl_elem_type_name(snd_ctl_elem_info_get_type(space->ctl_info));
|
|
}
|
|
if (strncasecmp(attr, "attr", 4) == 0) {
|
|
if (check_id_changed(space, 1))
|
|
return NULL;
|
|
res[0] = '\0';
|
|
if (snd_ctl_elem_info_is_readable(space->ctl_info))
|
|
strcat(res, "r");
|
|
if (snd_ctl_elem_info_is_writable(space->ctl_info))
|
|
strcat(res, "w");
|
|
if (snd_ctl_elem_info_is_volatile(space->ctl_info))
|
|
strcat(res, "v");
|
|
if (snd_ctl_elem_info_is_inactive(space->ctl_info))
|
|
strcat(res, "i");
|
|
if (snd_ctl_elem_info_is_locked(space->ctl_info))
|
|
strcat(res, "l");
|
|
if (snd_ctl_elem_info_is_tlv_readable(space->ctl_info))
|
|
strcat(res, "R");
|
|
if (snd_ctl_elem_info_is_tlv_writable(space->ctl_info))
|
|
strcat(res, "W");
|
|
if (snd_ctl_elem_info_is_tlv_commandable(space->ctl_info))
|
|
strcat(res, "C");
|
|
if (snd_ctl_elem_info_is_owner(space->ctl_info))
|
|
strcat(res, "o");
|
|
if (snd_ctl_elem_info_is_user(space->ctl_info))
|
|
strcat(res, "u");
|
|
return res;
|
|
}
|
|
if (strncasecmp(attr, "owner", 5) == 0) {
|
|
if (check_id_changed(space, 1))
|
|
return NULL;
|
|
val = snd_ctl_elem_info_get_owner(space->ctl_info);
|
|
goto value;
|
|
}
|
|
if (strncasecmp(attr, "count", 5) == 0) {
|
|
if (check_id_changed(space, 1))
|
|
return NULL;
|
|
val = snd_ctl_elem_info_get_count(space->ctl_info);
|
|
goto value;
|
|
}
|
|
if (strncasecmp(attr, "min", 3) == 0) {
|
|
if (check_id_changed(space, 1))
|
|
return NULL;
|
|
type = snd_ctl_elem_info_get_type(space->ctl_info);
|
|
if (type == SND_CTL_ELEM_TYPE_INTEGER64)
|
|
val = snd_ctl_elem_info_get_min64(space->ctl_info);
|
|
else if (type == SND_CTL_ELEM_TYPE_INTEGER)
|
|
val = snd_ctl_elem_info_get_min(space->ctl_info);
|
|
else
|
|
goto empty;
|
|
goto value;
|
|
}
|
|
if (strncasecmp(attr, "max", 3) == 0) {
|
|
if (check_id_changed(space, 1))
|
|
return NULL;
|
|
type = snd_ctl_elem_info_get_type(space->ctl_info);
|
|
if (type == SND_CTL_ELEM_TYPE_INTEGER64)
|
|
val = snd_ctl_elem_info_get_max64(space->ctl_info);
|
|
else if (type == SND_CTL_ELEM_TYPE_INTEGER)
|
|
val = snd_ctl_elem_info_get_max(space->ctl_info);
|
|
else
|
|
goto empty;
|
|
goto value;
|
|
}
|
|
if (strncasecmp(attr, "step", 3) == 0) {
|
|
if (check_id_changed(space, 1))
|
|
return NULL;
|
|
type = snd_ctl_elem_info_get_type(space->ctl_info);
|
|
if (type == SND_CTL_ELEM_TYPE_INTEGER64)
|
|
val = snd_ctl_elem_info_get_step64(space->ctl_info);
|
|
else if (type == SND_CTL_ELEM_TYPE_INTEGER)
|
|
val = snd_ctl_elem_info_get_step(space->ctl_info);
|
|
else
|
|
goto empty;
|
|
goto value;
|
|
}
|
|
if (strncasecmp(attr, "items", 5) == 0) {
|
|
if (check_id_changed(space, 1))
|
|
return NULL;
|
|
if (snd_ctl_elem_info_get_type(space->ctl_info) == SND_CTL_ELEM_TYPE_ENUMERATED)
|
|
val = snd_ctl_elem_info_get_items(space->ctl_info);
|
|
else {
|
|
empty:
|
|
res[0] = '\0';
|
|
return res;
|
|
}
|
|
goto value;
|
|
}
|
|
if (strncasecmp(attr, "value", 5) == 0) {
|
|
if (check_id_changed(space, 3))
|
|
return NULL;
|
|
return get_ctl_value(space);
|
|
}
|
|
if (strncasecmp(attr, "dBmin", 5) == 0) {
|
|
long min, max;
|
|
if (check_id_changed(space, 1))
|
|
return NULL;
|
|
if (snd_ctl_get_dB_range(snd_hctl_ctl(space->ctl_handle), space->ctl_id, &min, &max) < 0)
|
|
goto empty;
|
|
val = min;
|
|
dbvalue:
|
|
sprintf(res, "%li.%02idB", (long)(val / 100), (int)abs(val % 100));
|
|
return res;
|
|
}
|
|
if (strncasecmp(attr, "dBmax", 5) == 0) {
|
|
long min, max;
|
|
if (check_id_changed(space, 1))
|
|
return NULL;
|
|
if (snd_ctl_get_dB_range(snd_hctl_ctl(space->ctl_handle), space->ctl_id, &min, &max) < 0)
|
|
goto empty;
|
|
val = max;
|
|
goto dbvalue;
|
|
}
|
|
if (strncasecmp(attr, "enums", 5) == 0) {
|
|
unsigned int idx, items;
|
|
snd_hctl_elem_t *elem;
|
|
if (check_id_changed(space, 1))
|
|
return NULL;
|
|
if (snd_ctl_elem_info_get_type(space->ctl_info) != SND_CTL_ELEM_TYPE_ENUMERATED)
|
|
goto empty;
|
|
items = snd_ctl_elem_info_get_items(space->ctl_info);
|
|
strcpy(res, "|");
|
|
for (idx = 0; idx < items; idx++) {
|
|
snd_ctl_elem_info_set_item(space->ctl_info, idx);
|
|
elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
|
|
if (elem == NULL)
|
|
break;
|
|
if (snd_hctl_elem_info(elem, space->ctl_info) < 0)
|
|
break;
|
|
strlcat(res, snd_ctl_elem_info_get_item_name(space->ctl_info), sizeof(res));
|
|
strlcat(res, "|", sizeof(res));
|
|
}
|
|
return res;
|
|
}
|
|
if (strncasecmp(attr, "do_search", 9) == 0) {
|
|
int err, index = 0;
|
|
snd_hctl_elem_t *elem;
|
|
snd_ctl_elem_id_t *id;
|
|
char *pos = strchr(attr, ' ');
|
|
if (pos)
|
|
index = strtol(pos, NULL, 0);
|
|
err = snd_ctl_elem_id_malloc(&id);
|
|
if (err < 0)
|
|
return NULL;
|
|
elem = snd_hctl_first_elem(space->ctl_handle);
|
|
while (elem) {
|
|
snd_hctl_elem_get_id(elem, id);
|
|
if (!ctl_match(space->ctl_id, id))
|
|
goto next_search;
|
|
if (index > 0) {
|
|
index--;
|
|
goto next_search;
|
|
}
|
|
strcpy(res, "1");
|
|
snd_ctl_elem_id_copy(space->ctl_id, id);
|
|
snd_ctl_elem_id_free(id);
|
|
dbg("do_ctl_search found a control");
|
|
return res;
|
|
next_search:
|
|
elem = snd_hctl_elem_next(elem);
|
|
}
|
|
snd_ctl_elem_id_free(id);
|
|
strcpy(res, "0");
|
|
return res;
|
|
}
|
|
if (strncasecmp(attr, "do_count", 8) == 0) {
|
|
int err, index = 0;
|
|
snd_hctl_elem_t *elem;
|
|
snd_ctl_elem_id_t *id;
|
|
err = snd_ctl_elem_id_malloc(&id);
|
|
if (err < 0)
|
|
return NULL;
|
|
elem = snd_hctl_first_elem(space->ctl_handle);
|
|
while (elem) {
|
|
snd_hctl_elem_get_id(elem, id);
|
|
if (ctl_match(space->ctl_id, id))
|
|
index++;
|
|
elem = snd_hctl_elem_next(elem);
|
|
}
|
|
snd_ctl_elem_id_free(id);
|
|
sprintf(res, "%u", index);
|
|
dbg("do_ctl_count found %s controls", res);
|
|
return res;
|
|
}
|
|
Perror(space, "unknown ctl{} attribute '%s'", attr);
|
|
return NULL;
|
|
value:
|
|
sprintf(res, "%lli", val);
|
|
return res;
|
|
}
|
|
|
|
static int elemid_set(struct space *space, const char *attr, const char *value)
|
|
{
|
|
unsigned int val;
|
|
void (*fcn)(snd_ctl_elem_id_t *, unsigned int);
|
|
snd_ctl_elem_iface_t iface;
|
|
int err;
|
|
|
|
if (strncasecmp(attr, "numid", 5) == 0) {
|
|
fcn = snd_ctl_elem_id_set_numid;
|
|
goto value;
|
|
}
|
|
if (strncasecmp(attr, "iface", 5) == 0 ||
|
|
strncasecmp(attr, "interface", 9) == 0 ||
|
|
strncasecmp(attr, "reset", 5) == 0 ||
|
|
strncasecmp(attr, "search", 6) == 0) {
|
|
if (strlen(value) == 0 && strncasecmp(attr, "search", 6) == 0) {
|
|
iface = 0;
|
|
goto search;
|
|
}
|
|
for (iface = 0; iface <= SND_CTL_ELEM_IFACE_LAST; iface++) {
|
|
if (strcasecmp(value, snd_ctl_elem_iface_name(iface)) == 0) {
|
|
if (strncasecmp(attr, "reset", 5) == 0)
|
|
snd_ctl_elem_id_clear(space->ctl_id);
|
|
if (strncasecmp(attr, "search", 5) == 0) {
|
|
search:
|
|
snd_ctl_elem_id_clear(space->ctl_id);
|
|
/* -1 means all */
|
|
snd_ctl_elem_id_set_interface(space->ctl_id, -1);
|
|
snd_ctl_elem_id_set_device(space->ctl_id, -1);
|
|
snd_ctl_elem_id_set_subdevice(space->ctl_id, -1);
|
|
snd_ctl_elem_id_set_name(space->ctl_id, "*");
|
|
snd_ctl_elem_id_set_index(space->ctl_id, -1);
|
|
if (strlen(value) == 0)
|
|
return 0;
|
|
}
|
|
snd_ctl_elem_id_set_interface(space->ctl_id, iface);
|
|
space->ctl_id_changed = ~0;
|
|
return 0;
|
|
}
|
|
}
|
|
Perror(space, "unknown control interface name '%s'", value);
|
|
return -EINVAL;
|
|
}
|
|
if (strncasecmp(attr, "device", 6) == 0) {
|
|
fcn = snd_ctl_elem_id_set_device;
|
|
goto value;
|
|
}
|
|
if (strncasecmp(attr, "subdev", 6) == 0) {
|
|
fcn = snd_ctl_elem_id_set_subdevice;
|
|
goto value;
|
|
}
|
|
if (strncasecmp(attr, "name", 4) == 0) {
|
|
snd_ctl_elem_id_set_name(space->ctl_id, value);
|
|
space->ctl_id_changed = ~0;
|
|
return 0;
|
|
}
|
|
if (strncasecmp(attr, "index", 5) == 0) {
|
|
fcn = snd_ctl_elem_id_set_index;
|
|
goto value;
|
|
}
|
|
if (strncasecmp(attr, "values", 6) == 0 ||
|
|
strncasecmp(attr, "value", 5) == 0) {
|
|
err = check_id_changed(space, 1);
|
|
if (err < 0) {
|
|
Perror(space, "control element not found");
|
|
return err;
|
|
}
|
|
err = set_ctl_value(space, value, strncasecmp(attr, "values", 6) == 0);
|
|
if (err < 0) {
|
|
space->ctl_id_changed |= 2;
|
|
} else {
|
|
space->ctl_id_changed &= ~2;
|
|
snd_ctl_elem_value_set_id(space->ctl_value, space->ctl_id);
|
|
err = snd_ctl_elem_write(snd_hctl_ctl(space->ctl_handle), space->ctl_value);
|
|
if (err < 0) {
|
|
Perror(space, "value write error: %s", snd_strerror(err));
|
|
return err;
|
|
}
|
|
}
|
|
return err;
|
|
}
|
|
Perror(space, "unknown CTL{} attribute '%s'", attr);
|
|
return -EINVAL;
|
|
value:
|
|
val = (unsigned int)strtol(value, NULL, 0);
|
|
fcn(space->ctl_id, val);
|
|
space->ctl_id_changed = ~0;
|
|
return 0;
|
|
}
|
|
|
|
static int get_key(char **line, char **key, enum key_op *op, char **value)
|
|
{
|
|
char *linepos;
|
|
char *temp;
|
|
|
|
linepos = *line;
|
|
if (linepos == NULL && linepos[0] == '\0')
|
|
return -EINVAL;
|
|
|
|
/* skip whitespace */
|
|
while (isspace(linepos[0]) || linepos[0] == ',')
|
|
linepos++;
|
|
|
|
/* get the key */
|
|
if (linepos[0] == '\0')
|
|
return -EINVAL;
|
|
*key = linepos;
|
|
|
|
while (1) {
|
|
linepos++;
|
|
if (linepos[0] == '\0')
|
|
return -1;
|
|
if (isspace(linepos[0]))
|
|
break;
|
|
if (linepos[0] == '=')
|
|
break;
|
|
if (linepos[0] == '+')
|
|
break;
|
|
if (linepos[0] == '!')
|
|
break;
|
|
if (linepos[0] == ':')
|
|
break;
|
|
}
|
|
|
|
/* remember end of key */
|
|
temp = linepos;
|
|
|
|
/* skip whitespace after key */
|
|
while (isspace(linepos[0]))
|
|
linepos++;
|
|
if (linepos[0] == '\0')
|
|
return -EINVAL;
|
|
|
|
/* get operation type */
|
|
if (linepos[0] == '=' && linepos[1] == '=') {
|
|
*op = KEY_OP_MATCH;
|
|
linepos += 2;
|
|
dbg("operator=match");
|
|
} else if (linepos[0] == '!' && linepos[1] == '=') {
|
|
*op = KEY_OP_NOMATCH;
|
|
linepos += 2;
|
|
dbg("operator=nomatch");
|
|
} else if (linepos[0] == '+' && linepos[1] == '=') {
|
|
*op = KEY_OP_ADD;
|
|
linepos += 2;
|
|
dbg("operator=add");
|
|
} else if (linepos[0] == '=') {
|
|
*op = KEY_OP_ASSIGN;
|
|
linepos++;
|
|
dbg("operator=assign");
|
|
} else if (linepos[0] == ':' && linepos[1] == '=') {
|
|
*op = KEY_OP_ASSIGN_FINAL;
|
|
linepos += 2;
|
|
dbg("operator=assign_final");
|
|
} else
|
|
return -EINVAL;
|
|
|
|
/* terminate key */
|
|
temp[0] = '\0';
|
|
dbg("key='%s'", *key);
|
|
|
|
/* skip whitespace after operator */
|
|
while (isspace(linepos[0]))
|
|
linepos++;
|
|
if (linepos[0] == '\0')
|
|
return -EINVAL;
|
|
|
|
/* get the value*/
|
|
if (linepos[0] != '"')
|
|
return -EINVAL;
|
|
linepos++;
|
|
*value = linepos;
|
|
|
|
while (1) {
|
|
temp = strchr(linepos, '"');
|
|
if (temp && temp[-1] == '\\') {
|
|
linepos = temp + 1;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (!temp)
|
|
return -EINVAL;
|
|
temp[0] = '\0';
|
|
temp++;
|
|
dbg("value='%s'", *value);
|
|
|
|
/* move line to next key */
|
|
*line = temp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* extract possible KEY{attr} */
|
|
static char *get_key_attribute(struct space *space, char *str, char *res, size_t ressize)
|
|
{
|
|
char *pos;
|
|
char *attr;
|
|
|
|
attr = strchr(str, '{');
|
|
if (attr != NULL) {
|
|
attr++;
|
|
pos = strchr(attr, '}');
|
|
if (pos == NULL) {
|
|
Perror(space, "missing closing brace for format");
|
|
return NULL;
|
|
}
|
|
pos[0] = '\0';
|
|
strlcpy(res, attr, ressize);
|
|
pos[0] = '}';
|
|
dbg("attribute='%s'", res);
|
|
return res;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* extract possible {attr} and move str behind it */
|
|
static char *get_format_attribute(struct space *space, char **str)
|
|
{
|
|
char *pos;
|
|
char *attr = NULL;
|
|
|
|
if (*str[0] == '{') {
|
|
pos = strchr(*str, '}');
|
|
if (pos == NULL) {
|
|
Perror(space, "missing closing brace for format");
|
|
return NULL;
|
|
}
|
|
pos[0] = '\0';
|
|
attr = *str+1;
|
|
*str = pos+1;
|
|
dbg("attribute='%s', str='%s'", attr, *str);
|
|
}
|
|
return attr;
|
|
}
|
|
|
|
/* extract possible format length and move str behind it*/
|
|
static int get_format_len(struct space *space, char **str)
|
|
{
|
|
int num;
|
|
char *tail;
|
|
|
|
if (isdigit(*str[0])) {
|
|
num = (int) strtoul(*str, &tail, 10);
|
|
if (num > 0) {
|
|
*str = tail;
|
|
dbg("format length=%i", num);
|
|
return num;
|
|
} else {
|
|
Perror(space, "format parsing error '%s'", *str);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void apply_format(struct space *space, char *string, size_t maxsize)
|
|
{
|
|
char temp[PATH_SIZE];
|
|
char temp2[PATH_SIZE];
|
|
char *head, *tail, *pos, *cpos, *attr, *rest;
|
|
struct pair *pair;
|
|
int len;
|
|
int i;
|
|
int count;
|
|
enum subst_type {
|
|
SUBST_UNKNOWN,
|
|
SUBST_CARDINFO,
|
|
SUBST_CTL,
|
|
SUBST_RESULT,
|
|
SUBST_ATTR,
|
|
SUBST_SYSFSROOT,
|
|
SUBST_ENV,
|
|
SUBST_CONFIG,
|
|
};
|
|
static const struct subst_map {
|
|
char *name;
|
|
char fmt;
|
|
enum subst_type type;
|
|
} map[] = {
|
|
{ .name = "cardinfo", .fmt = 'i', .type = SUBST_CARDINFO },
|
|
{ .name = "ctl", .fmt = 'C', .type = SUBST_CTL },
|
|
{ .name = "result", .fmt = 'c', .type = SUBST_RESULT },
|
|
{ .name = "attr", .fmt = 's', .type = SUBST_ATTR },
|
|
{ .name = "sysfsroot", .fmt = 'r', .type = SUBST_SYSFSROOT },
|
|
{ .name = "env", .fmt = 'E', .type = SUBST_ENV },
|
|
{ .name = "config", .fmt = 'g', .type = SUBST_CONFIG },
|
|
{ NULL, '\0', 0 }
|
|
};
|
|
enum subst_type type;
|
|
const struct subst_map *subst;
|
|
|
|
head = string;
|
|
while (1) {
|
|
len = -1;
|
|
while (head[0] != '\0') {
|
|
if (head[0] == '$') {
|
|
/* substitute named variable */
|
|
if (head[1] == '\0')
|
|
break;
|
|
if (head[1] == '$') {
|
|
strlcpy(temp, head+2, sizeof(temp));
|
|
strlcpy(head+1, temp, maxsize);
|
|
head++;
|
|
continue;
|
|
}
|
|
head[0] = '\0';
|
|
for (subst = map; subst->name; subst++) {
|
|
if (strncasecmp(&head[1], subst->name, strlen(subst->name)) == 0) {
|
|
type = subst->type;
|
|
tail = head + strlen(subst->name)+1;
|
|
dbg("will substitute format name '%s'", subst->name);
|
|
goto found;
|
|
}
|
|
}
|
|
} else if (head[0] == '%') {
|
|
/* substitute format char */
|
|
if (head[1] == '\0')
|
|
break;
|
|
if (head[1] == '%') {
|
|
strlcpy(temp, head+2, sizeof(temp));
|
|
strlcpy(head+1, temp, maxsize);
|
|
head++;
|
|
continue;
|
|
}
|
|
head[0] = '\0';
|
|
tail = head+1;
|
|
len = get_format_len(space, &tail);
|
|
for (subst = map; subst->name; subst++) {
|
|
if (tail[0] == subst->fmt) {
|
|
type = subst->type;
|
|
tail++;
|
|
dbg("will substitute format char '%c'", subst->fmt);
|
|
goto found;
|
|
}
|
|
}
|
|
}
|
|
head++;
|
|
}
|
|
break;
|
|
found:
|
|
attr = get_format_attribute(space, &tail);
|
|
strlcpy(temp, tail, sizeof(temp));
|
|
dbg("format=%i, string='%s', tail='%s'", type ,string, tail);
|
|
|
|
switch (type) {
|
|
case SUBST_CARDINFO:
|
|
if (attr == NULL)
|
|
Perror(space, "missing identification parametr for cardinfo");
|
|
else {
|
|
const char *value = cardinfo_get(space, attr);
|
|
if (value == NULL)
|
|
break;
|
|
strlcat(string, value, maxsize);
|
|
dbg("substitute cardinfo{%s} '%s'", attr, value);
|
|
}
|
|
break;
|
|
case SUBST_CTL:
|
|
if (attr == NULL)
|
|
Perror(space, "missing identification parametr for ctl");
|
|
else {
|
|
const char *value = elemid_get(space, attr);
|
|
if (value == NULL)
|
|
break;
|
|
strlcat(string, value, maxsize);
|
|
dbg("substitute ctl{%s} '%s'", attr, value);
|
|
}
|
|
break;
|
|
case SUBST_RESULT:
|
|
if (space->program_result == NULL)
|
|
break;
|
|
/* get part part of the result string */
|
|
i = 0;
|
|
if (attr != NULL)
|
|
i = strtoul(attr, &rest, 10);
|
|
if (i > 0) {
|
|
dbg("request part #%d of result string", i);
|
|
cpos = space->program_result;
|
|
while (--i) {
|
|
while (cpos[0] != '\0' && !isspace(cpos[0]))
|
|
cpos++;
|
|
while (isspace(cpos[0]))
|
|
cpos++;
|
|
}
|
|
if (i > 0) {
|
|
Perror(space, "requested part of result string not found");
|
|
break;
|
|
}
|
|
strlcpy(temp2, cpos, sizeof(temp2));
|
|
/* %{2+}c copies the whole string from the second part on */
|
|
if (rest[0] != '+') {
|
|
cpos = strchr(temp2, ' ');
|
|
if (cpos)
|
|
cpos[0] = '\0';
|
|
}
|
|
strlcat(string, temp2, maxsize);
|
|
dbg("substitute part of result string '%s'", temp2);
|
|
} else {
|
|
strlcat(string, space->program_result, maxsize);
|
|
dbg("substitute result string '%s'", space->program_result);
|
|
}
|
|
break;
|
|
case SUBST_ATTR:
|
|
if (attr == NULL)
|
|
Perror(space, "missing file parameter for attr");
|
|
else {
|
|
const char *value = NULL;
|
|
size_t size;
|
|
|
|
pair = value_find(space, "sysfs_device");
|
|
if (pair == NULL)
|
|
break;
|
|
value = sysfs_attr_get_value(pair->value, attr);
|
|
|
|
if (value == NULL)
|
|
break;
|
|
|
|
/* strip trailing whitespace and replace untrusted characters of sysfs value */
|
|
size = strlcpy(temp2, value, sizeof(temp2));
|
|
if (size >= sizeof(temp2))
|
|
size = sizeof(temp2)-1;
|
|
while (size > 0 && isspace(temp2[size-1]))
|
|
temp2[--size] = '\0';
|
|
count = replace_untrusted_chars(temp2);
|
|
if (count > 0)
|
|
Perror(space, "%i untrusted character(s) replaced" , count);
|
|
strlcat(string, temp2, maxsize);
|
|
dbg("substitute sysfs value '%s'", temp2);
|
|
}
|
|
break;
|
|
case SUBST_SYSFSROOT:
|
|
strlcat(string, sysfs_path, maxsize);
|
|
dbg("substitute sysfs_path '%s'", sysfs_path);
|
|
break;
|
|
case SUBST_ENV:
|
|
if (attr == NULL) {
|
|
dbg("missing attribute");
|
|
break;
|
|
}
|
|
pos = getenv(attr);
|
|
if (pos == NULL) {
|
|
dbg("env '%s' not available", attr);
|
|
break;
|
|
}
|
|
dbg("substitute env '%s=%s'", attr, pos);
|
|
strlcat(string, pos, maxsize);
|
|
break;
|
|
case SUBST_CONFIG:
|
|
if (attr == NULL) {
|
|
dbg("missing attribute");
|
|
break;
|
|
}
|
|
pair = value_find(space, attr);
|
|
if (pair == NULL)
|
|
break;
|
|
strlcat(string, pair->value, maxsize);
|
|
break;
|
|
default:
|
|
Perror(space, "unknown substitution type=%i", type);
|
|
break;
|
|
}
|
|
/* possibly truncate to format-char specified length */
|
|
if (len != -1) {
|
|
head[len] = '\0';
|
|
dbg("truncate to %i chars, subtitution string becomes '%s'", len, head);
|
|
}
|
|
strlcat(string, temp, maxsize);
|
|
}
|
|
/* unescape strings */
|
|
head = tail = string;
|
|
while (*head != '\0') {
|
|
if (*head == '\\') {
|
|
head++;
|
|
if (*head == '\0')
|
|
break;
|
|
switch (*head) {
|
|
case 'a': *tail++ = '\a'; break;
|
|
case 'b': *tail++ = '\b'; break;
|
|
case 'n': *tail++ = '\n'; break;
|
|
case 'r': *tail++ = '\r'; break;
|
|
case 't': *tail++ = '\t'; break;
|
|
case 'v': *tail++ = '\v'; break;
|
|
case '\\': *tail++ = '\\'; break;
|
|
default: *tail++ = *head; break;
|
|
}
|
|
head++;
|
|
continue;
|
|
}
|
|
if (*head)
|
|
*tail++ = *head++;
|
|
}
|
|
*tail = 0;
|
|
}
|
|
|
|
static
|
|
int run_program1(struct space *space,
|
|
const char *command0, char *result,
|
|
size_t ressize, size_t *reslen, int log)
|
|
{
|
|
if (strncmp(command0, "__ctl_search", 12) == 0) {
|
|
const char *res = elemid_get(space, "do_search");
|
|
if (res == NULL || strcmp(res, "1") != 0)
|
|
return EXIT_FAILURE;
|
|
return EXIT_SUCCESS;
|
|
}
|
|
if (strncmp(command0, "__ctl_count", 11) == 0) {
|
|
const char *res = elemid_get(space, "do_count");
|
|
if (res == NULL || strcmp(res, "0") == 0)
|
|
return EXIT_FAILURE;
|
|
strlcpy(result, res, ressize);
|
|
return EXIT_SUCCESS;
|
|
}
|
|
Perror(space, "unknown buildin command '%s'", command0);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
static int parse(struct space *space, const char *filename);
|
|
|
|
static char *new_root_dir(const char *filename)
|
|
{
|
|
char *res, *tmp;
|
|
|
|
res = strdup(filename);
|
|
if (res) {
|
|
tmp = strrchr(res, '/');
|
|
if (tmp)
|
|
*tmp = '\0';
|
|
}
|
|
dbg("new_root_dir '%s' '%s'", filename, res);
|
|
return res;
|
|
}
|
|
|
|
/* return non-zero if the file name has the extension ".conf" */
|
|
static int conf_name_filter(const struct dirent *d)
|
|
{
|
|
char *ext = strrchr(d->d_name, '.');
|
|
return ext && !strcmp(ext, ".conf");
|
|
}
|
|
|
|
static int parse_line(struct space *space, char *line, size_t linesize)
|
|
{
|
|
char *linepos;
|
|
char *key, *value, *attr, *temp;
|
|
struct pair *pair;
|
|
enum key_op op;
|
|
int err = 0, count;
|
|
char string[PATH_SIZE];
|
|
char result[PATH_SIZE];
|
|
|
|
linepos = line;
|
|
while (*linepos != '\0') {
|
|
op = KEY_OP_UNSET;
|
|
|
|
err = get_key(&linepos, &key, &op, &value);
|
|
if (err < 0)
|
|
goto invalid;
|
|
|
|
if (strncasecmp(key, "LABEL", 5) == 0) {
|
|
if (op != KEY_OP_ASSIGN) {
|
|
Perror(space, "invalid LABEL operation");
|
|
goto invalid;
|
|
}
|
|
if (space->go_to && strcmp(space->go_to, value) == 0) {
|
|
free(space->go_to);
|
|
space->go_to = NULL;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (space->go_to) {
|
|
dbg("skip (GOTO '%s')", space->go_to);
|
|
break; /* not for us */
|
|
}
|
|
|
|
if (strncasecmp(key, "CTL{", 4) == 0) {
|
|
attr = get_key_attribute(space, key + 3, string, sizeof(string));
|
|
if (attr == NULL) {
|
|
Perror(space, "error parsing CTL attribute");
|
|
goto invalid;
|
|
}
|
|
if (op == KEY_OP_ASSIGN) {
|
|
strlcpy(result, value, sizeof(result));
|
|
apply_format(space, result, sizeof(result));
|
|
dbg("ctl assign: '%s' '%s'", value, attr);
|
|
err = elemid_set(space, attr, result);
|
|
if (space->program_result) {
|
|
free(space->program_result);
|
|
space->program_result = NULL;
|
|
}
|
|
snprintf(string, sizeof(string), "%i", err);
|
|
space->program_result = strdup(string);
|
|
err = 0;
|
|
if (space->program_result == NULL)
|
|
break;
|
|
} else if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
|
|
if (strncmp(attr, "write", 5) == 0) {
|
|
strlcpy(result, value, sizeof(result));
|
|
apply_format(space, result, sizeof(result));
|
|
dbg("ctl write: '%s' '%s'", value, attr);
|
|
err = elemid_set(space, "values", result);
|
|
if (err == 0 && op == KEY_OP_NOMATCH)
|
|
break;
|
|
if (err != 0 && op == KEY_OP_MATCH)
|
|
break;
|
|
} else {
|
|
temp = (char *)elemid_get(space, attr);
|
|
dbg("ctl match: '%s' '%s' '%s'", attr, value, temp);
|
|
if (!do_match(key, op, value, temp))
|
|
break;
|
|
}
|
|
} else {
|
|
Perror(space, "invalid CTL{} operation");
|
|
goto invalid;
|
|
}
|
|
continue;
|
|
}
|
|
if (strcasecmp(key, "RESULT") == 0) {
|
|
if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
|
|
if (!do_match(key, op, value, space->program_result))
|
|
break;
|
|
} else if (op == KEY_OP_ASSIGN) {
|
|
if (space->program_result) {
|
|
free(space->program_result);
|
|
space->program_result = NULL;
|
|
}
|
|
strlcpy(string, value, sizeof(string));
|
|
apply_format(space, string, sizeof(string));
|
|
space->program_result = strdup(string);
|
|
if (space->program_result == NULL)
|
|
break;
|
|
} else {
|
|
Perror(space, "invalid RESULT operation");
|
|
goto invalid;
|
|
}
|
|
continue;
|
|
}
|
|
if (strcasecmp(key, "PROGRAM") == 0) {
|
|
if (op == KEY_OP_UNSET)
|
|
continue;
|
|
strlcpy(string, value, sizeof(string));
|
|
apply_format(space, string, sizeof(string));
|
|
if (space->program_result) {
|
|
free(space->program_result);
|
|
space->program_result = NULL;
|
|
}
|
|
if (run_program(space, string, result, sizeof(result), NULL, space->log_run) != 0) {
|
|
dbg("PROGRAM '%s' is false", string);
|
|
if (op != KEY_OP_NOMATCH)
|
|
break;
|
|
} else {
|
|
remove_trailing_chars(result, '\n');
|
|
count = replace_untrusted_chars(result);
|
|
if (count)
|
|
info("%i untrusted character(s) replaced", count);
|
|
dbg("PROGRAM '%s' result is '%s'", string, result);
|
|
space->program_result = strdup(result);
|
|
if (space->program_result == NULL)
|
|
break;
|
|
dbg("PROGRAM returned successful");
|
|
if (op == KEY_OP_NOMATCH)
|
|
break;
|
|
}
|
|
dbg("PROGRAM key is true");
|
|
continue;
|
|
}
|
|
if (strncasecmp(key, "CARDINFO{", 9) == 0) {
|
|
attr = get_key_attribute(space, key + 8, string, sizeof(string));
|
|
if (attr == NULL) {
|
|
Perror(space, "error parsing CARDINFO attribute");
|
|
goto invalid;
|
|
}
|
|
if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
|
|
dbg("cardinfo: '%s' '%s'", value, attr);
|
|
temp = (char *)cardinfo_get(space, attr);
|
|
if (!do_match(key, op, value, temp))
|
|
break;
|
|
} else {
|
|
Perror(space, "invalid CARDINFO{} operation");
|
|
goto invalid;
|
|
}
|
|
continue;
|
|
}
|
|
if (strncasecmp(key, "ATTR{", 5) == 0) {
|
|
attr = get_key_attribute(space, key + 4, string, sizeof(string));
|
|
if (attr == NULL) {
|
|
Perror(space, "error parsing ATTR attribute");
|
|
goto invalid;
|
|
}
|
|
if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
|
|
pair = value_find(space, "sysfs_device");
|
|
if (pair == NULL)
|
|
break;
|
|
dbg("sysfs_attr: '%s' '%s'", pair->value, attr);
|
|
temp = sysfs_attr_get_value(pair->value, attr);
|
|
if (!do_match(key, op, value, temp))
|
|
break;
|
|
} else {
|
|
Perror(space, "invalid ATTR{} operation");
|
|
goto invalid;
|
|
}
|
|
continue;
|
|
}
|
|
if (strncasecmp(key, "ENV{", 4) == 0) {
|
|
attr = get_key_attribute(space, key + 3, string, sizeof(string));
|
|
if (attr == NULL) {
|
|
Perror(space, "error parsing ENV attribute");
|
|
goto invalid;
|
|
}
|
|
if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
|
|
temp = getenv(attr);
|
|
dbg("env: '%s' '%s'", attr, temp);
|
|
if (!do_match(key, op, value, temp))
|
|
break;
|
|
} else if (op == KEY_OP_ASSIGN ||
|
|
op == KEY_OP_ASSIGN_FINAL) {
|
|
strlcpy(result, value, sizeof(result));
|
|
apply_format(space, result, sizeof(result));
|
|
dbg("env set: '%s' '%s'", attr, result);
|
|
if (setenv(attr, result, op == KEY_OP_ASSIGN_FINAL))
|
|
break;
|
|
} else {
|
|
Perror(space, "invalid ENV{} operation");
|
|
goto invalid;
|
|
}
|
|
continue;
|
|
}
|
|
if (strcasecmp(key, "GOTO") == 0) {
|
|
if (op != KEY_OP_ASSIGN) {
|
|
Perror(space, "invalid GOTO operation");
|
|
goto invalid;
|
|
}
|
|
space->go_to = strdup(value);
|
|
if (space->go_to == NULL) {
|
|
err = -ENOMEM;
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
if (strcasecmp(key, "INCLUDE") == 0) {
|
|
char *rootdir, *go_to;
|
|
const char *filename;
|
|
struct stat st;
|
|
int linenum;
|
|
if (op != KEY_OP_ASSIGN) {
|
|
Perror(space, "invalid INCLUDE operation");
|
|
goto invalid;
|
|
}
|
|
if (value[0] == '/')
|
|
strlcpy(string, value, sizeof(string));
|
|
else {
|
|
strlcpy(string, space->rootdir, sizeof(string));
|
|
strlcat(string, "/", sizeof(string));
|
|
strlcat(string, value, sizeof(string));
|
|
}
|
|
rootdir = space->rootdir;
|
|
go_to = space->go_to;
|
|
filename = space->filename;
|
|
linenum = space->linenum;
|
|
if (stat(string, &st)) {
|
|
Perror(space, "invalid filename '%s'", string);
|
|
continue;
|
|
}
|
|
if (S_ISDIR(st.st_mode)) {
|
|
struct dirent **list;
|
|
int i, num;
|
|
num = scandir(string, &list, conf_name_filter,
|
|
alphasort);
|
|
if (num < 0) {
|
|
Perror(space, "invalid directory '%s'", string);
|
|
continue;
|
|
}
|
|
count = strlen(string);
|
|
for (i = 0; i < num; i++) {
|
|
string[count] = '\0';
|
|
strlcat(string, "/", sizeof(string));
|
|
strlcat(string, list[i]->d_name, sizeof(string));
|
|
space->go_to = NULL;
|
|
space->rootdir = new_root_dir(string);
|
|
free(list[i]);
|
|
if (space->rootdir) {
|
|
err = parse(space, string);
|
|
free(space->rootdir);
|
|
} else
|
|
err = -ENOMEM;
|
|
if (space->go_to) {
|
|
Perror(space, "unterminated GOTO '%s'", space->go_to);
|
|
free(space->go_to);
|
|
}
|
|
if (err)
|
|
break;
|
|
}
|
|
free(list);
|
|
} else {
|
|
space->go_to = NULL;
|
|
space->rootdir = new_root_dir(string);
|
|
if (space->rootdir) {
|
|
err = parse(space, string);
|
|
free(space->rootdir);
|
|
} else
|
|
err = -ENOMEM;
|
|
if (space->go_to) {
|
|
Perror(space, "unterminated GOTO '%s'", space->go_to);
|
|
free(space->go_to);
|
|
}
|
|
}
|
|
space->go_to = go_to;
|
|
space->rootdir = rootdir;
|
|
space->filename = filename;
|
|
space->linenum = linenum;
|
|
if (space->quit)
|
|
break;
|
|
if (err)
|
|
break;
|
|
continue;
|
|
}
|
|
if (strncasecmp(key, "ACCESS", 6) == 0) {
|
|
if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
|
|
if (value[0] == '$') {
|
|
strlcpy(string, value, sizeof(string));
|
|
apply_format(space, string, sizeof(string));
|
|
if (string[0] == '/')
|
|
goto __access1;
|
|
}
|
|
if (value[0] != '/') {
|
|
strlcpy(string, space->rootdir, sizeof(string));
|
|
strlcat(string, "/", sizeof(string));
|
|
strlcat(string, value, sizeof(string));
|
|
} else {
|
|
strlcpy(string, value, sizeof(string));
|
|
}
|
|
apply_format(space, string, sizeof(string));
|
|
__access1:
|
|
count = access(string, F_OK);
|
|
dbg("access(%s) = %i (%s)", string, count, value);
|
|
if (op == KEY_OP_MATCH && count != 0)
|
|
break;
|
|
if (op == KEY_OP_NOMATCH && count == 0)
|
|
break;
|
|
} else {
|
|
Perror(space, "invalid ACCESS operation");
|
|
goto invalid;
|
|
}
|
|
continue;
|
|
}
|
|
if (strncasecmp(key, "PRINT", 5) == 0) {
|
|
if (op != KEY_OP_ASSIGN) {
|
|
Perror(space, "invalid PRINT operation");
|
|
goto invalid;
|
|
}
|
|
strlcpy(string, value, sizeof(string));
|
|
apply_format(space, string, sizeof(string));
|
|
fwrite(string, strlen(string), 1, stdout);
|
|
continue;
|
|
}
|
|
if (strncasecmp(key, "ERROR", 5) == 0) {
|
|
if (op != KEY_OP_ASSIGN) {
|
|
Perror(space, "invalid ERROR operation");
|
|
goto invalid;
|
|
}
|
|
strlcpy(string, value, sizeof(string));
|
|
apply_format(space, string, sizeof(string));
|
|
fwrite(string, strlen(string), 1, stderr);
|
|
continue;
|
|
}
|
|
if (strncasecmp(key, "EXIT", 4) == 0) {
|
|
if (op != KEY_OP_ASSIGN) {
|
|
Perror(space, "invalid EXIT operation");
|
|
goto invalid;
|
|
}
|
|
strlcpy(string, value, sizeof(string));
|
|
apply_format(space, string, sizeof(string));
|
|
if (strcmp(string, "return") == 0)
|
|
return -EJUSTRETURN;
|
|
space->exit_code = strtol(string, NULL, 0);
|
|
space->quit = 1;
|
|
break;
|
|
}
|
|
if (strncasecmp(key, "CONFIG{", 7) == 0) {
|
|
attr = get_key_attribute(space, key + 6, string, sizeof(string));
|
|
if (attr == NULL) {
|
|
Perror(space, "error parsing CONFIG attribute");
|
|
goto invalid;
|
|
}
|
|
strlcpy(result, value, sizeof(result));
|
|
apply_format(space, result, sizeof(result));
|
|
if (op == KEY_OP_ASSIGN) {
|
|
err = value_set(space, attr, result);
|
|
dbg("CONFIG{%s}='%s'", attr, result);
|
|
break;
|
|
} else if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
|
|
pair = value_find(space, attr);
|
|
if (pair == NULL)
|
|
break;
|
|
if (!do_match(key, op, result, pair->value))
|
|
break;
|
|
} else {
|
|
Perror(space, "invalid CONFIG{} operation");
|
|
goto invalid;
|
|
}
|
|
}
|
|
|
|
Perror(space, "unknown key '%s'", key);
|
|
}
|
|
return err;
|
|
|
|
invalid:
|
|
Perror(space, "invalid rule");
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int parse(struct space *space, const char *filename)
|
|
{
|
|
char *buf, *bufline, *line;
|
|
size_t bufsize, pos, count, linesize;
|
|
unsigned int linenum, i, j, linenum_adj;
|
|
int err;
|
|
|
|
dbg("start of file '%s'", filename);
|
|
|
|
if (file_map(filename, &buf, &bufsize) != 0) {
|
|
err = errno;
|
|
error("Unable to open file '%s': %s", filename, strerror(err));
|
|
return -err;
|
|
}
|
|
|
|
err = 0;
|
|
pos = 0;
|
|
linenum = 0;
|
|
linesize = 128;
|
|
line = malloc(linesize);
|
|
if (line == NULL)
|
|
return -ENOMEM;
|
|
space->filename = filename;
|
|
while (!err && pos < bufsize && !space->quit) {
|
|
count = line_width(buf, bufsize, pos);
|
|
bufline = buf + pos;
|
|
pos += count + 1;
|
|
linenum++;
|
|
|
|
/* skip whitespaces */
|
|
while (count > 0 && isspace(bufline[0])) {
|
|
bufline++;
|
|
count--;
|
|
}
|
|
if (count == 0)
|
|
continue;
|
|
|
|
/* comment check */
|
|
if (bufline[0] == '#')
|
|
continue;
|
|
|
|
if (count > linesize - 1) {
|
|
free(line);
|
|
linesize = (count + 127 + 1) & ~127;
|
|
if (linesize > 2048) {
|
|
error("file %s, line %i too long", filename, linenum);
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
line = malloc(linesize);
|
|
if (line == NULL) {
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* skip backslash and newline from multiline rules */
|
|
linenum_adj = 0;
|
|
for (i = j = 0; i < count; i++) {
|
|
if (bufline[i] == '\\' && bufline[i+1] == '\n') {
|
|
linenum_adj++;
|
|
continue;
|
|
}
|
|
line[j++] = bufline[i];
|
|
}
|
|
line[j] = '\0';
|
|
|
|
dbg("read (%i) '%s'", linenum, line);
|
|
space->linenum = linenum;
|
|
err = parse_line(space, line, linesize);
|
|
if (err == -EJUSTRETURN) {
|
|
err = 0;
|
|
break;
|
|
}
|
|
linenum += linenum_adj;
|
|
}
|
|
|
|
free(line);
|
|
space->filename = NULL;
|
|
space->linenum = -1;
|
|
file_unmap(buf, bufsize);
|
|
dbg("end of file '%s'", filename);
|
|
return err ? err : -abs(space->exit_code);
|
|
}
|
|
|
|
int init(const char *filename, const char *cardname)
|
|
{
|
|
struct space *space;
|
|
int err = 0, card, first;
|
|
|
|
sysfs_init();
|
|
if (!cardname) {
|
|
first = 1;
|
|
card = -1;
|
|
while (1) {
|
|
if (snd_card_next(&card) < 0)
|
|
break;
|
|
if (card < 0) {
|
|
if (first) {
|
|
error("No soundcards found...");
|
|
return -ENODEV;
|
|
}
|
|
break;
|
|
}
|
|
first = 0;
|
|
err = init_space(&space, card);
|
|
if (err == 0) {
|
|
space->rootdir = new_root_dir(filename);
|
|
if (space->rootdir != NULL)
|
|
err = parse(space, filename);
|
|
free_space(space);
|
|
}
|
|
if (err < 0)
|
|
break;
|
|
}
|
|
} else {
|
|
card = snd_card_get_index(cardname);
|
|
if (card < 0) {
|
|
error("Cannot find soundcard '%s'...", cardname);
|
|
goto error;
|
|
}
|
|
memset(&space, 0, sizeof(space));
|
|
err = init_space(&space, card);
|
|
if (err == 0) {
|
|
space->rootdir = new_root_dir(filename);
|
|
if (space->rootdir != NULL)
|
|
err = parse(space, filename);
|
|
free_space(space);
|
|
}
|
|
}
|
|
error:
|
|
sysfs_cleanup();
|
|
return err;
|
|
}
|