]> sigrok.org Git - libsigrok.git/blob - src/resource.c
resource: Move sr_file_get_size() to resource.c
[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         const char *builtindir, *subdir;
103         const char *const *datadirs;
104         FILE *file;
105
106         (void)cb_data;
107
108         switch (res->type) {
109         case SR_RESOURCE_FIRMWARE:
110                 builtindir = FIRMWARE_DIR;
111                 subdir = "sigrok-firmware";
112                 break;
113         default:
114                 sr_err("%s: unknown type %d.", __func__, res->type);
115                 return SR_ERR_ARG;
116         }
117
118         file = try_open_file(g_get_user_data_dir(), subdir, name);
119         /*
120          * Scan the hard-coded directory before the system directories to
121          * avoid picking up possibly outdated files from a system install.
122          */
123         if (!file)
124                 file = try_open_file(builtindir, "", name);
125
126         if (!file) {
127                 datadirs = g_get_system_data_dirs();
128                 while (*datadirs && !file)
129                         file = try_open_file(*datadirs++, subdir, name);
130         }
131         if (!file) {
132                 sr_err("Failed to locate '%s'.", name);
133                 return SR_ERR;
134         }
135
136         filesize = sr_file_get_size(file);
137         if (filesize < 0) {
138                 sr_err("Failed to obtain size of '%s': %s",
139                         name, g_strerror(errno));
140                 fclose(file);
141                 return SR_ERR;
142         }
143         res->size = filesize;
144         res->handle = file;
145
146         return SR_OK;
147 }
148
149 static int resource_close_default(struct sr_resource *res, void *cb_data)
150 {
151         FILE *file;
152
153         (void)cb_data;
154
155         file = res->handle;
156         if (!file) {
157                 sr_err("%s: invalid handle.", __func__);
158                 return SR_ERR_ARG;
159         }
160
161         if (fclose(file) < 0) {
162                 sr_err("Failed to close file: %s", g_strerror(errno));
163                 return SR_ERR;
164         }
165         res->handle = NULL;
166
167         return SR_OK;
168 }
169
170 static ssize_t resource_read_default(const struct sr_resource *res,
171                 void *buf, size_t count, void *cb_data)
172 {
173         FILE *file;
174         size_t n_read;
175
176         (void)cb_data;
177
178         file = res->handle;
179         if (!file) {
180                 sr_err("%s: invalid handle.", __func__);
181                 return SR_ERR_ARG;
182         }
183         if (count > SSIZE_MAX) {
184                 sr_err("%s: count %zu too large.", __func__, count);
185                 return SR_ERR_ARG;
186         }
187
188         n_read = fread(buf, 1, count, file);
189
190         if (n_read != count && ferror(file)) {
191                 sr_err("Failed to read resource file: %s", g_strerror(errno));
192                 return SR_ERR;
193         }
194         return n_read;
195 }
196
197 /**
198  * Install resource access hooks.
199  *
200  * @param ctx libsigrok context. Must not be NULL.
201  * @param open_cb Resource open callback, or NULL to unset.
202  * @param close_cb Resource close callback, or NULL to unset.
203  * @param read_cb Resource read callback, or NULL to unset.
204  * @param cb_data User data pointer passed to callbacks.
205  *
206  * @retval SR_OK Success.
207  * @retval SR_ERR_ARG Invalid argument.
208  *
209  * @since 0.4.0
210  */
211 SR_API int sr_resource_set_hooks(struct sr_context *ctx,
212                 sr_resource_open_callback open_cb,
213                 sr_resource_close_callback close_cb,
214                 sr_resource_read_callback read_cb, void *cb_data)
215 {
216         if (!ctx) {
217                 sr_err("%s: ctx was NULL.", __func__);
218                 return SR_ERR_ARG;
219         }
220         if (open_cb && close_cb && read_cb) {
221                 ctx->resource_open_cb  = open_cb;
222                 ctx->resource_close_cb = close_cb;
223                 ctx->resource_read_cb  = read_cb;
224                 ctx->resource_cb_data  = cb_data;
225         } else if (!open_cb && !close_cb && !read_cb) {
226                 ctx->resource_open_cb  = &resource_open_default;
227                 ctx->resource_close_cb = &resource_close_default;
228                 ctx->resource_read_cb  = &resource_read_default;
229                 ctx->resource_cb_data  = ctx;
230         } else {
231                 sr_err("%s: inconsistent callback pointers.", __func__);
232                 return SR_ERR_ARG;
233         }
234         return SR_OK;
235 }
236
237 /**
238  * Open resource.
239  *
240  * @param ctx libsigrok context. Must not be NULL.
241  * @param[out] res Resource descriptor to fill in. Must not be NULL.
242  * @param type Resource type ID.
243  * @param name Name of the resource. Must not be NULL.
244  *
245  * @retval SR_OK Success.
246  * @retval SR_ERR_ARG Invalid argument.
247  * @retval SR_ERR Other error.
248  *
249  * @private
250  */
251 SR_PRIV int sr_resource_open(struct sr_context *ctx,
252                 struct sr_resource *res, int type, const char *name)
253 {
254         int ret;
255
256         res->size = 0;
257         res->handle = NULL;
258         res->type = type;
259
260         ret = (*ctx->resource_open_cb)(res, name, ctx->resource_cb_data);
261
262         if (ret != SR_OK)
263                 sr_err("Failed to open resource '%s'.", name);
264
265         return ret;
266 }
267
268 /**
269  * Close resource.
270  *
271  * @param ctx libsigrok context. Must not be NULL.
272  * @param[inout] res Resource descriptor. Must not be NULL.
273  *
274  * @retval SR_OK Success.
275  * @retval SR_ERR_ARG Invalid argument.
276  * @retval SR_ERR Other error.
277  *
278  * @private
279  */
280 SR_PRIV int sr_resource_close(struct sr_context *ctx, struct sr_resource *res)
281 {
282         int ret;
283
284         ret = (*ctx->resource_close_cb)(res, ctx->resource_cb_data);
285
286         if (ret != SR_OK)
287                 sr_err("Failed to close resource.");
288
289         return ret;
290 }
291
292 /**
293  * Read resource data.
294  *
295  * @param ctx libsigrok context. Must not be NULL.
296  * @param[in] res Resource descriptor. Must not be NULL.
297  * @param[out] buf Buffer to store @a count bytes into. Must not be NULL.
298  * @param count Number of bytes to read.
299  *
300  * @return The number of bytes actually read, or a negative value on error.
301  * @retval SR_ERR_ARG Invalid argument.
302  * @retval SR_ERR Other error.
303  *
304  * @private
305  */
306 SR_PRIV ssize_t sr_resource_read(struct sr_context *ctx,
307                 const struct sr_resource *res, void *buf, size_t count)
308 {
309         ssize_t n_read;
310
311         n_read = (*ctx->resource_read_cb)(res, buf, count,
312                         ctx->resource_cb_data);
313         if (n_read < 0)
314                 sr_err("Failed to read resource.");
315
316         return n_read;
317 }
318
319 /**
320  * Load a resource into memory.
321  *
322  * @param ctx libsigrok context. Must not be NULL.
323  * @param type Resource type ID.
324  * @param name Name of the resource. Must not be NULL.
325  * @param[out] size Size in bytes of the returned buffer. Must not be NULL.
326  * @param max_size Size limit. Error out if the resource is larger than this.
327  *
328  * @return A buffer containing the resource data, or NULL on failure. Must
329  *  be freed by the caller using g_free().
330  *
331  * @private
332  */
333 SR_PRIV void *sr_resource_load(struct sr_context *ctx,
334                 int type, const char *name, size_t *size, size_t max_size)
335 {
336         struct sr_resource res;
337         void *buf;
338         ssize_t n_read;
339
340         if (sr_resource_open(ctx, &res, type, name) != SR_OK)
341                 return NULL;
342
343         if (res.size > max_size) {
344                 sr_err("Size %" PRIu64 " of '%s' exceeds limit %zu.",
345                         res.size, name, max_size);
346                 sr_resource_close(ctx, &res);
347                 return NULL;
348         }
349         buf = g_try_malloc(res.size);
350         if (!buf) {
351                 sr_err("Failed to allocate buffer for '%s'.", name);
352                 sr_resource_close(ctx, &res);
353                 return NULL;
354         }
355
356         n_read = sr_resource_read(ctx, &res, buf, res.size);
357         sr_resource_close(ctx, &res);
358
359         if (n_read < 0 || (size_t)n_read != res.size) {
360                 if (n_read >= 0)
361                         sr_err("Failed to read '%s': premature end of file.",
362                                 name);
363                 g_free(buf);
364                 return NULL;
365         }
366
367         *size = res.size;
368         return buf;
369 }
370
371 /** @} */