stored.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  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. /*! \file
  19. *
  20. * \brief Stored file operations for Stasis
  21. *
  22. * \author David M. Lee, II <dlee@digium.com>
  23. */
  24. #include "asterisk.h"
  25. ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  26. #include "asterisk/astobj2.h"
  27. #include "asterisk/paths.h"
  28. #include "asterisk/stasis_app_recording.h"
  29. #include <sys/stat.h>
  30. #include <sys/types.h>
  31. #include <unistd.h>
  32. struct stasis_app_stored_recording {
  33. AST_DECLARE_STRING_FIELDS(
  34. AST_STRING_FIELD(name); /*!< Recording's name */
  35. AST_STRING_FIELD(file); /*!< Absolute filename, without extension; for use with streamfile */
  36. AST_STRING_FIELD(file_with_ext); /*!< Absolute filename, with extension; for use with everything else */
  37. );
  38. const char *format; /*!< Format name (i.e. filename extension) */
  39. };
  40. static void stored_recording_dtor(void *obj)
  41. {
  42. struct stasis_app_stored_recording *recording = obj;
  43. ast_string_field_free_memory(recording);
  44. }
  45. const char *stasis_app_stored_recording_get_file(
  46. struct stasis_app_stored_recording *recording)
  47. {
  48. if (!recording) {
  49. return NULL;
  50. }
  51. return recording->file;
  52. }
  53. /*!
  54. * \brief Split a path into directory and file, resolving canonical directory.
  55. *
  56. * The path is resolved relative to the recording directory. Both dir and file
  57. * are allocated strings, which you must ast_free().
  58. *
  59. * \param path Path to split.
  60. * \param[out] dir Output parameter for directory portion.
  61. * \param[out] fail Output parameter for the file portion.
  62. * \return 0 on success.
  63. * \return Non-zero on error.
  64. */
  65. static int split_path(const char *path, char **dir, char **file)
  66. {
  67. RAII_VAR(char *, relative_dir, NULL, ast_free);
  68. RAII_VAR(char *, absolute_dir, NULL, ast_free);
  69. RAII_VAR(char *, real_dir, NULL, ast_std_free);
  70. char *last_slash;
  71. const char *file_portion;
  72. relative_dir = ast_strdup(path);
  73. if (!relative_dir) {
  74. return -1;
  75. }
  76. last_slash = strrchr(relative_dir, '/');
  77. if (last_slash) {
  78. *last_slash = '\0';
  79. file_portion = last_slash + 1;
  80. ast_asprintf(&absolute_dir, "%s/%s",
  81. ast_config_AST_RECORDING_DIR, relative_dir);
  82. } else {
  83. /* There is no directory portion */
  84. file_portion = path;
  85. *relative_dir = '\0';
  86. absolute_dir = ast_strdup(ast_config_AST_RECORDING_DIR);
  87. }
  88. if (!absolute_dir) {
  89. return -1;
  90. }
  91. real_dir = realpath(absolute_dir, NULL);
  92. if (!real_dir) {
  93. return -1;
  94. }
  95. #if defined(__AST_DEBUG_MALLOC)
  96. *dir = ast_strdup(real_dir); /* Dupe so we can ast_free() */
  97. #else
  98. /*
  99. * ast_std_free() and ast_free() are the same thing at this time
  100. * so we don't need to dupe.
  101. */
  102. *dir = real_dir;
  103. real_dir = NULL;
  104. #endif /* defined(__AST_DEBUG_MALLOC) */
  105. *file = ast_strdup(file_portion);
  106. return 0;
  107. }
  108. struct match_recording_data {
  109. const char *file;
  110. char *file_with_ext;
  111. };
  112. static int is_recording(const char *filename)
  113. {
  114. const char *ext = strrchr(filename, '.');
  115. if (!ext) {
  116. /* No file extension; not us */
  117. return 0;
  118. }
  119. ++ext;
  120. if (!ast_get_format_for_file_ext(ext)) {
  121. ast_debug(5, "Recording %s: unrecognized format %s\n",
  122. filename, ext);
  123. /* Keep looking */
  124. return 0;
  125. }
  126. /* Return the index to the .ext */
  127. return ext - filename - 1;
  128. }
  129. static int handle_find_recording(const char *dir_name, const char *filename, void *obj)
  130. {
  131. struct match_recording_data *data = obj;
  132. int num;
  133. /* If not a recording or the names do not match the keep searching */
  134. if (!(num = is_recording(filename)) || strncmp(data->file, filename, num)) {
  135. return 0;
  136. }
  137. if (ast_asprintf(&data->file_with_ext, "%s/%s", dir_name, filename) < 0) {
  138. return -1;
  139. }
  140. return 1;
  141. }
  142. /*!
  143. * \brief Finds a recording in the given directory.
  144. *
  145. * This function searchs for a file with the given file name, with a registered
  146. * format that matches its extension.
  147. *
  148. * \param dir_name Directory to search (absolute path).
  149. * \param file File name, without extension.
  150. * \return Absolute path of the recording file.
  151. * \return \c NULL if recording is not found.
  152. */
  153. static char *find_recording(const char *dir_name, const char *file)
  154. {
  155. struct match_recording_data data = {
  156. .file = file,
  157. .file_with_ext = NULL
  158. };
  159. ast_file_read_dir(dir_name, handle_find_recording, &data);
  160. /* Note, string potentially allocated in handle_file_recording */
  161. return data.file_with_ext;
  162. }
  163. /*!
  164. * \brief Allocate a recording object.
  165. */
  166. static struct stasis_app_stored_recording *recording_alloc(void)
  167. {
  168. RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
  169. ao2_cleanup);
  170. int res;
  171. recording = ao2_alloc(sizeof(*recording), stored_recording_dtor);
  172. if (!recording) {
  173. return NULL;
  174. }
  175. res = ast_string_field_init(recording, 255);
  176. if (res != 0) {
  177. return NULL;
  178. }
  179. ao2_ref(recording, +1);
  180. return recording;
  181. }
  182. static int recording_sort(const void *obj_left, const void *obj_right, int flags)
  183. {
  184. const struct stasis_app_stored_recording *object_left = obj_left;
  185. const struct stasis_app_stored_recording *object_right = obj_right;
  186. const char *right_key = obj_right;
  187. int cmp;
  188. switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
  189. case OBJ_POINTER:
  190. right_key = object_right->name;
  191. /* Fall through */
  192. case OBJ_KEY:
  193. cmp = strcmp(object_left->name, right_key);
  194. break;
  195. case OBJ_PARTIAL_KEY:
  196. /*
  197. * We could also use a partial key struct containing a length
  198. * so strlen() does not get called for every comparison instead.
  199. */
  200. cmp = strncmp(object_left->name, right_key, strlen(right_key));
  201. break;
  202. default:
  203. /* Sort can only work on something with a full or partial key. */
  204. ast_assert(0);
  205. cmp = 0;
  206. break;
  207. }
  208. return cmp;
  209. }
  210. static int handle_scan_file(const char *dir_name, const char *filename, void *obj)
  211. {
  212. struct ao2_container *recordings = obj;
  213. struct stasis_app_stored_recording *recording;
  214. char *dot, *filepath;
  215. /* Skip if it is not a recording */
  216. if (!is_recording(filename)) {
  217. return 0;
  218. }
  219. if (ast_asprintf(&filepath, "%s/%s", dir_name, filename) < 0) {
  220. return -1;
  221. }
  222. recording = recording_alloc();
  223. if (!recording) {
  224. ast_free(filepath);
  225. return -1;
  226. }
  227. ast_string_field_set(recording, file_with_ext, filepath);
  228. /* Build file and format from full path */
  229. ast_string_field_set(recording, file, filepath);
  230. ast_free(filepath);
  231. dot = strrchr(recording->file, '.');
  232. *dot = '\0';
  233. recording->format = dot + 1;
  234. /* Removed the recording dir from the file for the name. */
  235. ast_string_field_set(recording, name,
  236. recording->file + strlen(ast_config_AST_RECORDING_DIR) + 1);
  237. /* Add it to the recordings container */
  238. ao2_link(recordings, recording);
  239. ao2_ref(recording, -1);
  240. return 0;
  241. }
  242. struct ao2_container *stasis_app_stored_recording_find_all(void)
  243. {
  244. struct ao2_container *recordings;
  245. int res;
  246. recordings = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK,
  247. AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, recording_sort, NULL);
  248. if (!recordings) {
  249. return NULL;
  250. }
  251. res = ast_file_read_dirs(ast_config_AST_RECORDING_DIR,
  252. handle_scan_file, recordings, -1);
  253. if (res) {
  254. ao2_ref(recordings, -1);
  255. return NULL;
  256. }
  257. return recordings;
  258. }
  259. struct stasis_app_stored_recording *stasis_app_stored_recording_find_by_name(
  260. const char *name)
  261. {
  262. RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
  263. ao2_cleanup);
  264. RAII_VAR(char *, dir, NULL, ast_free);
  265. RAII_VAR(char *, file, NULL, ast_free);
  266. RAII_VAR(char *, file_with_ext, NULL, ast_free);
  267. int res;
  268. struct stat file_stat;
  269. int prefix_len = strlen(ast_config_AST_RECORDING_DIR);
  270. errno = 0;
  271. if (!name) {
  272. errno = EINVAL;
  273. return NULL;
  274. }
  275. recording = recording_alloc();
  276. if (!recording) {
  277. return NULL;
  278. }
  279. res = split_path(name, &dir, &file);
  280. if (res != 0) {
  281. return NULL;
  282. }
  283. ast_string_field_build(recording, file, "%s/%s", dir, file);
  284. if (!ast_begins_with(dir, ast_config_AST_RECORDING_DIR)) {
  285. /* It's possible that one or more component of the recording path is
  286. * a symbolic link, this would prevent dir from ever matching. */
  287. char *real_basedir = realpath(ast_config_AST_RECORDING_DIR, NULL);
  288. if (!real_basedir || !ast_begins_with(dir, real_basedir)) {
  289. /* Attempt to escape the recording directory */
  290. ast_log(LOG_WARNING, "Attempt to access invalid recording directory %s\n",
  291. dir);
  292. ast_std_free(real_basedir);
  293. errno = EACCES;
  294. return NULL;
  295. }
  296. prefix_len = strlen(real_basedir);
  297. ast_std_free(real_basedir);
  298. }
  299. /* The actual name of the recording is file with the config dir
  300. * prefix removed.
  301. */
  302. ast_string_field_set(recording, name, recording->file + prefix_len + 1);
  303. file_with_ext = find_recording(dir, file);
  304. if (!file_with_ext) {
  305. return NULL;
  306. }
  307. ast_string_field_set(recording, file_with_ext, file_with_ext);
  308. recording->format = strrchr(recording->file_with_ext, '.');
  309. if (!recording->format) {
  310. return NULL;
  311. }
  312. ++(recording->format);
  313. res = stat(file_with_ext, &file_stat);
  314. if (res != 0) {
  315. return NULL;
  316. }
  317. if (!S_ISREG(file_stat.st_mode)) {
  318. /* Let's not play if it's not a regular file */
  319. errno = EACCES;
  320. return NULL;
  321. }
  322. ao2_ref(recording, +1);
  323. return recording;
  324. }
  325. int stasis_app_stored_recording_copy(struct stasis_app_stored_recording *src_recording, const char *dst,
  326. struct stasis_app_stored_recording **dst_recording)
  327. {
  328. RAII_VAR(char *, full_path, NULL, ast_free);
  329. char *dst_file = ast_strdupa(dst);
  330. char *format;
  331. char *last_slash;
  332. int res;
  333. /* Drop the extension if specified, core will do this for us */
  334. format = strrchr(dst_file, '.');
  335. if (format) {
  336. *format = '\0';
  337. }
  338. /* See if any intermediary directories need to be made */
  339. last_slash = strrchr(dst_file, '/');
  340. if (last_slash) {
  341. RAII_VAR(char *, tmp_path, NULL, ast_free);
  342. *last_slash = '\0';
  343. if (ast_asprintf(&tmp_path, "%s/%s", ast_config_AST_RECORDING_DIR, dst_file) < 0) {
  344. return -1;
  345. }
  346. if (ast_safe_mkdir(ast_config_AST_RECORDING_DIR,
  347. tmp_path, 0777) != 0) {
  348. /* errno set by ast_mkdir */
  349. return -1;
  350. }
  351. *last_slash = '/';
  352. if (ast_asprintf(&full_path, "%s/%s", ast_config_AST_RECORDING_DIR, dst_file) < 0) {
  353. return -1;
  354. }
  355. } else {
  356. /* There is no directory portion */
  357. if (ast_asprintf(&full_path, "%s/%s", ast_config_AST_RECORDING_DIR, dst_file) < 0) {
  358. return -1;
  359. }
  360. }
  361. ast_verb(4, "Copying recording %s to %s (format %s)\n", src_recording->file,
  362. full_path, src_recording->format);
  363. res = ast_filecopy(src_recording->file, full_path, src_recording->format);
  364. if (!res) {
  365. *dst_recording = stasis_app_stored_recording_find_by_name(dst_file);
  366. }
  367. return res;
  368. }
  369. int stasis_app_stored_recording_delete(
  370. struct stasis_app_stored_recording *recording)
  371. {
  372. /* Path was validated when the recording object was created */
  373. return unlink(recording->file_with_ext);
  374. }
  375. struct ast_json *stasis_app_stored_recording_to_json(
  376. struct stasis_app_stored_recording *recording)
  377. {
  378. if (!recording) {
  379. return NULL;
  380. }
  381. return ast_json_pack("{ s: s, s: s }",
  382. "name", recording->name,
  383. "format", recording->format);
  384. }