]> sigrok.org Git - libsigrok.git/blob - src/resource.c
resource: Also check $SIGROK_FIRMWARE_DIR for firmware files.
[libsigrok.git] / src / resource.c
1 /*
2  * This file is part of the libsigrok project.
3  *
4  * Copyright (C) 2015 Daniel Elstner <daniel.kitta@gmail.com>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <errno.h>
22 #include <stdio.h>
23 #include <glib.h>
24 #include <glib/gstdio.h>
25 #include <libsigrok/libsigrok.h>
26 #include "libsigrok-internal.h"
27
28 /** @cond PRIVATE */
29 #define LOG_PREFIX "resource"
30 /** @endcond */
31
32 /**
33  * @file
34  *
35  * Access to resource files.
36  */
37
38 /** Retrieve the size of the open stream @a file.
39  *
40  * This function only works on seekable streams. However, the set of seekable
41  * streams is generally congruent with the set of streams that have a size.
42  * Code that needs to work with any type of stream (including pipes) should
43  * require neither seekability nor advance knowledge of the size.
44  * On failure, the return value is negative and errno is set.
45  *
46  * @param file An I/O stream opened in binary mode.
47  * @return The size of @a file in bytes, or a negative value on failure.
48  *
49  * @private
50  */
51 SR_PRIV int64_t sr_file_get_size(FILE *file)
52 {
53         off_t filepos, filesize;
54
55         /* ftello() and fseeko() are not standard C, but part of POSIX.1-2001.
56          * Thus, if these functions are available at all, they can reasonably
57          * be expected to also conform to POSIX semantics. In particular, this
58          * means that ftello() after fseeko(..., SEEK_END) has a defined result
59          * and can be used to get the size of a seekable stream.
60          * On Windows, the result is fully defined only for binary streams.
61          */
62         filepos = ftello(file);
63         if (filepos < 0)
64                 return -1;
65
66         if (fseeko(file, 0, SEEK_END) < 0)
67                 return -1;
68
69         filesize = ftello(file);
70         if (filesize < 0)
71                 return -1;
72
73         if (fseeko(file, filepos, SEEK_SET) < 0)
74                 return -1;
75
76         return filesize;
77 }
78
79 static FILE *try_open_file(const char *datadir, const char *subdir,
80                 const char *name)
81 {
82         char *filename;
83         FILE *file;
84
85         filename = g_build_filename(datadir, subdir, name, NULL);
86         file = g_fopen(filename, "rb");
87
88         if (file)
89                 sr_info("Opened '%s'.", filename);
90         else
91                 sr_spew("Attempt to open '%s' failed: %s",
92                         filename, g_strerror(errno));
93         g_free(filename);
94
95         return file;
96 }
97
98 static int resource_open_default(struct sr_resource *res,
99                 const char *name, void *cb_data)
100 {
101         int64_t filesize;
102 #ifdef FIRMWARE_DIR
103         const char *builtindir;
104 #endif
105         const char *subdir, *env;
106         const char *const *datadirs;
107         FILE *file = NULL;
108
109         (void)cb_data;
110
111         switch (res->type) {
112         case SR_RESOURCE_FIRMWARE:
113 #ifdef FIRMWARE_DIR
114                 builtindir = FIRMWARE_DIR;
115 #endif
116                 subdir = "sigrok-firmware";
117                 break;
118         default:
119                 sr_err("%s: unknown type %d.", __func__, res->type);
120                 return SR_ERR_ARG;
121         }
122
123         env = g_getenv("SIGROK_FIRMWARE_DIR");
124         if (!env)
125                 sr_dbg("SIGROK_FIRMWARE_DIR environment variable not set, ignoring.");
126         else
127                 file = try_open_file(env, "", name);
128
129         if (!file)
130                 file = try_open_file(g_get_user_data_dir(), subdir, name);
131
132         /*
133          * Scan the hard-coded directory before the system directories to
134          * avoid picking up possibly outdated files from a system install.
135          */
136 #ifdef FIRMWARE_DIR
137         if (!file)
138                 file = try_open_file(builtindir, "", name);
139 #endif
140         if (!file) {
141                 datadirs = g_get_system_data_dirs();
142                 while (*datadirs && !file)
143                         file = try_open_file(*datadirs++, subdir, name);
144         }
145         if (!file) {
146                 sr_dbg("Failed to locate '%s'.", name);
147                 return SR_ERR;
148         }
149
150         filesize = sr_file_get_size(file);
151         if (filesize < 0) {
152                 sr_err("Failed to obtain size of '%s': %s",
153                         name, g_strerror(errno));
154                 fclose(file);
155                 return SR_ERR;
156         }
157         res->size = filesize;
158         res->handle = file;
159
160         return SR_OK;
161 }
162
163 static int resource_close_default(struct sr_resource *res, void *cb_data)
164 {
165         FILE *file;
166
167         (void)cb_data;
168
169         file = res->handle;
170         if (!file) {
171                 sr_err("%s: invalid handle.", __func__);
172                 return SR_ERR_ARG;
173         }
174
175         if (fclose(file) < 0) {
176                 sr_err("Failed to close file: %s", g_strerror(errno));
177                 return SR_ERR;
178         }
179         res->handle = NULL;
180
181         return SR_OK;
182 }
183
184 static gssize resource_read_default(const struct sr_resource *res,
185                 void *buf, size_t count, void *cb_data)
186 {
187         FILE *file;
188         size_t n_read;
189
190         (void)cb_data;
191
192         file = res->handle;
193         if (!file) {
194                 sr_err("%s: invalid handle.", __func__);
195                 return SR_ERR_ARG;
196         }
197         if (count > G_MAXSSIZE) {
198                 sr_err("%s: count %zu too large.", __func__, count);
199                 return SR_ERR_ARG;
200         }
201
202         n_read = fread(buf, 1, count, file);
203
204         if (n_read != count && ferror(file)) {
205                 sr_err("Failed to read resource file: %s", g_strerror(errno));
206                 return SR_ERR;
207         }
208         return n_read;
209 }
210
211 /**
212  * Install resource access hooks.
213  *
214  * @param ctx libsigrok context. Must not be NULL.
215  * @param open_cb Resource open callback, or NULL to unset.
216  * @param close_cb Resource close callback, or NULL to unset.
217  * @param read_cb Resource read callback, or NULL to unset.
218  * @param cb_data User data pointer passed to callbacks.
219  *
220  * @retval SR_OK Success.
221  * @retval SR_ERR_ARG Invalid argument.
222  *
223  * @since 0.4.0
224  */
225 SR_API int sr_resource_set_hooks(struct sr_context *ctx,
226                 sr_resource_open_callback open_cb,
227                 sr_resource_close_callback close_cb,
228                 sr_resource_read_callback read_cb, void *cb_data)
229 {
230         if (!ctx) {
231                 sr_err("%s: ctx was NULL.", __func__);
232                 return SR_ERR_ARG;
233         }
234         if (open_cb && close_cb && read_cb) {
235                 ctx->resource_open_cb = open_cb;
236                 ctx->resource_close_cb = close_cb;
237                 ctx->resource_read_cb = read_cb;
238                 ctx->resource_cb_data = cb_data;
239         } else if (!open_cb && !close_cb && !read_cb) {
240                 ctx->resource_open_cb = &resource_open_default;
241                 ctx->resource_close_cb = &resource_close_default;
242                 ctx->resource_read_cb = &resource_read_default;
243                 ctx->resource_cb_data = ctx;
244         } else {
245                 sr_err("%s: inconsistent callback pointers.", __func__);
246                 return SR_ERR_ARG;
247         }
248         return SR_OK;
249 }
250
251 /**
252  * Open resource.
253  *
254  * @param ctx libsigrok context. Must not be NULL.
255  * @param[out] res Resource descriptor to fill in. Must not be NULL.
256  * @param type Resource type ID.
257  * @param name Name of the resource. Must not be NULL.
258  *
259  * @retval SR_OK Success.
260  * @retval SR_ERR_ARG Invalid argument.
261  * @retval SR_ERR Other error.
262  *
263  * @private
264  */
265 SR_PRIV int sr_resource_open(struct sr_context *ctx,
266                 struct sr_resource *res, int type, const char *name)
267 {
268         int ret;
269
270         res->size = 0;
271         res->handle = NULL;
272         res->type = type;
273
274         ret = (*ctx->resource_open_cb)(res, name, ctx->resource_cb_data);
275
276         if (ret != SR_OK)
277                 sr_err("Failed to open resource '%s' (use loglevel 5/spew for"
278                        " details).", name);
279
280         return ret;
281 }
282
283 /**
284  * Close resource.
285  *
286  * @param ctx libsigrok context. Must not be NULL.
287  * @param[inout] res Resource descriptor. Must not be NULL.
288  *
289  * @retval SR_OK Success.
290  * @retval SR_ERR_ARG Invalid argument.
291  * @retval SR_ERR Other error.
292  *
293  * @private
294  */
295 SR_PRIV int sr_resource_close(struct sr_context *ctx, struct sr_resource *res)
296 {
297         int ret;
298
299         ret = (*ctx->resource_close_cb)(res, ctx->resource_cb_data);
300
301         if (ret != SR_OK)
302                 sr_err("Failed to close resource.");
303
304         return ret;
305 }
306
307 /**
308  * Read resource data.
309  *
310  * @param ctx libsigrok context. Must not be NULL.
311  * @param[in] res Resource descriptor. Must not be NULL.
312  * @param[out] buf Buffer to store @a count bytes into. Must not be NULL.
313  * @param count Number of bytes to read.
314  *
315  * @return The number of bytes actually read, or a negative value on error.
316  * @retval SR_ERR_ARG Invalid argument.
317  * @retval SR_ERR Other error.
318  *
319  * @private
320  */
321 SR_PRIV gssize sr_resource_read(struct sr_context *ctx,
322                 const struct sr_resource *res, void *buf, size_t count)
323 {
324         gssize n_read;
325
326         n_read = (*ctx->resource_read_cb)(res, buf, count,
327                         ctx->resource_cb_data);
328         if (n_read < 0)
329                 sr_err("Failed to read resource.");
330
331         return n_read;
332 }
333
334 /**
335  * Load a resource into memory.
336  *
337  * @param ctx libsigrok context. Must not be NULL.
338  * @param type Resource type ID.
339  * @param name Name of the resource. Must not be NULL.
340  * @param[out] size Size in bytes of the returned buffer. Must not be NULL.
341  * @param max_size Size limit. Error out if the resource is larger than this.
342  *
343  * @return A buffer containing the resource data, or NULL on failure. Must
344  *         be freed by the caller using g_free().
345  *
346  * @private
347  */
348 SR_PRIV void *sr_resource_load(struct sr_context *ctx,
349                 int type, const char *name, size_t *size, size_t max_size)
350 {
351         struct sr_resource res;
352         void *buf;
353         size_t res_size;
354         gssize n_read;
355
356         if (sr_resource_open(ctx, &res, type, name) != SR_OK)
357                 return NULL;
358
359         if (res.size > max_size) {
360                 sr_err("Size %" PRIu64 " of '%s' exceeds limit %zu.",
361                         res.size, name, max_size);
362                 sr_resource_close(ctx, &res);
363                 return NULL;
364         }
365         res_size = res.size;
366
367         buf = g_try_malloc(res_size);
368         if (!buf) {
369                 sr_err("Failed to allocate buffer for '%s'.", name);
370                 sr_resource_close(ctx, &res);
371                 return NULL;
372         }
373
374         n_read = sr_resource_read(ctx, &res, buf, res_size);
375         sr_resource_close(ctx, &res);
376
377         if (n_read < 0 || (size_t)n_read != res_size) {
378                 if (n_read >= 0)
379                         sr_err("Failed to read '%s': premature end of file.",
380                                 name);
381                 g_free(buf);
382                 return NULL;
383         }
384
385         *size = res_size;
386         return buf;
387 }
388
389 /** @} */