Asterisk - The Open Source Telephony Project  21.4.1
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 
46 static 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  */
92 static 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';
115  absolute_dir = ast_strdup(ast_config_AST_RECORDING_DIR);
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 }
131 struct match_recording_data {
132  const char *file;
133  size_t length;
134  char *file_with_ext;
135 };
136 
137 static 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 
147  if (!ast_get_format_for_file_ext(ext)) {
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 
158 static 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  */
188 static 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 
196  ast_file_read_dir(dir_name, handle_find_recording, &data);
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  */
205 static struct stasis_app_stored_recording *recording_alloc(void)
206 {
207  RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
208  ao2_cleanup);
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 
225 static 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 
255 static 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 {
299  struct ao2_container *recordings;
300  int res;
301 
304  if (!recordings) {
305  return NULL;
306  }
307 
308  res = ast_file_read_dirs(ast_config_AST_RECORDING_DIR,
309  handle_scan_file, recordings, -1);
310  if (res) {
311  ao2_ref(recordings, -1);
312  return NULL;
313  }
315  return recordings;
316 }
317 
319  const char *name)
320 {
321  RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
322  ao2_cleanup);
323  RAII_VAR(char *, dir, NULL, ast_free);
324  RAII_VAR(char *, file, NULL, ast_free);
325  RAII_VAR(char *, file_with_ext, 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 
348  if (!ast_begins_with(dir, ast_config_AST_RECORDING_DIR)) {
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  }
394  ao2_ref(recording, +1);
395  return recording;
396 }
397 
398 int 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  }
422  if (ast_safe_mkdir(ast_config_AST_RECORDING_DIR,
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 }
int ast_filecopy(const char *oldname, const char *newname, const char *fmt)
Copies a file.
Definition: file.c:1151
Stasis Application Recording API. See StasisApplication API" for detailed documentation.
Asterisk main include file. File version handling, generic pbx functions.
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
struct ast_json * ast_json_pack(char const *format,...)
Helper for creating complex JSON values.
Definition: json.c:612
int stasis_app_stored_recording_delete(struct stasis_app_stored_recording *recording)
Delete a recording from disk.
Definition: stored.c:443
static char * find_recording(const char *dir_name, const char *file)
Finds a recording in the given directory.
Definition: stored.c:187
const ast_string_field file_with_ext
Definition: stored.c:41
#define OBJ_KEY
Definition: astobj2.h:1151
#define OBJ_POINTER
Definition: astobj2.h:1150
static struct stasis_app_stored_recording * recording_alloc(void)
Allocate a recording object.
Definition: stored.c:204
#define AST_DECLARE_STRING_FIELDS(field_list)
Declare the fields needed in a structure.
Definition: stringfields.h:341
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
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
#define OBJ_PARTIAL_KEY
Definition: astobj2.h:1152
#define ast_asprintf(ret, fmt,...)
A wrapper for asprintf()
Definition: astmm.h:267
struct ao2_container * stasis_app_stored_recording_find_all(void)
Find all stored recordings on disk.
Definition: stored.c:294
static struct stasis_rest_handlers recordings
REST handler for /api-docs/recordings.json.
Asterisk file paths, configured in asterisk.conf.
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
Definition: stringfields.h:359
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
#define AST_STRING_FIELD(name)
Declare a string field.
Definition: stringfields.h:303
const char * stasis_app_stored_recording_get_extension(struct stasis_app_stored_recording *recording)
Returns the extension for this recording.
Definition: stored.c:71
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:393
#define ast_debug(level,...)
Log a DEBUG message.
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
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
const ast_string_field name
Definition: stored.c:41
struct ast_json * stasis_app_stored_recording_to_json(struct stasis_app_stored_recording *recording)
Convert stored recording info to JSON.
Definition: stored.c:450
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
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
#define ast_string_field_build(x, field, fmt, args...)
Set a field to a complex (built) value.
Definition: stringfields.h:555
const ast_string_field file
Definition: stored.c:41
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:314
Replace objects with duplicate keys in container.
Definition: astobj2.h:1211
const char * format
Definition: stored.c:41
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
static int recording_sort(const void *obj_left, const void *obj_right, int flags)
Definition: stored.c:223
Abstract JSON element (object, array, string, int, ...).
#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
Generic container type.
#define ast_file_read_dir(dir_name, on_file, obj)
Iterate over each file in a given directory.
Definition: file.h:203
#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
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
Definition: stringfields.h:374
#define ast_string_field_set(x, field, data)
Set a field to a simple string value.
Definition: stringfields.h:521
#define ao2_link(container, obj)
Add an object to a container.
Definition: astobj2.h:1532