| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Asterisk -- An open source telephony toolkit. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (C) 2013, Digium, Inc. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * David M. Lee, II <dlee@digium.com> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * See http://www.asterisk.org for more information about
 | 
					
						
							|  |  |  |  * the Asterisk project. Please do not directly contact | 
					
						
							|  |  |  |  * any of the maintainers of this project for assistance; | 
					
						
							|  |  |  |  * the project provides a web site, mailing lists and IRC | 
					
						
							|  |  |  |  * channels for your use. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software, distributed under the terms of | 
					
						
							|  |  |  |  * the GNU General Public License Version 2. See the LICENSE file | 
					
						
							|  |  |  |  * at the top of the source tree. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*! \file
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \brief Stored file operations for Stasis | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \author David M. Lee, II <dlee@digium.com> | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "asterisk.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "asterisk/astobj2.h"
 | 
					
						
							|  |  |  | #include "asterisk/paths.h"
 | 
					
						
							|  |  |  | #include "asterisk/stasis_app_recording.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <sys/stat.h>
 | 
					
						
							|  |  |  | #include <sys/types.h>
 | 
					
						
							|  |  |  | #include <unistd.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct stasis_app_stored_recording { | 
					
						
							|  |  |  | 	AST_DECLARE_STRING_FIELDS( | 
					
						
							|  |  |  | 		AST_STRING_FIELD(name);	/*!< Recording's name */ | 
					
						
							|  |  |  | 		AST_STRING_FIELD(file);	/*!< Absolute filename, without extension; for use with streamfile */ | 
					
						
							|  |  |  | 		AST_STRING_FIELD(file_with_ext);	/*!< Absolute filename, with extension; for use with everything else */ | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const char *format;	/*!< Format name (i.e. filename extension) */ | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void stored_recording_dtor(void *obj) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct stasis_app_stored_recording *recording = obj; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_string_field_free_memory(recording); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const char *stasis_app_stored_recording_get_file( | 
					
						
							|  |  |  | 	struct stasis_app_stored_recording *recording) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (!recording) { | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return recording->file; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-18 06:19:58 -05:00
										 |  |  | const char *stasis_app_stored_recording_get_filename( | 
					
						
							|  |  |  | 	struct stasis_app_stored_recording *recording) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (!recording) { | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return recording->file_with_ext; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const char *stasis_app_stored_recording_get_extension( | 
					
						
							|  |  |  | 	struct stasis_app_stored_recording *recording) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (!recording) { | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return recording->format; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | /*!
 | 
					
						
							|  |  |  |  * \brief Split a path into directory and file, resolving canonical directory. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * The path is resolved relative to the recording directory. Both dir and file | 
					
						
							|  |  |  |  * are allocated strings, which you must ast_free(). | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \param path Path to split. | 
					
						
							|  |  |  |  * \param[out] dir Output parameter for directory portion. | 
					
						
							| 
									
										
										
										
											2021-11-16 17:26:23 +01:00
										 |  |  |  * \param[out] file Output parameter for the file portion. | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  |  * \return 0 on success. | 
					
						
							|  |  |  |  * \return Non-zero on error. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static int split_path(const char *path, char **dir, char **file) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	RAII_VAR(char *, relative_dir, NULL, ast_free); | 
					
						
							|  |  |  | 	RAII_VAR(char *, absolute_dir, NULL, ast_free); | 
					
						
							| 
									
										
										
										
											2013-10-02 17:12:49 +00:00
										 |  |  | 	RAII_VAR(char *, real_dir, NULL, ast_std_free); | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | 	char *last_slash; | 
					
						
							|  |  |  | 	const char *file_portion; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	relative_dir = ast_strdup(path); | 
					
						
							|  |  |  | 	if (!relative_dir) { | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	last_slash = strrchr(relative_dir, '/'); | 
					
						
							|  |  |  | 	if (last_slash) { | 
					
						
							|  |  |  | 		*last_slash = '\0'; | 
					
						
							|  |  |  | 		file_portion = last_slash + 1; | 
					
						
							|  |  |  | 		ast_asprintf(&absolute_dir, "%s/%s", | 
					
						
							|  |  |  | 			ast_config_AST_RECORDING_DIR, relative_dir); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		/* There is no directory portion */ | 
					
						
							|  |  |  | 		file_portion = path; | 
					
						
							|  |  |  | 		*relative_dir = '\0'; | 
					
						
							|  |  |  | 		absolute_dir = ast_strdup(ast_config_AST_RECORDING_DIR); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (!absolute_dir) { | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	real_dir = realpath(absolute_dir, NULL); | 
					
						
							|  |  |  | 	if (!real_dir) { | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	*dir = ast_strdup(real_dir); /* Dupe so we can ast_free() */ | 
					
						
							|  |  |  | 	*file = ast_strdup(file_portion); | 
					
						
							| 
									
										
										
										
											2018-02-19 19:55:50 -06:00
										 |  |  | 	return (*dir && *file) ? 0 : -1; | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | struct match_recording_data { | 
					
						
							|  |  |  | 	const char *file; | 
					
						
							| 
									
										
										
										
											2022-03-10 12:07:40 -05:00
										 |  |  | 	size_t length; | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | 	char *file_with_ext; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int is_recording(const char *filename) | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | 	const char *ext = strrchr(filename, '.'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!ext) { | 
					
						
							|  |  |  | 		/* No file extension; not us */ | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	++ext; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!ast_get_format_for_file_ext(ext)) { | 
					
						
							|  |  |  | 		ast_debug(5, "Recording %s: unrecognized format %s\n", | 
					
						
							|  |  |  | 			filename, ext); | 
					
						
							|  |  |  | 		/* Keep looking */ | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Return the index to the .ext */ | 
					
						
							|  |  |  | 	return ext - filename - 1; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int handle_find_recording(const char *dir_name, const char *filename, void *obj) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct match_recording_data *data = obj; | 
					
						
							|  |  |  | 	int num; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* If not a recording or the names do not match the keep searching */ | 
					
						
							| 
									
										
										
										
											2022-03-10 12:07:40 -05:00
										 |  |  | 	if (!(num = is_recording(filename)) | 
					
						
							|  |  |  | 	   || data->length != num | 
					
						
							|  |  |  | 	   || strncmp(data->file, filename, num)) { | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | 		return 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-30 17:07:56 -04:00
										 |  |  | 	if (ast_asprintf(&data->file_with_ext, "%s/%s", dir_name, filename) < 0) { | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | 		return -1; | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return 1; | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \brief Finds a recording in the given directory. | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2021-10-30 21:04:36 -04:00
										 |  |  |  * This function searches for a file with the given file name, with a registered | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  |  * format that matches its extension. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \param dir_name Directory to search (absolute path). | 
					
						
							|  |  |  |  * \param file File name, without extension. | 
					
						
							|  |  |  |  * \return Absolute path of the recording file. | 
					
						
							| 
									
										
										
										
											2021-12-04 09:36:37 +01:00
										 |  |  |  * \retval NULL if recording is not found. | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  |  */ | 
					
						
							|  |  |  | static char *find_recording(const char *dir_name, const char *file) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | 	struct match_recording_data data = { | 
					
						
							|  |  |  | 		.file = file, | 
					
						
							| 
									
										
										
										
											2022-03-10 12:07:40 -05:00
										 |  |  | 		.length = strlen(file), | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | 		.file_with_ext = NULL | 
					
						
							|  |  |  | 	}; | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | 	ast_file_read_dir(dir_name, handle_find_recording, &data); | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | 	/* Note, string potentially allocated in handle_file_recording */ | 
					
						
							|  |  |  | 	return data.file_with_ext; | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \brief Allocate a recording object. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static struct stasis_app_stored_recording *recording_alloc(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	RAII_VAR(struct stasis_app_stored_recording *, recording, NULL, | 
					
						
							|  |  |  | 		ao2_cleanup); | 
					
						
							|  |  |  | 	int res; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	recording = ao2_alloc(sizeof(*recording), stored_recording_dtor); | 
					
						
							|  |  |  | 	if (!recording) { | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	res = ast_string_field_init(recording, 255); | 
					
						
							|  |  |  | 	if (res != 0) { | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ao2_ref(recording, +1); | 
					
						
							|  |  |  | 	return recording; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int recording_sort(const void *obj_left, const void *obj_right, int flags) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	const struct stasis_app_stored_recording *object_left = obj_left; | 
					
						
							|  |  |  | 	const struct stasis_app_stored_recording *object_right = obj_right; | 
					
						
							|  |  |  | 	const char *right_key = obj_right; | 
					
						
							|  |  |  | 	int cmp; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) { | 
					
						
							|  |  |  | 	case OBJ_POINTER: | 
					
						
							|  |  |  | 		right_key = object_right->name; | 
					
						
							|  |  |  | 		/* Fall through */ | 
					
						
							|  |  |  | 	case OBJ_KEY: | 
					
						
							|  |  |  | 		cmp = strcmp(object_left->name, right_key); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case OBJ_PARTIAL_KEY: | 
					
						
							|  |  |  | 		/*
 | 
					
						
							|  |  |  | 		 * We could also use a partial key struct containing a length | 
					
						
							|  |  |  | 		 * so strlen() does not get called for every comparison instead. | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		cmp = strncmp(object_left->name, right_key, strlen(right_key)); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		/* Sort can only work on something with a full or partial key. */ | 
					
						
							|  |  |  | 		ast_assert(0); | 
					
						
							|  |  |  | 		cmp = 0; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return cmp; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | static int handle_scan_file(const char *dir_name, const char *filename, void *obj) | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | 	struct ao2_container *recordings = obj; | 
					
						
							|  |  |  | 	struct stasis_app_stored_recording *recording; | 
					
						
							|  |  |  | 	char *dot, *filepath; | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | 	/* Skip if it is not a recording */ | 
					
						
							|  |  |  | 	if (!is_recording(filename)) { | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | 		return 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-30 17:07:56 -04:00
										 |  |  | 	if (ast_asprintf(&filepath, "%s/%s", dir_name, filename) < 0) { | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | 		return -1; | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	recording = recording_alloc(); | 
					
						
							|  |  |  | 	if (!recording) { | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | 		ast_free(filepath); | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | 	ast_string_field_set(recording, file_with_ext, filepath); | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | 	/* Build file and format from full path */ | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | 	ast_string_field_set(recording, file, filepath); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_free(filepath); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | 	dot = strrchr(recording->file, '.'); | 
					
						
							|  |  |  | 	*dot = '\0'; | 
					
						
							|  |  |  | 	recording->format = dot + 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Removed the recording dir from the file for the name. */ | 
					
						
							|  |  |  | 	ast_string_field_set(recording, name, | 
					
						
							|  |  |  | 		recording->file + strlen(ast_config_AST_RECORDING_DIR) + 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Add it to the recordings container */ | 
					
						
							|  |  |  | 	ao2_link(recordings, recording); | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | 	ao2_ref(recording, -1); | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct ao2_container *stasis_app_stored_recording_find_all(void) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | 	struct ao2_container *recordings; | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | 	int res; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	recordings = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK, | 
					
						
							|  |  |  | 		AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, recording_sort, NULL); | 
					
						
							|  |  |  | 	if (!recordings) { | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-28 15:11:35 -05:00
										 |  |  | 	res = ast_file_read_dirs(ast_config_AST_RECORDING_DIR, | 
					
						
							|  |  |  | 				 handle_scan_file, recordings, -1); | 
					
						
							|  |  |  | 	if (res) { | 
					
						
							|  |  |  | 		ao2_ref(recordings, -1); | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return recordings; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct stasis_app_stored_recording *stasis_app_stored_recording_find_by_name( | 
					
						
							|  |  |  | 	const char *name) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	RAII_VAR(struct stasis_app_stored_recording *, recording, NULL, | 
					
						
							|  |  |  | 		ao2_cleanup); | 
					
						
							|  |  |  | 	RAII_VAR(char *, dir, NULL, ast_free); | 
					
						
							|  |  |  | 	RAII_VAR(char *, file, NULL, ast_free); | 
					
						
							|  |  |  | 	RAII_VAR(char *, file_with_ext, NULL, ast_free); | 
					
						
							|  |  |  | 	int res; | 
					
						
							|  |  |  | 	struct stat file_stat; | 
					
						
							| 
									
										
										
										
											2018-01-12 19:37:43 -05:00
										 |  |  | 	int prefix_len = strlen(ast_config_AST_RECORDING_DIR); | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	errno = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!name) { | 
					
						
							|  |  |  | 		errno = EINVAL; | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	recording = recording_alloc(); | 
					
						
							|  |  |  | 	if (!recording) { | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	res = split_path(name, &dir, &file); | 
					
						
							|  |  |  | 	if (res != 0) { | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ast_string_field_build(recording, file, "%s/%s", dir, file); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!ast_begins_with(dir, ast_config_AST_RECORDING_DIR)) { | 
					
						
							| 
									
										
										
										
											2018-01-12 19:37:43 -05:00
										 |  |  | 		/* It's possible that one or more component of the recording path is
 | 
					
						
							|  |  |  | 		 * a symbolic link, this would prevent dir from ever matching. */ | 
					
						
							|  |  |  | 		char *real_basedir = realpath(ast_config_AST_RECORDING_DIR, NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (!real_basedir || !ast_begins_with(dir, real_basedir)) { | 
					
						
							|  |  |  | 			/* Attempt to escape the recording directory */ | 
					
						
							|  |  |  | 			ast_log(LOG_WARNING, "Attempt to access invalid recording directory %s\n", | 
					
						
							|  |  |  | 				dir); | 
					
						
							|  |  |  | 			ast_std_free(real_basedir); | 
					
						
							|  |  |  | 			errno = EACCES; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return NULL; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		prefix_len = strlen(real_basedir); | 
					
						
							|  |  |  | 		ast_std_free(real_basedir); | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* The actual name of the recording is file with the config dir
 | 
					
						
							|  |  |  | 	 * prefix removed. | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2018-01-12 19:37:43 -05:00
										 |  |  | 	ast_string_field_set(recording, name, recording->file + prefix_len + 1); | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	file_with_ext = find_recording(dir, file); | 
					
						
							|  |  |  | 	if (!file_with_ext) { | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ast_string_field_set(recording, file_with_ext, file_with_ext); | 
					
						
							|  |  |  | 	recording->format = strrchr(recording->file_with_ext, '.'); | 
					
						
							|  |  |  | 	if (!recording->format) { | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	++(recording->format); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	res = stat(file_with_ext, &file_stat); | 
					
						
							|  |  |  | 	if (res != 0) { | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!S_ISREG(file_stat.st_mode)) { | 
					
						
							|  |  |  | 		/* Let's not play if it's not a regular file */ | 
					
						
							|  |  |  | 		errno = EACCES; | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ao2_ref(recording, +1); | 
					
						
							|  |  |  | 	return recording; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-07-18 21:48:46 +00:00
										 |  |  | int stasis_app_stored_recording_copy(struct stasis_app_stored_recording *src_recording, const char *dst, | 
					
						
							|  |  |  | 	struct stasis_app_stored_recording **dst_recording) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	RAII_VAR(char *, full_path, NULL, ast_free); | 
					
						
							|  |  |  | 	char *dst_file = ast_strdupa(dst); | 
					
						
							|  |  |  | 	char *format; | 
					
						
							|  |  |  | 	char *last_slash; | 
					
						
							|  |  |  | 	int res; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Drop the extension if specified, core will do this for us */ | 
					
						
							|  |  |  | 	format = strrchr(dst_file, '.'); | 
					
						
							|  |  |  | 	if (format) { | 
					
						
							| 
									
										
										
										
											2015-04-07 02:03:48 +00:00
										 |  |  | 		*format = '\0'; | 
					
						
							| 
									
										
										
										
											2014-07-18 21:48:46 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* See if any intermediary directories need to be made */ | 
					
						
							|  |  |  | 	last_slash = strrchr(dst_file, '/'); | 
					
						
							|  |  |  | 	if (last_slash) { | 
					
						
							|  |  |  | 		RAII_VAR(char *, tmp_path, NULL, ast_free); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		*last_slash = '\0'; | 
					
						
							|  |  |  | 		if (ast_asprintf(&tmp_path, "%s/%s", ast_config_AST_RECORDING_DIR, dst_file) < 0) { | 
					
						
							|  |  |  | 			return -1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (ast_safe_mkdir(ast_config_AST_RECORDING_DIR, | 
					
						
							|  |  |  | 				tmp_path, 0777) != 0) { | 
					
						
							|  |  |  | 			/* errno set by ast_mkdir */ | 
					
						
							|  |  |  | 			return -1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		*last_slash = '/'; | 
					
						
							|  |  |  | 		if (ast_asprintf(&full_path, "%s/%s", ast_config_AST_RECORDING_DIR, dst_file) < 0) { | 
					
						
							|  |  |  | 			return -1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		/* There is no directory portion */ | 
					
						
							|  |  |  | 		if (ast_asprintf(&full_path, "%s/%s", ast_config_AST_RECORDING_DIR, dst_file) < 0) { | 
					
						
							|  |  |  | 			return -1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_verb(4, "Copying recording %s to %s (format %s)\n", src_recording->file, | 
					
						
							|  |  |  | 		full_path, src_recording->format); | 
					
						
							|  |  |  | 	res = ast_filecopy(src_recording->file, full_path, src_recording->format); | 
					
						
							|  |  |  | 	if (!res) { | 
					
						
							|  |  |  | 		*dst_recording = stasis_app_stored_recording_find_by_name(dst_file); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return res; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-30 13:28:50 +00:00
										 |  |  | int stasis_app_stored_recording_delete( | 
					
						
							|  |  |  | 	struct stasis_app_stored_recording *recording) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	/* Path was validated when the recording object was created */ | 
					
						
							|  |  |  | 	return unlink(recording->file_with_ext); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct ast_json *stasis_app_stored_recording_to_json( | 
					
						
							|  |  |  | 	struct stasis_app_stored_recording *recording) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (!recording) { | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ast_json_pack("{ s: s, s: s }", | 
					
						
							|  |  |  | 		"name", recording->name, | 
					
						
							|  |  |  | 		"format", recording->format); | 
					
						
							|  |  |  | } |