/* * Advanced Linux Sound Architecture Control Program - Parse initialization files * Copyright (c) by Jaroslav Kysela , * Greg Kroah-Hartman , * Kay Sievers * * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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:%d", 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) { int nibble1 = hextodigit(*(value++)); int nibble2 = hextodigit(*(value++)); if (nibble1 < 0 || nibble2 < 0) { Perror(space, "bad ctl hexa value"); return -EINVAL; } val = (nibble1 << 4) | nibble2; 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: 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, "%d", 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) { file_unmap(buf, bufsize); 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); line = NULL; 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, int flags, const char *cardname) { struct space *space; int err = 0, lasterr = 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) { if (ignore_nocards) return 0; error("No soundcards found..."); return -ENODEV; } break; } first = 0; err = init_ucm(flags, card); if (err == 0) continue; err = init_space(&space, card); if (err == 0) { space->rootdir = new_root_dir(filename); if (space->rootdir != NULL) err = parse(space, filename); if (err <= -99) { /* non-fatal errors */ if (lasterr == 0) lasterr = err; err = 0; } free_space(space); } if (err < 0) break; } err = lasterr; } else { card = snd_card_get_index(cardname); if (card < 0) { error("Cannot find soundcard '%s'...", cardname); goto error; } err = init_ucm(flags, card); if (err == 0) return 0; 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; }