/* * This file is part of ltrace. * Copyright (C) 2012,2013 Petr Machata, Red Hat Inc. * * 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 St, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include #include "common.h" #include "callback.h" #include "param.h" #include "prototype.h" #include "type.h" #include "options.h" #include "read_config_file.h" #include "backend.h" struct protolib_cache g_protocache; static struct protolib legacy_typedefs; void prototype_init(struct prototype *proto) { VECT_INIT(&proto->params, struct param); proto->return_info = NULL; proto->own_return_info = 0; } static void param_destroy_cb(struct param *param, void *data) { param_destroy(param); } void prototype_destroy(struct prototype *proto) { if (proto == NULL) return; if (proto->own_return_info) { type_destroy(proto->return_info); free(proto->return_info); } VECT_DESTROY(&proto->params, struct param, ¶m_destroy_cb, NULL); } int prototype_push_param(struct prototype *proto, struct param *param) { return VECT_PUSHBACK(&proto->params, param); } size_t prototype_num_params(struct prototype *proto) { return vect_size(&proto->params); } void prototype_destroy_nth_param(struct prototype *proto, size_t n) { assert(n < prototype_num_params(proto)); VECT_ERASE(&proto->params, struct param, n, n+1, ¶m_destroy_cb, NULL); } struct param * prototype_get_nth_param(struct prototype *proto, size_t n) { assert(n < prototype_num_params(proto)); return VECT_ELEMENT(&proto->params, struct param, n); } struct each_param_data { struct prototype *proto; enum callback_status (*cb)(struct prototype *, struct param *, void *); void *data; }; static enum callback_status each_param_cb(struct param *param, void *data) { struct each_param_data *cb_data = data; return (cb_data->cb)(cb_data->proto, param, cb_data->data); } struct param * prototype_each_param(struct prototype *proto, struct param *start_after, enum callback_status (*cb)(struct prototype *, struct param *, void *), void *data) { struct each_param_data cb_data = { proto, cb, data }; return VECT_EACH(&proto->params, struct param, start_after, &each_param_cb, &cb_data); } void named_type_init(struct named_type *named, struct arg_type_info *info, int own_type) { named->info = info; named->own_type = own_type; named->forward = 0; } void named_type_destroy(struct named_type *named) { if (named->own_type) { type_destroy(named->info); free(named->info); } } void protolib_init(struct protolib *plib) { DICT_INIT(&plib->prototypes, char *, struct prototype, dict_hash_string, dict_eq_string, NULL); DICT_INIT(&plib->named_types, char *, struct named_type, dict_hash_string, dict_eq_string, NULL); VECT_INIT(&plib->imports, struct protolib *); plib->refs = 0; } static void destroy_prototype_cb(struct prototype *proto, void *data) { prototype_destroy(proto); } static void destroy_named_type_cb(struct named_type *named, void *data) { named_type_destroy(named); } void protolib_destroy(struct protolib *plib) { assert(plib->refs == 0); VECT_DESTROY(&plib->imports, struct prototype *, NULL, NULL); DICT_DESTROY(&plib->prototypes, const char *, struct prototype, dict_dtor_string, destroy_prototype_cb, NULL); DICT_DESTROY(&plib->named_types, const char *, struct named_type, dict_dtor_string, destroy_named_type_cb, NULL); } static struct protolib ** each_import(struct protolib *plib, struct protolib **start_after, enum callback_status (*cb)(struct protolib **, void *), void *data) { assert(plib != NULL); return VECT_EACH(&plib->imports, struct protolib *, start_after, cb, data); } static enum callback_status is_or_imports(struct protolib **plibp, void *data) { assert(plibp != NULL); assert(*plibp != NULL); struct protolib *import = data; if (*plibp == import || each_import(*plibp, NULL, &is_or_imports, import) != NULL) return CBS_STOP; else return CBS_CONT; } int protolib_add_import(struct protolib *plib, struct protolib *import) { assert(plib != NULL); assert(import != NULL); if (is_or_imports(&import, plib) == CBS_STOP) { fprintf(stderr, "Recursive import rejected.\n"); return -2; } return VECT_PUSHBACK(&plib->imports, &import) < 0 ? -1 : 0; } static int bailout(const char *name, int own) { int save_errno = errno; if (own) free((char *)name); errno = save_errno; return -1; } int protolib_add_prototype(struct protolib *plib, const char *name, int own_name, struct prototype *proto) { assert(plib != NULL); if (strdup_if(&name, name, !own_name) < 0) return -1; if (DICT_INSERT(&plib->prototypes, &name, proto) < 0) return bailout(name, own_name); return 0; } int protolib_add_named_type(struct protolib *plib, const char *name, int own_name, struct named_type *named) { assert(plib != NULL); if (strdup_if(&name, name, !own_name) < 0) return -1; if (DICT_INSERT(&plib->named_types, &name, named) < 0) return bailout(name, own_name); return 0; } struct lookup { const char *name; struct dict *(*getter)(struct protolib *plib); bool imports; void *result; }; static struct dict * get_prototypes(struct protolib *plib) { assert(plib != NULL); return &plib->prototypes; } static struct dict * get_named_types(struct protolib *plib) { assert(plib != NULL); return &plib->named_types; } static enum callback_status protolib_lookup_rec(struct protolib **plibp, void *data) { assert(plibp != NULL); assert(*plibp != NULL); struct lookup *lookup = data; struct dict *dict = (*lookup->getter)(*plibp); lookup->result = dict_find(dict, &lookup->name); if (lookup->result != NULL) return CBS_STOP; if (lookup->imports && each_import(*plibp, NULL, &protolib_lookup_rec, lookup) != NULL) { assert(lookup->result != NULL); return CBS_STOP; } return CBS_CONT; } static void * protolib_lookup(struct protolib *plib, const char *name, struct dict *(*getter)(struct protolib *), bool imports) { assert(plib != NULL); struct lookup lookup = { name, getter, imports, NULL }; if (protolib_lookup_rec(&plib, &lookup) == CBS_STOP) assert(lookup.result != NULL); else assert(lookup.result == NULL); return lookup.result; } struct prototype * protolib_lookup_prototype(struct protolib *plib, const char *name, bool imports) { assert(plib != NULL); return protolib_lookup(plib, name, &get_prototypes, imports); } struct named_type * protolib_lookup_type(struct protolib *plib, const char *name, bool imports) { assert(plib != NULL); return protolib_lookup(plib, name, &get_named_types, imports); } static void destroy_protolib_cb(struct protolib **plibp, void *data) { assert(plibp != NULL); if (*plibp != NULL && --(*plibp)->refs == 0) { protolib_destroy(*plibp); free(*plibp); } } void protolib_cache_destroy(struct protolib_cache *cache) { DICT_DESTROY(&cache->protolibs, const char *, struct protolib *, dict_dtor_string, destroy_protolib_cb, NULL); } struct load_config_data { struct protolib_cache *self; const char *key; struct protolib *result; }; static struct protolib * consider_config_dir(struct protolib_cache *cache, const char *path, const char *key) { size_t len = sizeof ".conf"; char *buf = alloca(strlen(path) + 1 + strlen(key) + len); sprintf(buf, "%s/%s.conf", path, key); return protolib_cache_file(cache, buf, 0); } static enum callback_status consider_confdir_cb(struct opt_F_t *entry, void *d) { if (opt_F_get_kind(entry) != OPT_F_DIR) return CBS_CONT; struct load_config_data *data = d; data->result = consider_config_dir(data->self, entry->pathname, data->key); return data->result != NULL ? CBS_STOP : CBS_CONT; } static int load_dash_F_dirs(struct protolib_cache *cache, const char *key, struct protolib **retp) { struct load_config_data data = {cache, key}; if (VECT_EACH(&opt_F, struct opt_F_t, NULL, consider_confdir_cb, &data) == NULL) /* Not found. That's fine. */ return 0; if (data.result == NULL) /* There were errors. */ return -1; *retp = data.result; return 0; } static int load_config(struct protolib_cache *cache, const char *key, int private, struct protolib **retp) { const char **dirs = NULL; if (os_get_config_dirs(private, &dirs) < 0 || dirs == NULL) return -1; for (; *dirs != NULL; ++dirs) { struct protolib *plib = consider_config_dir(cache, *dirs, key); if (plib != NULL) { *retp = plib; break; } } return 0; } static enum callback_status import_legacy_file(char **fnp, void *data) { struct protolib_cache *cache = data; struct protolib *plib = protolib_cache_file(cache, *fnp, 1); if (plib != NULL) { /* The cache now owns the file name. */ *fnp = NULL; if (protolib_add_import(&cache->imports, plib) < 0) return CBS_STOP; } return CBS_CONT; } static int add_ltrace_conf(struct protolib_cache *cache) { /* Look into private config directories for .ltrace.conf and * into system config directories for ltrace.conf. If it's * found, add it to implicit import module. */ struct vect legacy_files; VECT_INIT(&legacy_files, char *); if (os_get_ltrace_conf_filenames(&legacy_files) < 0) { vect_destroy(&legacy_files, NULL, NULL); return -1; } int ret = VECT_EACH(&legacy_files, char *, NULL, import_legacy_file, cache) == NULL ? 0 : -1; VECT_DESTROY(&legacy_files, char *, vect_dtor_string, NULL); return ret; } static enum callback_status add_imports_cb(struct opt_F_t *entry, void *data) { struct protolib_cache *self = data; if (opt_F_get_kind(entry) != OPT_F_FILE) return CBS_CONT; struct protolib *new_import = protolib_cache_file(self, entry->pathname, 0); if (new_import == NULL || protolib_add_import(&self->imports, new_import) < 0) /* N.B. If new_import is non-NULL, it has been already * cached. We don't therefore destroy it on * failures. */ return CBS_STOP; return CBS_CONT; } int protolib_cache_init(struct protolib_cache *cache, struct protolib *import) { DICT_INIT(&cache->protolibs, char *, struct protolib *, dict_hash_string, dict_eq_string, NULL); protolib_init(&cache->imports); /* At this point the cache is consistent. This is important, * because next we will use it to cache files that we load * due to -F. * * But we are about to construct the implicit import module, * which means this module can't be itself imported to the * files that we load now. So remember that we are still * bootstrapping. */ cache->bootstrap = 1; if (protolib_add_import(&cache->imports, &legacy_typedefs) < 0 || (import != NULL && protolib_add_import(&cache->imports, import) < 0) || add_ltrace_conf(cache) < 0 || VECT_EACH(&opt_F, struct opt_F_t, NULL, add_imports_cb, cache) != NULL) { protolib_cache_destroy(cache); return -1; } cache->bootstrap = 0; return 0; } static enum callback_status add_import_cb(struct protolib **importp, void *data) { struct protolib *plib = data; if (protolib_add_import(plib, *importp) < 0) return CBS_STOP; else return CBS_CONT; } static struct protolib * build_default_config(struct protolib_cache *cache, const char *key) { struct protolib *new_plib = malloc(sizeof(*new_plib)); if (new_plib == NULL) { fprintf(stderr, "Couldn't create config module %s: %s\n", key, strerror(errno)); return NULL; } protolib_init(new_plib); /* If bootstrapping, copy over imports from implicit import * module to new_plib. We can't reference the implicit * import module itself, because new_plib will become part of * this same implicit import module itself. */ if ((cache->bootstrap && each_import(&cache->imports, NULL, add_import_cb, new_plib) != NULL) || (!cache->bootstrap && protolib_add_import(new_plib, &cache->imports) < 0)) { fprintf(stderr, "Couldn't add imports to config module %s: %s\n", key, strerror(errno)); protolib_destroy(new_plib); free(new_plib); return NULL; } return new_plib; } static void attempt_to_cache(struct protolib_cache *cache, const char *key, struct protolib *plib) { if (protolib_cache_protolib(cache, key, 1, plib) == 0 || plib == NULL) /* Never mind failing to store a NULL. */ return; /* Returning a protolib that hasn't been cached would leak * that protolib, but perhaps it's less bad then giving up * outright. At least print an error message. */ fprintf(stderr, "Couldn't cache prototype library for %s\n", key); free((void *) key); } int protolib_cache_maybe_load(struct protolib_cache *cache, const char *key, int own_key, bool allow_private, struct protolib **retp) { if (DICT_FIND_VAL(&cache->protolibs, &key, retp) == 0) return 0; if (strdup_if(&key, key, !own_key) < 0) { fprintf(stderr, "Couldn't cache %s: %s\n", key, strerror(errno)); return -1; } *retp = NULL; if (load_dash_F_dirs(cache, key, retp) < 0 || (*retp == NULL && allow_private && load_config(cache, key, 1, retp) < 0) || (*retp == NULL && load_config(cache, key, 0, retp) < 0)) { fprintf(stderr, "Error occurred when attempting to load a prototype " "library for %s.\n", key); if (!own_key) free((void *) key); return -1; } if (*retp != NULL) attempt_to_cache(cache, key, *retp); else if (!own_key) free((void *) key); return 0; } struct protolib * protolib_cache_load(struct protolib_cache *cache, const char *key, int own_key, bool allow_private) { struct protolib *plib; if (protolib_cache_maybe_load(cache, key, own_key, allow_private, &plib) < 0) return NULL; if (plib == NULL) plib = protolib_cache_default(cache, key, own_key); return plib; } struct protolib * protolib_cache_default(struct protolib_cache *cache, const char *key, int own_key) { if (strdup_if(&key, key, !own_key) < 0) { fprintf(stderr, "Couldn't cache default %s: %s\n", key, strerror(errno)); return NULL; } struct protolib *plib = build_default_config(cache, key); /* Whatever came out of this (even NULL), store it in * the cache. */ attempt_to_cache(cache, key, plib); return plib; } struct protolib * protolib_cache_file(struct protolib_cache *cache, const char *filename, int own_filename) { { struct protolib *plib; if (DICT_FIND_VAL(&cache->protolibs, &filename, &plib) == 0) return plib; } FILE *stream = fopen(filename, "r"); if (stream == NULL) return NULL; if (strdup_if(&filename, filename, !own_filename) < 0) { fprintf(stderr, "Couldn't cache %s: %s\n", filename, strerror(errno)); fclose(stream); return NULL; } struct protolib *new_plib = build_default_config(cache, filename); if (new_plib == NULL || read_config_file(stream, filename, new_plib) < 0) { fclose(stream); if (own_filename) free((char *) filename); if (new_plib != NULL) { protolib_destroy(new_plib); free(new_plib); } return NULL; } attempt_to_cache(cache, filename, new_plib); fclose(stream); return new_plib; } int protolib_cache_protolib(struct protolib_cache *cache, const char *filename, int own_filename, struct protolib *plib) { if (strdup_if(&filename, filename, !own_filename) < 0) { fprintf(stderr, "Couldn't cache %s: %s\n", filename, strerror(errno)); return -1; } int rc = DICT_INSERT(&cache->protolibs, &filename, &plib); if (rc < 0 && own_filename) free((char *) filename); if (rc == 0 && plib != NULL) plib->refs++; return rc; } static void destroy_global_config(void) { protolib_cache_destroy(&g_protocache); protolib_destroy(&legacy_typedefs); } void init_global_config(void) { protolib_init(&legacy_typedefs); struct arg_type_info *ptr_info = type_get_voidptr(); static struct named_type voidptr_type; named_type_init(&voidptr_type, ptr_info, 0); /* Build legacy typedefs first. This is used by * protolib_cache_init call below. */ if (protolib_add_named_type(&legacy_typedefs, "addr", 0, &voidptr_type) < 0 || protolib_add_named_type(&legacy_typedefs, "file", 0, &voidptr_type) < 0) { fprintf(stderr, "Couldn't initialize aliases `addr' and `file'.\n"); exit(1); } if (protolib_cache_init(&g_protocache, NULL) < 0) { fprintf(stderr, "Couldn't init prototype cache\n"); exit(1); } atexit(destroy_global_config); }