Asterisk - The Open Source Telephony Project GIT-master-f36a736
stored.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2013, Digium, Inc.
5 *
6 * David M. Lee, II <dlee@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/*! \file
20 *
21 * \brief Stored file operations for Stasis
22 *
23 * \author David M. Lee, II <dlee@digium.com>
24 */
25
26#include "asterisk.h"
27
28#include "asterisk/astobj2.h"
29#include "asterisk/paths.h"
31
32#include <sys/stat.h>
33#include <sys/types.h>
34#include <unistd.h>
35
38 AST_STRING_FIELD(name); /*!< Recording's name */
39 AST_STRING_FIELD(file); /*!< Absolute filename, without extension; for use with streamfile */
40 AST_STRING_FIELD(file_with_ext); /*!< Absolute filename, with extension; for use with everything else */
41 );
42
43 const char *format; /*!< Format name (i.e. filename extension) */
44};
45
46static void stored_recording_dtor(void *obj)
47{
48 struct stasis_app_stored_recording *recording = obj;
49
51}
52
54 struct stasis_app_stored_recording *recording)
55{
56 if (!recording) {
57 return NULL;
58 }
59 return recording->file;
60}
61
63 struct stasis_app_stored_recording *recording)
64{
65 if (!recording) {
66 return NULL;
67 }
68 return recording->file_with_ext;
69}
70
72 struct stasis_app_stored_recording *recording)
73{
74 if (!recording) {
75 return NULL;
76 }
77 return recording->format;
78}
79
80/*!
81 * \brief Split a path into directory and file, resolving canonical directory.
82 *
83 * The path is resolved relative to the recording directory. Both dir and file
84 * are allocated strings, which you must ast_free().
85 *
86 * \param path Path to split.
87 * \param[out] dir Output parameter for directory portion.
88 * \param[out] file Output parameter for the file portion.
89 * \return 0 on success.
90 * \return Non-zero on error.
91 */
92static int split_path(const char *path, char **dir, char **file)
93{
94 RAII_VAR(char *, relative_dir, NULL, ast_free);
95 RAII_VAR(char *, absolute_dir, NULL, ast_free);
96 RAII_VAR(char *, real_dir, NULL, ast_std_free);
97 char *last_slash;
98 const char *file_portion;
99
100 relative_dir = ast_strdup(path);
101 if (!relative_dir) {
102 return -1;
103 }
104
105 last_slash = strrchr(relative_dir, '/');
106 if (last_slash) {
107 *last_slash = '\0';
108 file_portion = last_slash + 1;
109 ast_asprintf(&absolute_dir, "%s/%s",
110 ast_config_AST_RECORDING_DIR, relative_dir);
111 } else {
112 /* There is no directory portion */
113 file_portion = path;
114 *relative_dir = '\0';
116 }
117 if (!absolute_dir) {
118 return -1;
119 }
120
121 real_dir = realpath(absolute_dir, NULL);
122 if (!real_dir) {
123 return -1;
124 }
125
126 *dir = ast_strdup(real_dir); /* Dupe so we can ast_free() */
127 *file = ast_strdup(file_portion);
128 return (*dir && *file) ? 0 : -1;
129}
130
132 const char *file;
133 size_t length;
135};
136
137static int is_recording(const char *filename)
138{
139 const char *ext = strrchr(filename, '.');
140
141 if (!ext) {
142 /* No file extension; not us */
143 return 0;
144 }
145 ++ext;
146
148 ast_debug(5, "Recording %s: unrecognized format %s\n",
149 filename, ext);
150 /* Keep looking */
151 return 0;
152 }
153
154 /* Return the index to the .ext */
155 return ext - filename - 1;
156}
157
158static int handle_find_recording(const char *dir_name, const char *filename, void *obj)
159{
160 struct match_recording_data *data = obj;
161 int num;
162
163 /* If not a recording or the names do not match the keep searching */
164 if (!(num = is_recording(filename))
165 || data->length != num
166 || strncmp(data->file, filename, num)) {
167 return 0;
168 }
169
170 if (ast_asprintf(&data->file_with_ext, "%s/%s", dir_name, filename) < 0) {
171 return -1;
172 }
173
174 return 1;
175}
176
177/*!
178 * \brief Finds a recording in the given directory.
179 *
180 * This function searches for a file with the given file name, with a registered
181 * format that matches its extension.
182 *
183 * \param dir_name Directory to search (absolute path).
184 * \param file File name, without extension.
185 * \return Absolute path of the recording file.
186 * \retval NULL if recording is not found.
187 */
188static char *find_recording(const char *dir_name, const char *file)
189{
190 struct match_recording_data data = {
191 .file = file,
192 .length = strlen(file),
193 .file_with_ext = NULL
194 };
195
197
198 /* Note, string potentially allocated in handle_file_recording */
199 return data.file_with_ext;
200}
201
202/*!
203 * \brief Allocate a recording object.
204 */
206{
207 RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
209 int res;
210
211 recording = ao2_alloc(sizeof(*recording), stored_recording_dtor);
212 if (!recording) {
213 return NULL;
214 }
215
216 res = ast_string_field_init(recording, 255);
217 if (res != 0) {
218 return NULL;
219 }
220
221 ao2_ref(recording, +1);
222 return recording;
223}
224
225static int recording_sort(const void *obj_left, const void *obj_right, int flags)
226{
227 const struct stasis_app_stored_recording *object_left = obj_left;
228 const struct stasis_app_stored_recording *object_right = obj_right;
229 const char *right_key = obj_right;
230 int cmp;
231
232 switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
233 case OBJ_POINTER:
234 right_key = object_right->name;
235 /* Fall through */
236 case OBJ_KEY:
237 cmp = strcmp(object_left->name, right_key);
238 break;
239 case OBJ_PARTIAL_KEY:
240 /*
241 * We could also use a partial key struct containing a length
242 * so strlen() does not get called for every comparison instead.
243 */
244 cmp = strncmp(object_left->name, right_key, strlen(right_key));
245 break;
246 default:
247 /* Sort can only work on something with a full or partial key. */
248 ast_assert(0);
249 cmp = 0;
250 break;
251 }
252 return cmp;
253}
254
255static int handle_scan_file(const char *dir_name, const char *filename, void *obj)
256{
257 struct ao2_container *recordings = obj;
258 struct stasis_app_stored_recording *recording;
259 char *dot, *filepath;
260
261 /* Skip if it is not a recording */
262 if (!is_recording(filename)) {
263 return 0;
264 }
265
266 if (ast_asprintf(&filepath, "%s/%s", dir_name, filename) < 0) {
267 return -1;
268 }
269
270 recording = recording_alloc();
271 if (!recording) {
272 ast_free(filepath);
273 return -1;
274 }
275
276 ast_string_field_set(recording, file_with_ext, filepath);
277 /* Build file and format from full path */
278 ast_string_field_set(recording, file, filepath);
279
280 ast_free(filepath);
281
282 dot = strrchr(recording->file, '.');
283 *dot = '\0';
284 recording->format = dot + 1;
285
286 /* Removed the recording dir from the file for the name. */
287 ast_string_field_set(recording, name,
288 recording->file + strlen(ast_config_AST_RECORDING_DIR) + 1);
289
290 /* Add it to the recordings container */
291 ao2_link(recordings, recording);
292 ao2_ref(recording, -1);
293
294 return 0;
295}
296
298{
300 int res;
301
304 if (!recordings) {
305 return NULL;
306 }
307
310 if (res) {
311 ao2_ref(recordings, -1);
312 return NULL;
313 }
314
315 return recordings;
316}
317
319 const char *name)
320{
321 RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
323 RAII_VAR(char *, dir, NULL, ast_free);
324 RAII_VAR(char *, file, NULL, ast_free);
326 int res;
327 struct stat file_stat;
328 int prefix_len = strlen(ast_config_AST_RECORDING_DIR);
329
330 errno = 0;
331
332 if (!name) {
333 errno = EINVAL;
334 return NULL;
335 }
336
337 recording = recording_alloc();
338 if (!recording) {
339 return NULL;
340 }
341
342 res = split_path(name, &dir, &file);
343 if (res != 0) {
344 return NULL;
345 }
346 ast_string_field_build(recording, file, "%s/%s", dir, file);
347
349 /* It's possible that one or more component of the recording path is
350 * a symbolic link, this would prevent dir from ever matching. */
351 char *real_basedir = realpath(ast_config_AST_RECORDING_DIR, NULL);
352
353 if (!real_basedir || !ast_begins_with(dir, real_basedir)) {
354 /* Attempt to escape the recording directory */
355 ast_log(LOG_WARNING, "Attempt to access invalid recording directory %s\n",
356 dir);
357 ast_std_free(real_basedir);
358 errno = EACCES;
359
360 return NULL;
361 }
362
363 prefix_len = strlen(real_basedir);
364 ast_std_free(real_basedir);
365 }
366
367 /* The actual name of the recording is file with the config dir
368 * prefix removed.
369 */
370 ast_string_field_set(recording, name, recording->file + prefix_len + 1);
371
372 file_with_ext = find_recording(dir, file);
373 if (!file_with_ext) {
374 return NULL;
375 }
376 ast_string_field_set(recording, file_with_ext, file_with_ext);
377 recording->format = strrchr(recording->file_with_ext, '.');
378 if (!recording->format) {
379 return NULL;
380 }
381 ++(recording->format);
382
383 res = stat(file_with_ext, &file_stat);
384 if (res != 0) {
385 return NULL;
386 }
387
388 if (!S_ISREG(file_stat.st_mode)) {
389 /* Let's not play if it's not a regular file */
390 errno = EACCES;
391 return NULL;
392 }
393
394 ao2_ref(recording, +1);
395 return recording;
396}
397
398int stasis_app_stored_recording_copy(struct stasis_app_stored_recording *src_recording, const char *dst,
399 struct stasis_app_stored_recording **dst_recording)
400{
401 RAII_VAR(char *, full_path, NULL, ast_free);
402 char *dst_file = ast_strdupa(dst);
403 char *format;
404 char *last_slash;
405 int res;
406
407 /* Drop the extension if specified, core will do this for us */
408 format = strrchr(dst_file, '.');
409 if (format) {
410 *format = '\0';
411 }
412
413 /* See if any intermediary directories need to be made */
414 last_slash = strrchr(dst_file, '/');
415 if (last_slash) {
416 RAII_VAR(char *, tmp_path, NULL, ast_free);
417
418 *last_slash = '\0';
419 if (ast_asprintf(&tmp_path, "%s/%s", ast_config_AST_RECORDING_DIR, dst_file) < 0) {
420 return -1;
421 }
423 tmp_path, 0777) != 0) {
424 /* errno set by ast_mkdir */
425 return -1;
426 }
427 *last_slash = '/';
428 if (ast_asprintf(&full_path, "%s/%s", ast_config_AST_RECORDING_DIR, dst_file) < 0) {
429 return -1;
430 }
431 } else {
432 /* There is no directory portion */
433 if (ast_asprintf(&full_path, "%s/%s", ast_config_AST_RECORDING_DIR, dst_file) < 0) {
434 return -1;
435 }
436 }
437
438 ast_verb(4, "Copying recording %s to %s (format %s)\n", src_recording->file,
439 full_path, src_recording->format);
440 res = ast_filecopy(src_recording->file, full_path, src_recording->format);
441 if (!res) {
442 *dst_recording = stasis_app_stored_recording_find_by_name(dst_file);
443 }
444
445 return res;
446}
447
449 struct stasis_app_stored_recording *recording)
450{
451 /* Path was validated when the recording object was created */
452 return unlink(recording->file_with_ext);
453}
454
456 struct stasis_app_stored_recording *recording)
457{
458 if (!recording) {
459 return NULL;
460 }
461
462 return ast_json_pack("{ s: s, s: s }",
463 "name", recording->name,
464 "format", recording->format);
465}
Asterisk main include file. File version handling, generic pbx functions.
void ast_std_free(void *ptr)
Definition: astmm.c:1734
#define ast_free(a)
Definition: astmm.h:180
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_asprintf(ret, fmt,...)
A wrapper for asprintf()
Definition: astmm.h:267
#define ast_log
Definition: astobj2.c:42
#define ao2_link(container, obj)
Add an object to a container.
Definition: astobj2.h:1532
#define OBJ_KEY
Definition: astobj2.h:1151
#define OBJ_POINTER
Definition: astobj2.h:1150
@ AO2_ALLOC_OPT_LOCK_NOLOCK
Definition: astobj2.h:367
#define ao2_cleanup(obj)
Definition: astobj2.h:1934
#define ao2_container_alloc_rbtree(ao2_options, container_options, sort_fn, cmp_fn)
Allocate and initialize a red-black tree container.
Definition: astobj2.h:1349
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
#define OBJ_PARTIAL_KEY
Definition: astobj2.h:1152
#define ao2_alloc(data_size, destructor_fn)
Definition: astobj2.h:409
@ AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE
Replace objects with duplicate keys in container.
Definition: astobj2.h:1211
#define ast_file_read_dir(dir_name, on_file, obj)
Iterate over each file in a given directory.
Definition: file.h:203
int ast_file_read_dirs(const char *dir_name, ast_file_on_file on_file, void *obj, int max_depth)
Recursively iterate through files and directories up to max_depth.
Definition: file.c:1274
struct ast_format * ast_get_format_for_file_ext(const char *file_ext)
Get the ast_format associated with the given file extension.
Definition: file.c:2006
int ast_filecopy(const char *oldname, const char *newname, const char *fmt)
Copies a file.
Definition: file.c:1151
static const char name[]
Definition: format_mp3.c:68
const char * ext
Definition: http.c:150
#define ast_debug(level,...)
Log a DEBUG message.
#define ast_verb(level,...)
#define LOG_WARNING
struct ast_json * ast_json_pack(char const *format,...)
Helper for creating complex JSON values.
Definition: json.c:612
int errno
Asterisk file paths, configured in asterisk.conf.
const char * ast_config_AST_RECORDING_DIR
Definition: options.c:156
static struct stasis_rest_handlers recordings
REST handler for /api-docs/recordings.json.
#define NULL
Definition: resample.c:96
Stasis Application Recording API. See StasisApplication API" for detailed documentation.
static void stored_recording_dtor(void *obj)
Definition: stored.c:46
const char * stasis_app_stored_recording_get_filename(struct stasis_app_stored_recording *recording)
Returns the full filename, with extension, for this recording.
Definition: stored.c:62
const char * stasis_app_stored_recording_get_file(struct stasis_app_stored_recording *recording)
Returns the filename for this recording, for use with streamfile.
Definition: stored.c:53
static int is_recording(const char *filename)
Definition: stored.c:137
static int recording_sort(const void *obj_left, const void *obj_right, int flags)
Definition: stored.c:225
struct ao2_container * stasis_app_stored_recording_find_all(void)
Find all stored recordings on disk.
Definition: stored.c:297
static int handle_scan_file(const char *dir_name, const char *filename, void *obj)
Definition: stored.c:255
struct ast_json * stasis_app_stored_recording_to_json(struct stasis_app_stored_recording *recording)
Convert stored recording info to JSON.
Definition: stored.c:455
struct stasis_app_stored_recording * stasis_app_stored_recording_find_by_name(const char *name)
Creates a stored recording object, with the given name.
Definition: stored.c:318
static struct stasis_app_stored_recording * recording_alloc(void)
Allocate a recording object.
Definition: stored.c:205
static char * find_recording(const char *dir_name, const char *file)
Finds a recording in the given directory.
Definition: stored.c:188
int stasis_app_stored_recording_delete(struct stasis_app_stored_recording *recording)
Delete a recording from disk.
Definition: stored.c:448
const char * stasis_app_stored_recording_get_extension(struct stasis_app_stored_recording *recording)
Returns the extension for this recording.
Definition: stored.c:71
static int split_path(const char *path, char **dir, char **file)
Split a path into directory and file, resolving canonical directory.
Definition: stored.c:92
static int handle_find_recording(const char *dir_name, const char *filename, void *obj)
Definition: stored.c:158
int stasis_app_stored_recording_copy(struct stasis_app_stored_recording *src_recording, const char *dst, struct stasis_app_stored_recording **dst_recording)
Copy a recording.
Definition: stored.c:398
#define AST_DECLARE_STRING_FIELDS(field_list)
Declare the fields needed in a structure.
Definition: stringfields.h:341
#define AST_STRING_FIELD(name)
Declare a string field.
Definition: stringfields.h:303
#define ast_string_field_set(x, field, data)
Set a field to a simple string value.
Definition: stringfields.h:521
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
Definition: stringfields.h:359
#define ast_string_field_build(x, field, fmt, args...)
Set a field to a complex (built) value.
Definition: stringfields.h:555
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
Definition: stringfields.h:374
static int force_inline attribute_pure ast_begins_with(const char *str, const char *prefix)
Checks whether a string begins with another.
Definition: strings.h:97
Generic container type.
Abstract JSON element (object, array, string, int, ...).
char * file_with_ext
Definition: stored.c:134
const char * file
Definition: stored.c:132
const char * format
Definition: stored.c:43
const ast_string_field file_with_ext
Definition: stored.c:41
const ast_string_field file
Definition: stored.c:41
const ast_string_field name
Definition: stored.c:41
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941
int ast_safe_mkdir(const char *base_path, const char *path, int mode)
Recursively create directory path, but only if it resolves within the given base_path.
Definition: utils.c:2584
#define ast_assert(a)
Definition: utils.h:739