From: Daniel Elstner Date: Sat, 26 Sep 2015 11:38:30 +0000 (+0200) Subject: resource: New internal API for accessing resource files X-Git-Tag: libsigrok-0.4.0~240 X-Git-Url: http://sigrok.org/gitweb/?a=commitdiff_plain;h=bee246665b05a59f91ff7d51040cbaee614ab0c7;hp=98654c99daf85bd2a81a9c87f517e800c31e34f2;p=libsigrok.git resource: New internal API for accessing resource files The resource API provides a generic means for accessing resources that are bundled with sigrok, such as device firmware files. Since the manner of resource bundling is platform-dependent, users of libsigrok may override the functions used to open, close and read a resource. The default implementation accesses resources as files located in one of the XDG data directories or a directory defined at compile time. --- diff --git a/Makefile.am b/Makefile.am index bde3b2ce..06832cae 100644 --- a/Makefile.am +++ b/Makefile.am @@ -51,6 +51,7 @@ libsigrok_la_SOURCES = \ src/soft-trigger.c \ src/analog.c \ src/fallback.c \ + src/resource.c \ src/strutil.c \ src/log.c \ src/version.c \ diff --git a/include/libsigrok/libsigrok.h b/include/libsigrok/libsigrok.h index cf8f9b20..6c67f570 100644 --- a/include/libsigrok/libsigrok.h +++ b/include/libsigrok/libsigrok.h @@ -557,6 +557,25 @@ struct sr_option { GSList *values; }; +/** Resource type. + * @since 0.4.0 + */ +enum sr_resource_type { + SR_RESOURCE_FIRMWARE = 1, +}; + +/** Resource descriptor. + * @since 0.4.0 + */ +struct sr_resource { + /** Size of resource in bytes; set by resource open callback. */ + uint64_t size; + /** File handle or equivalent; set by resource open callback. */ + void *handle; + /** Resource type (SR_RESOURCE_FIRMWARE, ...) */ + int type; +}; + /** Output module flags. */ enum sr_output_flag { /** If set, this output module writes the output itself. */ diff --git a/include/libsigrok/proto.h b/include/libsigrok/proto.h index 1efd6aab..e482b309 100644 --- a/include/libsigrok/proto.h +++ b/include/libsigrok/proto.h @@ -202,6 +202,20 @@ SR_API int sr_trigger_match_add(struct sr_trigger_stage *stage, SR_API GSList *sr_serial_list(const struct sr_dev_driver *driver); SR_API void sr_serial_free(struct sr_serial_port *serial); +/*--- resource.c ------------------------------------------------------------*/ + +typedef int (*sr_resource_open_callback)(struct sr_resource *res, + const char *name, void *cb_data); +typedef int (*sr_resource_close_callback)(struct sr_resource *res, + void *cb_data); +typedef ssize_t (*sr_resource_read_callback)(const struct sr_resource *res, + void *buf, size_t count, void *cb_data); + +SR_API int sr_resource_set_hooks(struct sr_context *ctx, + sr_resource_open_callback open_cb, + sr_resource_close_callback close_cb, + sr_resource_read_callback read_cb, void *cb_data); + /*--- strutil.c -------------------------------------------------------------*/ SR_API char *sr_si_string_u64(uint64_t x, const char *unit); diff --git a/src/backend.c b/src/backend.c index 8084a1c5..5be826ab 100644 --- a/src/backend.c +++ b/src/backend.c @@ -510,6 +510,7 @@ SR_API int sr_init(struct sr_context **ctx) goto done; } #endif + sr_resource_set_hooks(context, NULL, NULL, NULL, NULL); *ctx = context; context = NULL; diff --git a/src/libsigrok-internal.h b/src/libsigrok-internal.h index 275deac4..32f49197 100644 --- a/src/libsigrok-internal.h +++ b/src/libsigrok-internal.h @@ -209,6 +209,10 @@ struct sr_context { #ifdef HAVE_LIBUSB_1_0 libusb_context *libusb_ctx; #endif + sr_resource_open_callback resource_open_cb; + sr_resource_close_callback resource_close_cb; + sr_resource_read_callback resource_read_cb; + void *resource_cb_data; }; /** Input module metadata keys. */ @@ -765,6 +769,20 @@ SR_PRIV int std_dev_clear(const struct sr_dev_driver *driver, std_dev_clear_callback clear_private); SR_PRIV int std_serial_dev_close(struct sr_dev_inst *sdi); +/*--- resource.c ------------------------------------------------------------*/ + +SR_PRIV int sr_resource_open(struct sr_context *ctx, + struct sr_resource *res, int type, const char *name) + G_GNUC_WARN_UNUSED_RESULT; +SR_PRIV int sr_resource_close(struct sr_context *ctx, + struct sr_resource *res); +SR_PRIV ssize_t sr_resource_read(struct sr_context *ctx, + const struct sr_resource *res, void *buf, size_t count) + G_GNUC_WARN_UNUSED_RESULT; +SR_PRIV void *sr_resource_load(struct sr_context *ctx, int type, + const char *name, size_t *size, size_t max_size) + G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; + /*--- strutil.c -------------------------------------------------------------*/ SR_PRIV int sr_atol(const char *str, long *ret); diff --git a/src/resource.c b/src/resource.c new file mode 100644 index 00000000..b29be608 --- /dev/null +++ b/src/resource.c @@ -0,0 +1,330 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2015 Daniel Elstner + * + * 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 3 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, see . + */ + +#include +#include +#include +#include +#include +#include +#include "libsigrok-internal.h" + +/** @cond PRIVATE */ +#define LOG_PREFIX "resource" +/** @endcond */ + +/** + * @file + * + * Access to resource files. + */ + +static FILE *try_open_file(const char *datadir, const char *subdir, + const char *name) +{ + char *filename; + FILE *file; + + filename = g_build_filename(datadir, subdir, name, NULL); + file = g_fopen(filename, "rb"); + + if (file) + sr_info("Opened '%s'.", filename); + else + sr_spew("Attempt to open '%s' failed: %s", + filename, g_strerror(errno)); + g_free(filename); + + return file; +} + +static int resource_open_default(struct sr_resource *res, + const char *name, void *cb_data) +{ + int64_t filesize; + const char *builtindir, *subdir; + const char *const *datadirs; + FILE *file; + + (void)cb_data; + + switch (res->type) { + case SR_RESOURCE_FIRMWARE: + builtindir = FIRMWARE_DIR; + subdir = "sigrok-firmware"; + break; + default: + sr_err("%s: unknown type %d.", __func__, res->type); + return SR_ERR_ARG; + } + + file = try_open_file(g_get_user_data_dir(), subdir, name); + /* + * Scan the hard-coded directory before the system directories to + * avoid picking up possibly outdated files from a system install. + */ + if (!file) + file = try_open_file(builtindir, "", name); + + if (!file) { + datadirs = g_get_system_data_dirs(); + while (*datadirs && !file) + file = try_open_file(*datadirs++, subdir, name); + } + if (!file) { + sr_err("Failed to locate '%s'.", name); + return SR_ERR; + } + + filesize = sr_file_get_size(file); + if (filesize < 0) { + sr_err("Failed to obtain size of '%s': %s", + name, g_strerror(errno)); + fclose(file); + return SR_ERR; + } + res->size = filesize; + res->handle = file; + + return SR_OK; +} + +static int resource_close_default(struct sr_resource *res, void *cb_data) +{ + FILE *file; + + (void)cb_data; + + file = res->handle; + if (!file) { + sr_err("%s: invalid handle.", __func__); + return SR_ERR_ARG; + } + + if (fclose(file) < 0) { + sr_err("Failed to close file: %s", g_strerror(errno)); + return SR_ERR; + } + res->handle = NULL; + + return SR_OK; +} + +static ssize_t resource_read_default(const struct sr_resource *res, + void *buf, size_t count, void *cb_data) +{ + FILE *file; + size_t n_read; + + (void)cb_data; + + file = res->handle; + if (!file) { + sr_err("%s: invalid handle.", __func__); + return SR_ERR_ARG; + } + if (count > SSIZE_MAX) { + sr_err("%s: count %zu too large.", __func__, count); + return SR_ERR_ARG; + } + + n_read = fread(buf, 1, count, file); + + if (n_read != count && ferror(file)) { + sr_err("Failed to read resource file: %s", g_strerror(errno)); + return SR_ERR; + } + return n_read; +} + +/** + * Install resource access hooks. + * + * @param ctx libsigrok context. Must not be NULL. + * @param open_cb Resource open callback, or NULL to unset. + * @param close_cb Resource close callback, or NULL to unset. + * @param read_cb Resource read callback, or NULL to unset. + * @param cb_data User data pointer passed to callbacks. + * + * @retval SR_OK Success. + * @retval SR_ERR_ARG Invalid argument. + * + * @since 0.4.0 + */ +SR_API int sr_resource_set_hooks(struct sr_context *ctx, + sr_resource_open_callback open_cb, + sr_resource_close_callback close_cb, + sr_resource_read_callback read_cb, void *cb_data) +{ + if (!ctx) { + sr_err("%s: ctx was NULL.", __func__); + return SR_ERR_ARG; + } + if (open_cb && close_cb && read_cb) { + ctx->resource_open_cb = open_cb; + ctx->resource_close_cb = close_cb; + ctx->resource_read_cb = read_cb; + ctx->resource_cb_data = cb_data; + } else if (!open_cb && !close_cb && !read_cb) { + ctx->resource_open_cb = &resource_open_default; + ctx->resource_close_cb = &resource_close_default; + ctx->resource_read_cb = &resource_read_default; + ctx->resource_cb_data = ctx; + } else { + sr_err("%s: inconsistent callback pointers.", __func__); + return SR_ERR_ARG; + } + return SR_OK; +} + +/** + * Open resource. + * + * @param ctx libsigrok context. Must not be NULL. + * @param[out] res Resource descriptor to fill in. Must not be NULL. + * @param type Resource type ID. + * @param name Name of the resource. Must not be NULL. + * + * @retval SR_OK Success. + * @retval SR_ERR_ARG Invalid argument. + * @retval SR_ERR Other error. + * + * @private + */ +SR_PRIV int sr_resource_open(struct sr_context *ctx, + struct sr_resource *res, int type, const char *name) +{ + int ret; + + res->size = 0; + res->handle = NULL; + res->type = type; + + ret = (*ctx->resource_open_cb)(res, name, ctx->resource_cb_data); + + if (ret != SR_OK) + sr_err("Failed to open resource '%s'.", name); + + return ret; +} + +/** + * Close resource. + * + * @param ctx libsigrok context. Must not be NULL. + * @param[inout] res Resource descriptor. Must not be NULL. + * + * @retval SR_OK Success. + * @retval SR_ERR_ARG Invalid argument. + * @retval SR_ERR Other error. + * + * @private + */ +SR_PRIV int sr_resource_close(struct sr_context *ctx, struct sr_resource *res) +{ + int ret; + + ret = (*ctx->resource_close_cb)(res, ctx->resource_cb_data); + + if (ret != SR_OK) + sr_err("Failed to close resource."); + + return ret; +} + +/** + * Read resource data. + * + * @param ctx libsigrok context. Must not be NULL. + * @param[in] res Resource descriptor. Must not be NULL. + * @param[out] buf Buffer to store @a count bytes into. Must not be NULL. + * @param count Number of bytes to read. + * + * @return The number of bytes actually read, or a negative value on error. + * @retval SR_ERR_ARG Invalid argument. + * @retval SR_ERR Other error. + * + * @private + */ +SR_PRIV ssize_t sr_resource_read(struct sr_context *ctx, + const struct sr_resource *res, void *buf, size_t count) +{ + ssize_t n_read; + + n_read = (*ctx->resource_read_cb)(res, buf, count, + ctx->resource_cb_data); + if (n_read < 0) + sr_err("Failed to read resource."); + + return n_read; +} + +/** + * Load a resource into memory. + * + * @param ctx libsigrok context. Must not be NULL. + * @param type Resource type ID. + * @param name Name of the resource. Must not be NULL. + * @param[out] size Size in bytes of the returned buffer. Must not be NULL. + * @param max_size Size limit. Error out if the resource is larger than this. + * + * @return A buffer containing the resource data, or NULL on failure. Must + * be freed by the caller using g_free(). + * + * @private + */ +SR_PRIV void *sr_resource_load(struct sr_context *ctx, + int type, const char *name, size_t *size, size_t max_size) +{ + struct sr_resource res; + void *buf; + ssize_t n_read; + + if (sr_resource_open(ctx, &res, type, name) != SR_OK) + return NULL; + + if (res.size > max_size) { + sr_err("Size %" PRIu64 " of '%s' exceeds limit %zu.", + res.size, name, max_size); + sr_resource_close(ctx, &res); + return NULL; + } + buf = g_try_malloc(res.size); + if (!buf) { + sr_err("Failed to allocate buffer for '%s'.", name); + sr_resource_close(ctx, &res); + return NULL; + } + + n_read = sr_resource_read(ctx, &res, buf, res.size); + sr_resource_close(ctx, &res); + + if (n_read < 0 || (size_t)n_read != res.size) { + if (n_read >= 0) + sr_err("Failed to read '%s': premature end of file.", + name); + g_free(buf); + return NULL; + } + + *size = res.size; + return buf; +} + +/** @} */