Merge "res_pjsip_registrar.c: Prevent potential double free if AOR is not found"
[asterisk/asterisk.git] / main / media_cache.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2015, Matt Jordan
5  *
6  * Matt Jordan <mjordan@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*!
20  * \file
21  * \brief An in-memory media cache
22  *
23  * \author \verbatim Matt Jordan <mjordan@digium.com> \endverbatim
24  *
25  */
26
27 /*** MODULEINFO
28         <support_level>core</support_level>
29  ***/
30
31 #include "asterisk.h"
32
33 #include <sys/stat.h>
34 #include "asterisk/config.h"
35 #include "asterisk/bucket.h"
36 #include "asterisk/astdb.h"
37 #include "asterisk/cli.h"
38 #include "asterisk/file.h"
39 #include "asterisk/media_cache.h"
40
41 /*! The name of the AstDB family holding items in the cache. */
42 #define AST_DB_FAMILY "MediaCache"
43
44 /*! Length of 'MediaCache' + 2 '/' characters */
45 #define AST_DB_FAMILY_LEN 12
46
47 /*! Number of buckets in the ao2 container holding our media items */
48 #define AO2_BUCKETS 61
49
50 /*! Our one and only container holding media items */
51 static struct ao2_container *media_cache;
52
53 int ast_media_cache_exists(const char *uri)
54 {
55         struct ast_bucket_file *bucket_file;
56
57         if (ast_strlen_zero(uri)) {
58                 return 0;
59         }
60
61         bucket_file = ao2_find(media_cache, uri, OBJ_SEARCH_KEY);
62         if (bucket_file) {
63                 ao2_ref(bucket_file, -1);
64                 return 1;
65         }
66
67         /* Check to see if any bucket implementation could return this item */
68         bucket_file = ast_bucket_file_retrieve(uri);
69         if (bucket_file) {
70                 ao2_ref(bucket_file, -1);
71                 return 1;
72         }
73
74         return 0;
75 }
76
77 /*!
78  * \internal
79  * \brief Sync \c bucket_file metadata to the AstDB
80  */
81 static int metadata_sync_to_astdb(void *obj, void *arg, int flags)
82 {
83         struct ast_bucket_metadata *metadata = obj;
84         const char *hash = arg;
85
86         ast_db_put(hash, metadata->name, metadata->value);
87
88         return 0;
89 }
90
91 /*!
92  * \internal
93  * \brief Sync a media cache item to the AstDB
94  * \param bucket_file The \c ast_bucket_file media cache item to sync
95  */
96 static void media_cache_item_sync_to_astdb(struct ast_bucket_file *bucket_file)
97 {
98         char hash[41]; /* 40 character SHA1 hash */
99
100         ast_sha1_hash(hash, ast_sorcery_object_get_id(bucket_file));
101         if (ast_db_put(AST_DB_FAMILY, ast_sorcery_object_get_id(bucket_file), hash)) {
102                 return;
103         }
104
105         ast_db_put(hash, "path", bucket_file->path);
106         ast_bucket_file_metadata_callback(bucket_file, metadata_sync_to_astdb, hash);
107 }
108
109 /*!
110  * \internal
111  * \brief Delete a media cache item from the AstDB
112  * \param bucket_file The \c ast_bucket_file media cache item to delete
113  */
114 static void media_cache_item_del_from_astdb(struct ast_bucket_file *bucket_file)
115 {
116         char *hash_value;
117
118         if (ast_db_get_allocated(AST_DB_FAMILY, ast_sorcery_object_get_id(bucket_file), &hash_value)) {
119                 return;
120         }
121
122         ast_db_deltree(hash_value, NULL);
123         ast_db_del(AST_DB_FAMILY, ast_sorcery_object_get_id(bucket_file));
124         ast_free(hash_value);
125 }
126
127 /*!
128  * \internal
129  * \brief Normalize the value of a Content-Type header
130  *
131  * This will trim off any optional parameters after the type/subtype.
132  */
133 static void normalize_content_type_header(char *content_type)
134 {
135         char *params = strchr(content_type, ';');
136
137         if (params) {
138                 *params-- = 0;
139                 while (params > content_type && (*params == ' ' || *params == '\t')) {
140                         *params-- = 0;
141                 }
142         }
143 }
144
145 /*!
146  * \internal
147  * \brief Update the name of the file backing a \c bucket_file
148  * \param preferred_file_name The preferred name of the backing file
149  */
150 static void bucket_file_update_path(struct ast_bucket_file *bucket_file,
151         const char *preferred_file_name)
152 {
153         char *ext;
154
155         if (!ast_strlen_zero(preferred_file_name) && strcmp(bucket_file->path, preferred_file_name)) {
156                 /* Use the preferred file name if available */
157
158                 rename(bucket_file->path, preferred_file_name);
159                 ast_copy_string(bucket_file->path, preferred_file_name,
160                         sizeof(bucket_file->path));
161         } else if (!strchr(bucket_file->path, '.') && (ext = strrchr(ast_sorcery_object_get_id(bucket_file), '.'))) {
162                 /* If we don't have a file extension and were provided one in the URI, use it */
163                 char found_ext[32];
164                 char new_path[PATH_MAX + sizeof(found_ext)];
165
166                 ast_bucket_file_metadata_set(bucket_file, "ext", ext);
167
168                 /* Don't pass '.' while checking for supported extension */
169                 if (!ast_get_format_for_file_ext(ext + 1)) {
170                         /* If the file extension passed in the URI isn't supported check for the
171                          * extension based on the MIME type passed in the Content-Type header before
172                          * giving up.
173                          * If a match is found then retrieve the extension from the supported list
174                          * corresponding to the mime-type and use that to rename the file */
175                         struct ast_bucket_metadata *header = ast_bucket_file_metadata_get(bucket_file, "content-type");
176                         if (header) {
177                                 char *mime_type = ast_strdup(header->value);
178                                 if (mime_type) {
179                                         normalize_content_type_header(mime_type);
180                                         if (!ast_strlen_zero(mime_type)) {
181                                                 if (ast_get_extension_for_mime_type(mime_type, found_ext, sizeof(found_ext))) {
182                                                         ext = found_ext;
183                                                 }
184                                         }
185                                         ast_free(mime_type);
186                                 }
187                         }
188                 }
189
190                 snprintf(new_path, sizeof(new_path), "%s%s", bucket_file->path, ext);
191                 rename(bucket_file->path, new_path);
192                 ast_copy_string(bucket_file->path, new_path, sizeof(bucket_file->path));
193         }
194 }
195
196 int ast_media_cache_retrieve(const char *uri, const char *preferred_file_name,
197         char *file_path, size_t len)
198 {
199         struct ast_bucket_file *bucket_file;
200         char *ext;
201         SCOPED_AO2LOCK(media_lock, media_cache);
202
203         if (ast_strlen_zero(uri)) {
204                 return -1;
205         }
206
207         /* First, retrieve from the ao2 cache here. If we find a bucket_file
208          * matching the requested URI, ask the appropriate backend if it is
209          * stale. If not; return it.
210          */
211         bucket_file = ao2_find(media_cache, uri, OBJ_SEARCH_KEY | OBJ_NOLOCK);
212         if (bucket_file) {
213                 if (!ast_bucket_file_is_stale(bucket_file)
214                         && ast_file_is_readable(bucket_file->path)) {
215                         ast_copy_string(file_path, bucket_file->path, len);
216                         if ((ext = strrchr(file_path, '.'))) {
217                                 *ext = '\0';
218                         }
219                         ao2_ref(bucket_file, -1);
220
221                         ast_debug(5, "Returning media at local file: %s\n", file_path);
222                         return 0;
223                 }
224
225                 /* Stale! Remove the item completely, as we're going to replace it next */
226                 ao2_unlink_flags(media_cache, bucket_file, OBJ_NOLOCK);
227                 ast_bucket_file_delete(bucket_file);
228                 ao2_ref(bucket_file, -1);
229         }
230
231         /* Either this is new or the resource is stale; do a full retrieve
232          * from the appropriate bucket_file backend
233          */
234         bucket_file = ast_bucket_file_retrieve(uri);
235         if (!bucket_file) {
236                 ast_debug(2, "Failed to obtain media at '%s'\n", uri);
237                 return -1;
238         }
239
240         /* We can manipulate the 'immutable' bucket_file here, as we haven't
241          * let anyone know of its existence yet
242          */
243         bucket_file_update_path(bucket_file, preferred_file_name);
244         media_cache_item_sync_to_astdb(bucket_file);
245         ast_copy_string(file_path, bucket_file->path, len);
246         if ((ext = strrchr(file_path, '.'))) {
247                 *ext = '\0';
248         }
249         ao2_link_flags(media_cache, bucket_file, OBJ_NOLOCK);
250         ao2_ref(bucket_file, -1);
251
252         ast_debug(5, "Returning media at local file: %s\n", file_path);
253
254         return 0;
255 }
256
257 int ast_media_cache_retrieve_metadata(const char *uri, const char *key,
258         char *value, size_t len)
259 {
260         struct ast_bucket_file *bucket_file;
261         struct ast_bucket_metadata *metadata;
262
263         if (ast_strlen_zero(uri) || ast_strlen_zero(key) || !value) {
264                 return -1;
265         }
266
267         bucket_file = ao2_find(media_cache, uri, OBJ_SEARCH_KEY);
268         if (!bucket_file) {
269                 return -1;
270         }
271
272         metadata = ao2_find(bucket_file->metadata, key, OBJ_SEARCH_KEY);
273         if (!metadata) {
274                 ao2_ref(bucket_file, -1);
275                 return -1;
276         }
277         ast_copy_string(value, metadata->value, len);
278
279         ao2_ref(metadata, -1);
280         ao2_ref(bucket_file, -1);
281         return 0;
282 }
283
284 int ast_media_cache_create_or_update(const char *uri, const char *file_path,
285         struct ast_variable *metadata)
286 {
287         struct ast_bucket_file *bucket_file;
288         struct ast_variable *it_metadata;
289         struct stat st;
290         char tmp[128];
291         char *ext;
292         char *file_path_ptr;
293         int created = 0;
294         SCOPED_AO2LOCK(media_lock, media_cache);
295
296         if (ast_strlen_zero(file_path) || ast_strlen_zero(uri)) {
297                 return -1;
298         }
299         file_path_ptr = ast_strdupa(file_path);
300
301         if (stat(file_path, &st)) {
302                 ast_log(LOG_WARNING, "Unable to obtain information for file %s for URI %s\n",
303                         file_path, uri);
304                 return -1;
305         }
306
307         bucket_file = ao2_find(media_cache, uri, OBJ_SEARCH_KEY | OBJ_NOLOCK);
308         if (bucket_file) {
309                 struct ast_bucket_file *clone;
310
311                 clone = ast_bucket_file_clone(bucket_file);
312                 if (!clone) {
313                         ao2_ref(bucket_file, -1);
314                         return -1;
315                 }
316
317                 /* Remove the old bucket_file. We'll replace it if we succeed below. */
318                 ao2_unlink_flags(media_cache, bucket_file, OBJ_NOLOCK);
319                 ao2_ref(bucket_file, -1);
320
321                 bucket_file = clone;
322         } else {
323                 bucket_file = ast_bucket_file_alloc(uri);
324                 if (!bucket_file) {
325                         ast_log(LOG_WARNING, "Failed to create file storage for %s and %s\n",
326                                 uri, file_path);
327                         return -1;
328                 }
329                 created = 1;
330         }
331
332         strcpy(bucket_file->path, file_path);
333         bucket_file->created.tv_sec = st.st_ctime;
334         bucket_file->modified.tv_sec = st.st_mtime;
335
336         snprintf(tmp, sizeof(tmp), "%ld", (long)st.st_atime);
337         ast_bucket_file_metadata_set(bucket_file, "accessed", tmp);
338
339         snprintf(tmp, sizeof(tmp), "%jd", (intmax_t)st.st_size);
340         ast_bucket_file_metadata_set(bucket_file, "size", tmp);
341
342         ext = strrchr(file_path_ptr, '.');
343         if (ext) {
344                 ast_bucket_file_metadata_set(bucket_file, "ext", ext + 1);
345         }
346
347         for (it_metadata = metadata; it_metadata; it_metadata = it_metadata->next) {
348                 ast_bucket_file_metadata_set(bucket_file, it_metadata->name, it_metadata->value);
349         }
350
351         if (created && ast_bucket_file_create(bucket_file)) {
352                 ast_log(LOG_WARNING, "Failed to create media for %s\n", uri);
353                 ao2_ref(bucket_file, -1);
354                 return -1;
355         }
356         media_cache_item_sync_to_astdb(bucket_file);
357
358         ao2_link_flags(media_cache, bucket_file, OBJ_NOLOCK);
359         ao2_ref(bucket_file, -1);
360         return 0;
361 }
362
363 int ast_media_cache_delete(const char *uri)
364 {
365         struct ast_bucket_file *bucket_file;
366         int res;
367
368         if (ast_strlen_zero(uri)) {
369                 return -1;
370         }
371
372         bucket_file = ao2_find(media_cache, uri, OBJ_SEARCH_KEY | OBJ_UNLINK);
373         if (!bucket_file) {
374                 return -1;
375         }
376
377         res = ast_bucket_file_delete(bucket_file);
378         media_cache_item_del_from_astdb(bucket_file);
379
380         ao2_ref(bucket_file, -1);
381
382         return res;
383 }
384
385 /*!
386  * \internal
387  * \brief Remove a media cache item from the AstDB
388  * \param uri The unique URI that represents the item in the cache
389  * \param hash The hash key for the item in the AstDB
390  */
391 static void media_cache_remove_from_astdb(const char *uri, const char *hash)
392 {
393         ast_db_del(AST_DB_FAMILY, uri + AST_DB_FAMILY_LEN);
394         ast_db_deltree(hash, NULL);
395 }
396
397 /*!
398  * \internal
399  * \brief Create an item in the media cache from entries in the AstDB
400  * \param uri The unique URI that represents the item in the cache
401  * \param hash The hash key for the item in the AstDB
402  * \retval 0 success
403  * \retval -1 failure
404  */
405 static int media_cache_item_populate_from_astdb(const char *uri, const char *hash)
406 {
407         struct ast_bucket_file *bucket_file;
408         struct ast_db_entry *db_tree;
409         struct ast_db_entry *db_entry;
410         struct stat st;
411
412         bucket_file = ast_bucket_file_alloc(uri);
413         if (!bucket_file) {
414                 return -1;
415         }
416
417         db_tree = ast_db_gettree(hash, NULL);
418         for (db_entry = db_tree; db_entry; db_entry = db_entry->next) {
419                 const char *key = strchr(db_entry->key + 1, '/');
420
421                 if (ast_strlen_zero(key)) {
422                         continue;
423                 }
424                 key++;
425
426                 if (!strcasecmp(key, "path")) {
427                         strcpy(bucket_file->path, db_entry->data);
428
429                         if (stat(bucket_file->path, &st)) {
430                                 ast_log(LOG_WARNING, "Unable to obtain information for file %s for URI %s\n",
431                                         bucket_file->path, uri);
432                                 ao2_ref(bucket_file, -1);
433                                 ast_db_freetree(db_tree);
434                                 return -1;
435                         }
436                 } else {
437                         ast_bucket_file_metadata_set(bucket_file, key, db_entry->data);
438                 }
439         }
440         ast_db_freetree(db_tree);
441
442         if (ast_strlen_zero(bucket_file->path)) {
443                 ao2_ref(bucket_file, -1);
444                 ast_log(LOG_WARNING, "Failed to restore media cache item for '%s' from AstDB: no 'path' specified\n",
445                         uri);
446                 return -1;
447         }
448
449         ao2_link(media_cache, bucket_file);
450         ao2_ref(bucket_file, -1);
451
452         return 0;
453 }
454
455 /*!
456  * \internal
457  * \brief Populate the media cache from entries in the AstDB
458  */
459 static void media_cache_populate_from_astdb(void)
460 {
461         struct ast_db_entry *db_entry;
462         struct ast_db_entry *db_tree;
463
464         db_tree = ast_db_gettree(AST_DB_FAMILY, NULL);
465         for (db_entry = db_tree; db_entry; db_entry = db_entry->next) {
466                 if (media_cache_item_populate_from_astdb(db_entry->key + AST_DB_FAMILY_LEN, db_entry->data)) {
467                         media_cache_remove_from_astdb(db_entry->key, db_entry->data);
468                 }
469         }
470         ast_db_freetree(db_tree);
471 }
472
473 /*!
474  * \internal
475  * \brief ao2 callback function for \ref media_cache_handle_show_all
476  */
477 static int media_cache_prnt_summary(void *obj, void *arg, int flags)
478 {
479 #define FORMAT_ROW "%-40s\n\t%-40s\n"
480         struct ast_bucket_file *bucket_file = obj;
481         struct ast_cli_args *a = arg;
482
483         ast_cli(a->fd, FORMAT_ROW, ast_sorcery_object_get_id(bucket_file), bucket_file->path);
484
485 #undef FORMAT_ROW
486         return CMP_MATCH;
487 }
488
489 static char *media_cache_handle_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
490 {
491         switch (cmd) {
492         case CLI_INIT:
493                 e->command = "media cache show all";
494                 e->usage =
495                         "Usage: media cache show all\n"
496                         "       Display a summary of all current items in the media cache.\n";
497                 return NULL;
498         case CLI_GENERATE:
499                 return NULL;
500         }
501
502         if (a->argc != 4) {
503                 return CLI_SHOWUSAGE;
504         }
505
506         ast_cli(a->fd, "URI\n\tLocal File\n");
507         ast_cli(a->fd, "---------------\n");
508         ao2_callback(media_cache, OBJ_NODATA | OBJ_MULTIPLE, media_cache_prnt_summary, a);
509
510         return CLI_SUCCESS;
511 }
512
513 /*!
514  * \internal
515  * \brief CLI tab completion function for URIs
516  */
517 static char *cli_complete_uri(const char *word)
518 {
519         struct ast_bucket_file *bucket_file;
520         struct ao2_iterator it_media_items;
521         int wordlen = strlen(word);
522
523         it_media_items = ao2_iterator_init(media_cache, 0);
524         while ((bucket_file = ao2_iterator_next(&it_media_items))) {
525                 if (!strncasecmp(word, ast_sorcery_object_get_id(bucket_file), wordlen)) {
526                         ast_cli_completion_add(ast_strdup(ast_sorcery_object_get_id(bucket_file)));
527                 }
528                 ao2_ref(bucket_file, -1);
529         }
530         ao2_iterator_destroy(&it_media_items);
531
532         return NULL;
533 }
534
535 static char *media_cache_handle_show_item(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
536 {
537 #define FORMAT_ROW "\t%20s: %-40.40s\n"
538         struct ast_bucket_file *bucket_file;
539         struct ao2_iterator it_metadata;
540         struct ast_bucket_metadata *metadata;
541
542         switch (cmd) {
543         case CLI_INIT:
544                 e->command = "media cache show";
545                 e->usage =
546                         "Usage: media cache show <uri>\n"
547                         "       Display all information about a particular item in the media cache.\n";
548                 return NULL;
549         case CLI_GENERATE:
550                 return a->pos == e->args ? cli_complete_uri(a->word) : NULL;
551         }
552
553         if (a->argc != 4) {
554                 return CLI_SHOWUSAGE;
555         }
556
557         bucket_file = ao2_find(media_cache, a->argv[3], OBJ_SEARCH_KEY);
558         if (!bucket_file) {
559                 ast_cli(a->fd, "Unable to find '%s' in the media cache\n", a->argv[3]);
560                 return CLI_SUCCESS;
561         }
562
563         ast_cli(a->fd, "URI: %s\n", ast_sorcery_object_get_id(bucket_file));
564         ast_cli(a->fd, "%s\n", "----------------------------------------");
565         ast_cli(a->fd, FORMAT_ROW, "Path", bucket_file->path);
566
567         it_metadata = ao2_iterator_init(bucket_file->metadata, 0);
568         while ((metadata = ao2_iterator_next(&it_metadata))) {
569                 ast_cli(a->fd, FORMAT_ROW, metadata->name, metadata->value);
570                 ao2_ref(metadata, -1);
571         }
572         ao2_iterator_destroy(&it_metadata);
573
574         ao2_ref(bucket_file, -1);
575 #undef FORMAT_ROW
576         return CLI_SUCCESS;
577 }
578
579 static char *media_cache_handle_delete_item(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
580 {
581         switch (cmd) {
582         case CLI_INIT:
583                 e->command = "media cache delete";
584                 e->usage =
585                         "Usage: media cache delete <uri>\n"
586                         "       Delete an item from the media cache.\n\n"
587                         "       Note that this will also remove any local storage of the media associated\n"
588                         "       with the URI, and will inform the backend supporting the URI scheme that\n"
589                         "       it should remove the item.\n";
590                 return NULL;
591         case CLI_GENERATE:
592                 return a->pos == e->args ? cli_complete_uri(a->word) : NULL;
593         }
594
595         if (a->argc != 4) {
596                 return CLI_SHOWUSAGE;
597         }
598
599         if (ast_media_cache_delete(a->argv[3])) {
600                 ast_cli(a->fd, "Unable to delete '%s'\n", a->argv[3]);
601         } else {
602                 ast_cli(a->fd, "Deleted '%s' from the media cache\n", a->argv[3]);
603         }
604
605         return CLI_SUCCESS;
606 }
607
608 static char *media_cache_handle_refresh_item(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
609 {
610         char file_path[PATH_MAX];
611
612         switch (cmd) {
613         case CLI_INIT:
614                 e->command = "media cache refresh";
615                 e->usage =
616                         "Usage: media cache refresh <uri>\n"
617                         "       Ask for a refresh of a particular URI.\n\n"
618                         "       If the item does not already exist in the media cache, the item will be\n"
619                         "       populated from the backend supporting the URI scheme.\n";
620                 return NULL;
621         case CLI_GENERATE:
622                 return a->pos == e->args ? cli_complete_uri(a->word) : NULL;
623         }
624
625         if (a->argc != 4) {
626                 return CLI_SHOWUSAGE;
627         }
628
629         if (ast_media_cache_retrieve(a->argv[3], NULL, file_path, sizeof(file_path))) {
630                 ast_cli(a->fd, "Unable to refresh '%s'\n", a->argv[3]);
631         } else {
632                 ast_cli(a->fd, "Refreshed '%s' to local storage '%s'\n", a->argv[3], file_path);
633         }
634
635         return CLI_SUCCESS;
636 }
637
638 static char *media_cache_handle_create_item(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
639 {
640         switch (cmd) {
641         case CLI_INIT:
642                 e->command = "media cache create";
643                 e->usage =
644                         "Usage: media cache create <uri> <file>\n"
645                         "       Create an item in the media cache by associating a local media file with\n"
646                         "       some URI.\n";
647                 return NULL;
648         case CLI_GENERATE:
649                 return NULL;
650         }
651
652         if (a->argc != 5) {
653                 return CLI_SHOWUSAGE;
654         }
655
656         if (ast_media_cache_create_or_update(a->argv[3], a->argv[4], NULL)) {
657                 ast_cli(a->fd, "Unable to create '%s' associated with local file '%s'\n",
658                         a->argv[3], a->argv[4]);
659         } else {
660                 ast_cli(a->fd, "Created '%s' for '%s' in the media cache\n",
661                         a->argv[3], a->argv[4]);
662         }
663
664         return CLI_SUCCESS;
665 }
666
667 static struct ast_cli_entry cli_media_cache[] = {
668         AST_CLI_DEFINE(media_cache_handle_show_all, "Show all items in the media cache"),
669         AST_CLI_DEFINE(media_cache_handle_show_item, "Show a single item in the media cache"),
670         AST_CLI_DEFINE(media_cache_handle_delete_item, "Remove an item from the media cache"),
671         AST_CLI_DEFINE(media_cache_handle_refresh_item, "Refresh an item in the media cache"),
672         AST_CLI_DEFINE(media_cache_handle_create_item, "Create an item in the media cache"),
673 };
674
675 /*!
676  * \internal
677  * \brief Shutdown the media cache
678  */
679 static void media_cache_shutdown(void)
680 {
681         ao2_cleanup(media_cache);
682         media_cache = NULL;
683
684         ast_cli_unregister_multiple(cli_media_cache, ARRAY_LEN(cli_media_cache));
685 }
686
687 int ast_media_cache_init(void)
688 {
689         ast_register_cleanup(media_cache_shutdown);
690
691         media_cache = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, AO2_BUCKETS,
692                 ast_sorcery_object_id_hash, NULL, ast_sorcery_object_id_compare);
693         if (!media_cache) {
694                 return -1;
695         }
696
697         if (ast_cli_register_multiple(cli_media_cache, ARRAY_LEN(cli_media_cache))) {
698                 return -1;
699         }
700
701         media_cache_populate_from_astdb();
702
703         return 0;
704 }