Asterisk - The Open Source Telephony Project  18.5.0
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 <[email protected]>
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 <[email protected]>
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] fail 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';
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 }
132  const char *file;
133  char *file_with_ext;
134 };
136 static int is_recording(const char *filename)
137 {
138  const char *ext = strrchr(filename, '.');
139 
140  if (!ext) {
141  /* No file extension; not us */
142  return 0;
143  }
144  ++ext;
145 
146  if (!ast_get_format_for_file_ext(ext)) {
147  ast_debug(5, "Recording %s: unrecognized format %s\n",
148  filename, ext);
149  /* Keep looking */
150  return 0;
151  }
152 
153  /* Return the index to the .ext */
154  return ext - filename - 1;
155 }
157 static int handle_find_recording(const char *dir_name, const char *filename, void *obj)
158 {
159  struct match_recording_data *data = obj;
160  int num;
161 
162  /* If not a recording or the names do not match the keep searching */
163  if (!(num = is_recording(filename)) || strncmp(data->file, filename, num)) {
164  return 0;
165  }
166 
167  if (ast_asprintf(&data->file_with_ext, "%s/%s", dir_name, filename) < 0) {
168  return -1;
169  }
170 
171  return 1;
172 }
173 
174 /*!
175  * \brief Finds a recording in the given directory.
176  *
177  * This function searchs for a file with the given file name, with a registered
178  * format that matches its extension.
179  *
180  * \param dir_name Directory to search (absolute path).
181  * \param file File name, without extension.
182  * \return Absolute path of the recording file.
183  * \return \c NULL if recording is not found.
184  */
185 static char *find_recording(const char *dir_name, const char *file)
186 {
187  struct match_recording_data data = {
188  .file = file,
189  .file_with_ext = NULL
190  };
191 
192  ast_file_read_dir(dir_name, handle_find_recording, &data);
193 
194  /* Note, string potentially allocated in handle_file_recording */
195  return data.file_with_ext;
196 }
197 
198 /*!
199  * \brief Allocate a recording object.
200  */
201 static struct stasis_app_stored_recording *recording_alloc(void)
202 {
203  RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
204  ao2_cleanup);
205  int res;
206 
207  recording = ao2_alloc(sizeof(*recording), stored_recording_dtor);
208  if (!recording) {
209  return NULL;
210  }
211 
212  res = ast_string_field_init(recording, 255);
213  if (res != 0) {
214  return NULL;
215  }
216 
217  ao2_ref(recording, +1);
218  return recording;
219 }
220 
221 static int recording_sort(const void *obj_left, const void *obj_right, int flags)
222 {
223  const struct stasis_app_stored_recording *object_left = obj_left;
224  const struct stasis_app_stored_recording *object_right = obj_right;
225  const char *right_key = obj_right;
226  int cmp;
227 
228  switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
229  case OBJ_POINTER:
230  right_key = object_right->name;
231  /* Fall through */
232  case OBJ_KEY:
233  cmp = strcmp(object_left->name, right_key);
234  break;
235  case OBJ_PARTIAL_KEY:
236  /*
237  * We could also use a partial key struct containing a length
238  * so strlen() does not get called for every comparison instead.
239  */
240  cmp = strncmp(object_left->name, right_key, strlen(right_key));
241  break;
242  default:
243  /* Sort can only work on something with a full or partial key. */
244  ast_assert(0);
245  cmp = 0;
246  break;
247  }
248  return cmp;
249 }
250 
251 static int handle_scan_file(const char *dir_name, const char *filename, void *obj)
252 {
253  struct ao2_container *recordings = obj;
254  struct stasis_app_stored_recording *recording;
255  char *dot, *filepath;
256 
257  /* Skip if it is not a recording */
258  if (!is_recording(filename)) {
259  return 0;
260  }
261 
262  if (ast_asprintf(&filepath, "%s/%s", dir_name, filename) < 0) {
263  return -1;
264  }
265 
266  recording = recording_alloc();
267  if (!recording) {
268  ast_free(filepath);
269  return -1;
270  }
271 
272  ast_string_field_set(recording, file_with_ext, filepath);
273  /* Build file and format from full path */
274  ast_string_field_set(recording, file, filepath);
275 
276  ast_free(filepath);
277 
278  dot = strrchr(recording->file, '.');
279  *dot = '\0';
280  recording->format = dot + 1;
281 
282  /* Removed the recording dir from the file for the name. */
283  ast_string_field_set(recording, name,
284  recording->file + strlen(ast_config_AST_RECORDING_DIR) + 1);
285 
286  /* Add it to the recordings container */
287  ao2_link(recordings, recording);
288  ao2_ref(recording, -1);
289 
290  return 0;
291 }
292 
294 {
295  struct ao2_container *recordings;
296  int res;
297 
300  if (!recordings) {
301  return NULL;
302  }
303 
305  handle_scan_file, recordings, -1);
306  if (res) {
307  ao2_ref(recordings, -1);
308  return NULL;
309  }
311  return recordings;
312 }
313 
315  const char *name)
316 {
317  RAII_VAR(struct stasis_app_stored_recording *, recording, NULL,
318  ao2_cleanup);
319  RAII_VAR(char *, dir, NULL, ast_free);
320  RAII_VAR(char *, file, NULL, ast_free);
321  RAII_VAR(char *, file_with_ext, NULL, ast_free);
322  int res;
323  struct stat file_stat;
324  int prefix_len = strlen(ast_config_AST_RECORDING_DIR);
325 
326  errno = 0;
327 
328  if (!name) {
329  errno = EINVAL;
330  return NULL;
331  }
332 
333  recording = recording_alloc();
334  if (!recording) {
335  return NULL;
336  }
337 
338  res = split_path(name, &dir, &file);
339  if (res != 0) {
340  return NULL;
341  }
342  ast_string_field_build(recording, file, "%s/%s", dir, file);
343 
345  /* It's possible that one or more component of the recording path is
346  * a symbolic link, this would prevent dir from ever matching. */
347  char *real_basedir = realpath(ast_config_AST_RECORDING_DIR, NULL);
348 
349  if (!real_basedir || !ast_begins_with(dir, real_basedir)) {
350  /* Attempt to escape the recording directory */
351  ast_log(LOG_WARNING, "Attempt to access invalid recording directory %s\n",
352  dir);
353  ast_std_free(real_basedir);
354  errno = EACCES;
355 
356  return NULL;
357  }
358 
359  prefix_len = strlen(real_basedir);
360  ast_std_free(real_basedir);
361  }
362 
363  /* The actual name of the recording is file with the config dir
364  * prefix removed.
365  */
366  ast_string_field_set(recording, name, recording->file + prefix_len + 1);
367 
369  if (!file_with_ext) {
370  return NULL;
371  }
373  recording->format = strrchr(recording->file_with_ext, '.');
374  if (!recording->format) {
375  return NULL;
376  }
377  ++(recording->format);
378 
379  res = stat(file_with_ext, &file_stat);
380  if (res != 0) {
381  return NULL;
382  }
383 
384  if (!S_ISREG(file_stat.st_mode)) {
385  /* Let's not play if it's not a regular file */
386  errno = EACCES;
387  return NULL;
388  }
390  ao2_ref(recording, +1);
391  return recording;
392 }
393 
394 int stasis_app_stored_recording_copy(struct stasis_app_stored_recording *src_recording, const char *dst,
395  struct stasis_app_stored_recording **dst_recording)
396 {
397  RAII_VAR(char *, full_path, NULL, ast_free);
398  char *dst_file = ast_strdupa(dst);
399  char *format;
400  char *last_slash;
401  int res;
402 
403  /* Drop the extension if specified, core will do this for us */
404  format = strrchr(dst_file, '.');
405  if (format) {
406  *format = '\0';
407  }
408 
409  /* See if any intermediary directories need to be made */
410  last_slash = strrchr(dst_file, '/');
411  if (last_slash) {
412  RAII_VAR(char *, tmp_path, NULL, ast_free);
413 
414  *last_slash = '\0';
415  if (ast_asprintf(&tmp_path, "%s/%s", ast_config_AST_RECORDING_DIR, dst_file) < 0) {
416  return -1;
417  }
419  tmp_path, 0777) != 0) {
420  /* errno set by ast_mkdir */
421  return -1;
422  }
423  *last_slash = '/';
424  if (ast_asprintf(&full_path, "%s/%s", ast_config_AST_RECORDING_DIR, dst_file) < 0) {
425  return -1;
426  }
427  } else {
428  /* There is no directory portion */
429  if (ast_asprintf(&full_path, "%s/%s", ast_config_AST_RECORDING_DIR, dst_file) < 0) {
430  return -1;
431  }
432  }
433 
434  ast_verb(4, "Copying recording %s to %s (format %s)\n", src_recording->file,
435  full_path, src_recording->format);
436  res = ast_filecopy(src_recording->file, full_path, src_recording->format);
437  if (!res) {
438  *dst_recording = stasis_app_stored_recording_find_by_name(dst_file);
439  }
440 
441  return res;
442 }
443 
445  struct stasis_app_stored_recording *recording)
446 {
447  /* Path was validated when the recording object was created */
448  return unlink(recording->file_with_ext);
449 }
450 
452  struct stasis_app_stored_recording *recording)
453 {
454  if (!recording) {
455  return NULL;
456  }
457 
458  return ast_json_pack("{ s: s, s: s }",
459  "name", recording->name,
460  "format", recording->format);
461 }
int ast_filecopy(const char *oldname, const char *newname, const char *fmt)
Copies a file.
Definition: file.c:1108
Stasis Application Recording API. See StasisApplication API" for detailed documentation.
void ast_std_free(void *ptr)
Definition: astmm.c:1766
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:591
int stasis_app_stored_recording_delete(struct stasis_app_stored_recording *recording)
Delete a recording from disk.
Definition: stored.c:439
static void stored_recording_dtor(void *obj)
Definition: stored.c:46
char * file_with_ext
Definition: stored.c:132
const char * file
Definition: stored.c:131
static char * find_recording(const char *dir_name, const char *file)
Finds a recording in the given directory.
Definition: stored.c:184
const ast_string_field file_with_ext
Definition: stored.c:41
#define OBJ_KEY
Definition: astobj2.h:1155
#define OBJ_POINTER
Definition: astobj2.h:1154
static struct stasis_app_stored_recording * recording_alloc(void)
Allocate a recording object.
Definition: stored.c:200
#define LOG_WARNING
Definition: logger.h:274
#define AST_DECLARE_STRING_FIELDS(field_list)
Declare the fields needed in a structure.
Definition: stringfields.h:337
#define ast_assert(a)
Definition: utils.h:695
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:243
#define NULL
Definition: resample.c:96
const char * ext
Definition: http.c:147
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:1231
#define ast_verb(level,...)
Definition: logger.h:463
static int handle_find_recording(const char *dir_name, const char *filename, void *obj)
Definition: stored.c:156
static int handle_scan_file(const char *dir_name, const char *filename, void *obj)
Definition: stored.c:249
#define OBJ_PARTIAL_KEY
Definition: astobj2.h:1156
#define ast_asprintf(ret, fmt,...)
A wrapper for asprintf()
Definition: astmm.h:269
struct ao2_container * stasis_app_stored_recording_find_all(void)
Find all stored recordings on disk.
Definition: stored.c:290
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:452
#define ast_log
Definition: astobj2.c:42
static struct stasis_rest_handlers recordings
REST handler for /api-docs/recordings.json.
Asterisk file paths, configured in asterisk.conf.
#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:911
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
Definition: stringfields.h:353
#define AST_STRING_FIELD(name)
Declare a string field.
Definition: stringfields.h:299
#define ao2_ref(o, delta)
Definition: astobj2.h:464
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:300
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:389
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
int errno
const ast_string_field name
Definition: stored.c:41
#define ao2_alloc(data_size, destructor_fn)
Definition: astobj2.h:411
struct ast_json * stasis_app_stored_recording_to_json(struct stasis_app_stored_recording *recording)
Convert stored recording info to JSON.
Definition: stored.c:446
#define ast_free(a)
Definition: astmm.h:182
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: main/utils.c:2336
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:1938
#define ast_string_field_build(x, field, fmt, args...)
Set a field to a complex (built) value.
Definition: stringfields.h:550
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:310
Replace objects with duplicate keys in container.
Definition: astobj2.h:1215
const char * ast_config_AST_RECORDING_DIR
Definition: options.c:156
const char * format
Definition: stored.c:41
#define ao2_cleanup(obj)
Definition: astobj2.h:1958
static int force_inline attribute_pure ast_begins_with(const char *str, const char *prefix)
Definition: strings.h:94
static int recording_sort(const void *obj_left, const void *obj_right, int flags)
Definition: stored.c:219
Abstract JSON element (object, array, string, int, ...).
#define ao2_container_alloc_rbtree(ao2_options, container_options, sort_fn, cmp_fn)
Definition: astobj2.h:1358
Generic container type.
#define ast_file_read_dir(dir_name, on_file, obj)
Iterate over each file in a given directory.
Definition: file.h:171
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
Definition: stringfields.h:368
static int is_recording(const char *filename)
Definition: stored.c:135
#define ast_string_field_set(x, field, data)
Set a field to a simple string value.
Definition: stringfields.h:514
#define ao2_link(container, obj)
Definition: astobj2.h:1549