Remove use of deprecated readdir_r
[src/app-framework-main.git] / src / afm-db.c
1 /*
2  Copyright 2015, 2016, 2017 IoT.bzh
3
4  author: José Bollo <jose.bollo@iot.bzh>
5
6  Licensed under the Apache License, Version 2.0 (the "License");
7  you may not use this file except in compliance with the License.
8  You may obtain a copy of the License at
9
10      http://www.apache.org/licenses/LICENSE-2.0
11
12  Unless required by applicable law or agreed to in writing, software
13  distributed under the License is distributed on an "AS IS" BASIS,
14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  See the License for the specific language governing permissions and
16  limitations under the License.
17 */
18
19 #include <stdlib.h>
20 #include <assert.h>
21 #include <string.h>
22 #include <errno.h>
23 #include <dirent.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26 #include <sys/types.h>
27
28 #include <json-c/json.h>
29
30 #include "utils-json.h"
31 #include "wgt-info.h"
32 #include "afm-db.h"
33
34 /*
35  * The json object recorded by application widget
36  * has the following contents:
37  *  {
38  *    id: STRING, the application identifier without version info
39  *    path: STRING, the path of the root directory for the application
40  *    content: STRING, the relative path to the entryu point of the application
41  *    type: STRING, the mime type describing the type 'content'
42  *    bindings: ARRAY, array of bindings
43  *      [
44  *        STRING, path to the binding
45  *      ]
46  *    public: OBJECT, public content describing the application widget
47  *      {  
48  *        id: STRING, the application identifier "idaver"
49  *        version: STRING, the full version of the application
50  *        width: INTEGER, the width indication or 0
51  *        height: INTEGER, the height indication or 0
52  *        name: STRING, the name of the application
53  *        description: STRING, the description of the application
54  *        shortname: STRING, the short name of the application
55  *        author: STRING, the author of the application
56  *      }
57  */
58
59 /*
60  * The structure afm_apps records the data about applications
61  * for several accesses.
62  */
63 struct afm_apps {
64         struct json_object *pubarr; /* array of the public data of apps */
65         struct json_object *direct; /* hash of applications by their "idaver" */
66         struct json_object *byapp;  /* hash of applications by their id */
67 };
68
69 /*
70  * Two types of directory are handled:
71  *  - root directories: contains subdirectories appid/version
72  *         containing the applications
73  *  - application directories: it contains an application
74  */
75 enum dir_type {
76         type_root, /* type for root directory */
77         type_app   /* type for application directory */
78 };
79
80 /*
81  * Structure for recording a path to application(s)
82  * in the list of directories.
83  */
84 struct afm_db_dir {
85         struct afm_db_dir *next; /* link to the next item of the list */
86         enum dir_type type;      /* the type of the path */
87         char path[1];            /* the path of the directory */
88 };
89
90 /*
91  * The structure afm_db records the applications
92  * for a set of directories recorded as a linked list
93  */
94 struct afm_db {
95         int refcount;               /* count of references to the structure */
96         struct afm_db_dir *dirhead; /* first directory of the set */
97         struct afm_db_dir *dirtail; /* last directory of the set */
98         struct afm_apps applications; /* the data about applications */
99 };
100
101 /*
102  * The structure enumdata records data used when enumerating
103  * application directories of a root directory.
104  */
105 struct enumdata {
106         char path[PATH_MAX];   /* "current" computed path */
107         int length;            /* length of path */
108         struct afm_apps apps;  /* */
109 };
110
111 /*
112  * Release the data of the afm_apps object 'apps'.
113  */
114 static void apps_put(struct afm_apps *apps)
115 {
116         json_object_put(apps->pubarr);
117         json_object_put(apps->direct);
118         json_object_put(apps->byapp);
119 }
120
121 /*
122  * Adds the application widget 'desc' of the directory 'path' to the
123  * afm_apps object 'apps'.
124  * Returns 0 in case of success.
125  * Returns -1 and set errno in case of error
126  */
127 static int addwgt(struct afm_apps *apps, const char *path,
128                                                 const struct wgt_desc *desc)
129 {
130         const struct wgt_desc_feature *feat;
131         struct json_object *priv, *pub, *bya, *plugs, *str;
132
133         /* create the application structure */
134         priv = json_object_new_object();
135         if (!priv)
136                 return -1;
137
138         pub = j_add_new_object(priv, "public");
139         if (!pub)
140                 goto error;
141
142         plugs = j_add_new_array(priv, "bindings");
143         if (!plugs)
144                 goto error;
145
146         if(!j_add_string(priv, "id", desc->id)
147         || !j_add_string(priv, "path", path)
148         || !j_add_string(priv, "content", desc->content_src)
149         || !j_add_string(priv, "type", desc->content_type)
150         || !j_add_string(pub, "id", desc->idaver)
151         || !j_add_string(pub, "version", desc->version)
152         || !j_add_integer(pub, "width", desc->width)
153         || !j_add_integer(pub, "height", desc->height)
154         || !j_add_string(pub, "name", desc->name)
155         || !j_add_string(pub, "description", desc->description)
156         || !j_add_string(pub, "shortname", desc->name_short)
157         || !j_add_string(pub, "author", desc->author))
158                 goto error;
159
160         /* extract bindings from features */
161         feat = desc->features;
162         while (feat) {
163                 static const char prefix[] = FWK_PREFIX_BINDING;
164                 if (!memcmp(feat->name, prefix, sizeof prefix - 1)) {
165                         str = json_object_new_string (
166                                         feat->name + sizeof prefix - 1);
167                         if (str == NULL)
168                                 goto error;
169                         if (json_object_array_add(plugs, str)) {
170                                 json_object_put(str);
171                                 goto error;
172                         }
173                 }
174                 feat = feat->next;
175         }
176
177         /* record the application structure */
178         if (!j_add(apps->direct, desc->idaver, priv))
179                 goto error;
180
181         if (json_object_array_add(apps->pubarr, pub))
182                 goto error;
183         json_object_get(pub);
184
185         if (!json_object_object_get_ex(apps->byapp, desc->id, &bya)) {
186                 bya = j_add_new_object(apps->byapp, desc->id);
187                 if (!bya)
188                         goto error;
189         }
190
191         if (!j_add(bya, desc->version, priv))
192                 goto error;
193         json_object_get(priv);
194         return 0;
195
196 error:
197         json_object_put(priv);
198         return -1;
199 }
200
201 /*
202  * Adds the application widget in the directory 'path' to the
203  * afm_apps object 'apps'.
204  * Returns 0 in case of success.
205  * Returns -1 and set errno in case of error
206  */
207 static int addapp(struct afm_apps *apps, const char *path)
208 {
209         int rc;
210         struct wgt_info *info;
211
212         /* connect to the widget */
213         info = wgt_info_createat(AT_FDCWD, path, 0, 1, 0);
214         if (info == NULL) {
215                 if (errno == ENOENT)
216                         return 0; /* silently ignore bad directories */
217                 return -1;
218         }
219         /* adds the widget */
220         rc = addwgt(apps, path, wgt_info_desc(info));
221         wgt_info_unref(info);
222         return rc;
223 }
224
225 /*
226  * Enumerate the directories designated by 'data' and call the
227  * function 'callto' for each of them.
228  */
229 static int enumentries(struct enumdata *data, int (*callto)(struct enumdata *))
230 {
231         DIR *dir;
232         int rc;
233         char *beg;
234         struct dirent *e;
235         size_t len;
236
237         /* opens the directory */
238         dir = opendir(data->path);
239         if (!dir)
240                 return -1;
241
242         /* prepare appending entry names */
243         beg = data->path + data->length;
244         *beg++ = '/';
245
246         /* enumerate entries */
247         for(;;) {
248                 errno = 0;
249                 e = readdir(dir);
250                 if (!e) {
251                         rc = !errno - 1;
252                         break;
253                 }
254                 if (e->d_name[0] != '.' || (e->d_name[1]
255                         && (e->d_name[1] != '.' || e->d_name[2]))) {
256                         /* prepare callto */
257                         len = strlen(e->d_name);
258                         if (beg + len >= data->path + sizeof data->path) {
259                                 errno = ENAMETOOLONG;
260                                 return -1;
261                         }
262                         data->length = (int)(stpcpy(beg, e->d_name)
263                                                                 - data->path);
264                         /* call the function */
265                         rc = callto(data);
266                         if (rc)
267                                 break;
268                 }       
269         }
270         closedir(dir);
271         return rc;
272 }
273
274 /*
275  * called for each version directory.
276  */
277 static int recordapp(struct enumdata *data)
278 {
279         return addapp(&data->apps, data->path);
280 }
281
282 /*
283  * called for each application directory.
284  * enumerate directories of the existing versions.
285  */
286 static int enumvers(struct enumdata *data)
287 {
288         int rc = enumentries(data, recordapp);
289         return !rc || errno != ENOTDIR ? 0 : rc;
290 }
291
292 /*
293  * Adds the directory of 'path' and 'type' to the afm_db object 'afdb'.
294  * Returns 0 in case of success.
295  * Returns -1 and set errno in case of error
296  * Possible errno values: ENOMEM, ENAMETOOLONG
297  */
298 static int add_dir(struct afm_db *afdb, const char *path, enum dir_type type)
299 {
300         struct afm_db_dir *dir;
301         size_t len;
302
303         assert(afdb);
304
305         /* check size */
306         len = strlen(path);
307         if (len >= PATH_MAX) {
308                 errno = ENAMETOOLONG;
309                 return -1;
310         }
311
312         /* avoiding duplications */
313         dir = afdb->dirhead;
314         while(dir != NULL && (strcmp(dir->path, path) || dir->type != type))
315                 dir = dir ->next;
316         if (dir != NULL)
317                 return 0;
318
319         /* allocates the structure */
320         dir = malloc(strlen(path) + sizeof * dir);
321         if (dir == NULL) {
322                 errno = ENOMEM;
323                 return -1;
324         }
325
326         /* add it at tail */
327         dir->next = NULL;
328         dir->type = type;
329         strcpy(dir->path, path);
330         if (afdb->dirtail == NULL)
331                 afdb->dirhead = dir;
332         else
333                 afdb->dirtail->next = dir;
334         afdb->dirtail = dir;
335         return 0;
336 }
337
338 /*
339  * Creates an afm_db object and returns it with one reference added.
340  * Return NULL with errno = ENOMEM if memory exhausted.
341  */
342 struct afm_db *afm_db_create()
343 {
344         struct afm_db *afdb = malloc(sizeof * afdb);
345         if (afdb == NULL)
346                 errno = ENOMEM;
347         else {
348                 afdb->refcount = 1;
349                 afdb->dirhead = NULL;
350                 afdb->dirtail = NULL;
351                 afdb->applications.pubarr = NULL;
352                 afdb->applications.direct = NULL;
353                 afdb->applications.byapp = NULL;
354         }
355         return afdb;
356 }
357
358 /*
359  * Adds a reference to an existing afm_db.
360  */
361 void afm_db_addref(struct afm_db *afdb)
362 {
363         assert(afdb);
364         afdb->refcount++;
365 }
366
367 /*
368  * Removes a reference to an existing afm_db object.
369  * Removes the objet if there no more reference to it.
370  */
371 void afm_db_unref(struct afm_db *afdb)
372 {
373         struct afm_db_dir *dir;
374
375         assert(afdb);
376         if (!--afdb->refcount) {
377                 /* no more reference, clean the memory used by the object */
378                 apps_put(&afdb->applications);
379                 while (afdb->dirhead != NULL) {
380                         dir = afdb->dirhead;
381                         afdb->dirhead = dir->next;
382                         free(dir);
383                 }
384                 free(afdb);
385         }
386 }
387
388 /*
389  * Adds the root directory of 'path' to the afm_db object 'afdb'.
390  * Be aware that no check is done on the directory of 'path' that will
391  * only be used within calls to the function 'afm_db_update_applications'.
392  * Returns 0 in case of success.
393  * Returns -1 and set errno in case of error
394  * Possible errno values: ENOMEM, ENAMETOOLONG
395  */
396 int afm_db_add_root(struct afm_db *afdb, const char *path)
397 {
398         return add_dir(afdb, path, type_root);
399 }
400
401 /*
402  * Adds the application directory of 'path' to the afm_db object 'afdb'.
403  * Be aware that no check is done on the directory of 'path' that will
404  * only be used within calls to the function 'afm_db_update_applications'.
405  * Returns 0 in case of success.
406  * Returns -1 and set errno in case of error
407  * Possible errno values: ENOMEM, ENAMETOOLONG
408  */
409 int afm_db_add_application(struct afm_db *afdb, const char *path)
410 {
411         return add_dir(afdb, path, type_app);
412 }
413
414 /*
415  * Regenerate the list of applications of the afm_bd object 'afdb'.
416  * Returns 0 in case of success.
417  * Returns -1 and set errno in case of error
418  */
419 int afm_db_update_applications(struct afm_db *afdb)
420 {
421         int rc;
422         struct enumdata edata;
423         struct afm_apps oldapps;
424         struct afm_db_dir *dir;
425
426         /* create the result */
427         edata.apps.pubarr = json_object_new_array();
428         edata.apps.direct = json_object_new_object();
429         edata.apps.byapp = json_object_new_object();
430         if (edata.apps.pubarr == NULL || edata.apps.direct == NULL
431                                                 || edata.apps.byapp == NULL) {
432                 errno = ENOMEM;
433                 goto error;
434         }
435         /* for each directory of afdb */
436         for (dir = afdb->dirhead ; dir != NULL ; dir = dir->next) {
437                 if (dir->type == type_root) {
438                         edata.length = (int)(stpcpy(edata.path, dir->path)
439                                                                 - edata.path);
440                         assert(edata.length < (int)sizeof edata.path);
441                         /* enumerate the applications */
442                         rc = enumentries(&edata, enumvers);
443                         if (rc)
444                                 goto error;
445                 } else {
446                         rc = addapp(&edata.apps, dir->path);
447                 }
448         }
449         /* commit the result */
450         oldapps = afdb->applications;
451         afdb->applications = edata.apps;
452         apps_put(&oldapps);
453         return 0;
454
455 error:
456         apps_put(&edata.apps);
457         return -1;
458 }
459
460 /*
461  * Ensure that applications of the afm_bd object 'afdb' are listed.
462  * Returns 0 in case of success.
463  * Returns -1 and set errno in case of error
464  */
465 int afm_db_ensure_applications(struct afm_db *afdb)
466 {
467         return afdb->applications.pubarr ? 0 : afm_db_update_applications(afdb);
468 }
469
470 /*
471  * Get the list of the applications public data of the afm_db object 'afdb'.
472  * The list is returned as a JSON-array that must be released using
473  * 'json_object_put'.
474  * Returns NULL in case of error.
475  */
476 struct json_object *afm_db_application_list(struct afm_db *afdb)
477 {
478         return afm_db_ensure_applications(afdb) ? NULL
479                         : json_object_get(afdb->applications.pubarr);
480 }
481
482 /*
483  * Get the private data of the applications of 'id' in the afm_db object 'afdb'.
484  * It returns a JSON-object that must be released using 'json_object_put'.
485  * Returns NULL in case of error.
486  */
487 struct json_object *afm_db_get_application(struct afm_db *afdb, const char *id)
488 {
489         int i;
490         struct json_object *result;
491
492         if (afm_db_ensure_applications(afdb))
493                 return NULL;
494
495         /* search case sensitively */
496         if (json_object_object_get_ex( afdb->applications.direct, id, &result))
497                 return json_object_get(result);
498
499         /* fallback to a case insensitive search */
500         i = json_object_array_length(afdb->applications.pubarr);
501         while (i) {
502                 result = json_object_array_get_idx(afdb->applications.pubarr, --i);
503                 if (result
504                   && json_object_object_get_ex(result, "id", &result)
505                   && !strcasecmp(id, json_object_get_string(result))) {
506                         if (json_object_object_get_ex( afdb->applications.direct, 
507                                                        json_object_get_string(result),
508                                                        &result))
509                                 return json_object_get(result);
510                         else
511                                 return NULL;
512                 }
513         }
514         return NULL;
515 }
516
517 /*
518  * Get the public data of the applications of 'id' in the afm_db object 'afdb'.
519  * It returns a JSON-object that must be released using 'json_object_put'.
520  * Returns NULL in case of error.
521  */
522 struct json_object *afm_db_get_application_public(struct afm_db *afdb,
523                                                         const char *id)
524 {
525         struct json_object *result;
526         struct json_object *priv = afm_db_get_application(afdb, id);
527         if (priv == NULL)
528                 return NULL;
529         if (json_object_object_get_ex(priv, "public", &result))
530                 json_object_get(result);
531         else
532                 result = NULL;
533         json_object_put(priv);
534         return result;
535 }
536
537
538
539
540 #if defined(TESTAPPFWK)
541 #include <stdio.h>
542 int main()
543 {
544 struct afm_db *afdb = afm_db_create();
545 afm_db_add_root(afdb,FWK_APP_DIR);
546 afm_db_update_applications(afdb);
547 printf("array = %s\n", json_object_to_json_string_ext(afdb->applications.pubarr, 3));
548 printf("direct = %s\n", json_object_to_json_string_ext(afdb->applications.direct, 3));
549 printf("byapp = %s\n", json_object_to_json_string_ext(afdb->applications.byapp, 3));
550 return 0;
551 }
552 #endif
553