Asterisk - The Open Source Telephony Project  21.4.1
func_env.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2010, Digium, Inc.
5  *
6  * See http://www.asterisk.org for more information about
7  * the Asterisk project. Please do not directly contact
8  * any of the maintainers of this project for assistance;
9  * the project provides a web site, mailing lists and IRC
10  * channels for your use.
11  *
12  * This program is free software, distributed under the terms of
13  * the GNU General Public License Version 2. See the LICENSE file
14  * at the top of the source tree.
15  */
16 
17 /*! \file
18  *
19  * \brief Environment related dialplan functions
20  *
21  * \ingroup functions
22  */
23 
24 /*** MODULEINFO
25  <support_level>core</support_level>
26  ***/
27 
28 #include "asterisk.h"
29 
30 #include <sys/stat.h> /* stat(2) */
31 #include <libgen.h> /* dirname and basename */
32 
33 #include "asterisk/module.h"
34 #include "asterisk/channel.h"
35 #include "asterisk/pbx.h"
36 #include "asterisk/utils.h"
37 #include "asterisk/app.h"
38 #include "asterisk/file.h"
39 
40 /*** DOCUMENTATION
41  <function name="ENV" language="en_US">
42  <synopsis>
43  Gets or sets the environment variable specified.
44  </synopsis>
45  <syntax>
46  <parameter name="varname" required="true">
47  <para>Environment variable name</para>
48  </parameter>
49  </syntax>
50  <description>
51  <para>Variables starting with <literal>AST_</literal> are reserved to the system and may not be set.</para>
52  <para>Additionally, the following system variables are available as special built-in dialplan variables.
53  These variables cannot be set or modified and are read-only.</para>
54  <variablelist>
55  <variable name="EPOCH">
56  <para>Current unix style epoch</para>
57  </variable>
58  <variable name="SYSTEMNAME">
59  <para>value of the <literal>systemname</literal> option from <literal>asterisk.conf</literal></para>
60  </variable>
61  <variable name="ASTCACHEDIR">
62  <para>value of the <literal>astcachedir</literal> option from <literal>asterisk.conf</literal></para>
63  </variable>
64  <variable name="ASTETCDIR">
65  <para>value of the <literal>astetcdir</literal> option from <literal>asterisk.conf</literal></para>
66  </variable>
67  <variable name="ASTMODDIR">
68  <para>value of the <literal>astmoddir</literal> option from <literal>asterisk.conf</literal></para>
69  </variable>
70  <variable name="ASTVARLIBDIR">
71  <para>value of the <literal>astvarlib</literal> option from <literal>asterisk.conf</literal></para>
72  </variable>
73  <variable name="ASTDBDIR">
74  <para>value of the <literal>astdbdir</literal> option from <literal>asterisk.conf</literal></para>
75  </variable>
76  <variable name="ASTKEYDIR">
77  <para>value of the <literal>astkeydir</literal> option from <literal>asterisk.conf</literal></para>
78  </variable>
79  <variable name="ASTDATADIR">
80  <para>value of the <literal>astdatadir</literal> option from <literal>asterisk.conf</literal></para>
81  </variable>
82  <variable name="ASTAGIDIR">
83  <para>value of the <literal>astagidir</literal> option from <literal>asterisk.conf</literal></para>
84  </variable>
85  <variable name="ASTSPOOLDIR">
86  <para>value of the <literal>astspooldir</literal> option from <literal>asterisk.conf</literal></para>
87  </variable>
88  <variable name="ASTRUNDIR">
89  <para>value of the <literal>astrundir</literal> option from <literal>asterisk.conf</literal></para>
90  </variable>
91  <variable name="ASTLOGDIR">
92  <para>value of the <literal>astlogdir</literal> option from <literal>asterisk.conf</literal></para>
93  </variable>
94  <variable name="ASTSBINDIR">
95  <para>value of the <literal>astsbindir</literal> option from <literal>asterisk.conf</literal></para>
96  </variable>
97  <variable name="ENTITYID">
98  <para>Global Entity ID set automatically, or from <literal>asterisk.conf</literal></para>
99  </variable>
100  </variablelist>
101  </description>
102  </function>
103  <function name="STAT" language="en_US">
104  <synopsis>
105  Does a check on the specified file.
106  </synopsis>
107  <syntax>
108  <parameter name="flag" required="true">
109  <para>Flag may be one of the following:</para>
110  <para>d - Checks if the file is a directory.</para>
111  <para>e - Checks if the file exists.</para>
112  <para>f - Checks if the file is a regular file.</para>
113  <para>m - Returns the file mode (in octal)</para>
114  <para>s - Returns the size (in bytes) of the file</para>
115  <para>A - Returns the epoch at which the file was last accessed.</para>
116  <para>C - Returns the epoch at which the inode was last changed.</para>
117  <para>M - Returns the epoch at which the file was last modified.</para>
118  </parameter>
119  <parameter name="filename" required="true" />
120  </syntax>
121  <description>
122  <note>
123  <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
124  is set to <literal>no</literal>, this function can only be executed from the
125  dialplan, and not directly from external protocols.</para>
126  </note>
127  </description>
128  </function>
129  <function name="FILE" language="en_US">
130  <synopsis>
131  Read or write text file.
132  </synopsis>
133  <syntax>
134  <parameter name="filename" required="true" />
135  <parameter name="offset">
136  <para>Maybe specified as any number. If negative, <replaceable>offset</replaceable> specifies the number
137  of bytes back from the end of the file.</para>
138  </parameter>
139  <parameter name="length">
140  <para>If specified, will limit the length of the data read to that size. If negative,
141  trims <replaceable>length</replaceable> bytes from the end of the file.</para>
142  </parameter>
143  <parameter name="options">
144  <optionlist>
145  <option name="l">
146  <para>Line mode: offset and length are assumed to be
147  measured in lines, instead of byte offsets.</para>
148  </option>
149  <option name="a">
150  <para>In write mode only, the append option is used to
151  append to the end of the file, instead of overwriting
152  the existing file.</para>
153  </option>
154  <option name="d">
155  <para>In write mode and line mode only, this option does
156  not automatically append a newline string to the end of
157  a value. This is useful for deleting lines, instead of
158  setting them to blank.</para>
159  </option>
160  </optionlist>
161  </parameter>
162  <parameter name="format">
163  <para>The <replaceable>format</replaceable> parameter may be
164  used to delimit the type of line terminators in line mode.</para>
165  <optionlist>
166  <option name="u">
167  <para>Unix newline format.</para>
168  </option>
169  <option name="d">
170  <para>DOS newline format.</para>
171  </option>
172  <option name="m">
173  <para>Macintosh newline format.</para>
174  </option>
175  </optionlist>
176  </parameter>
177  </syntax>
178  <description>
179  <para>Read and write text file in character and line mode.</para>
180  <para>Examples:</para>
181  <para>Read mode (byte):</para>
182  <example title="Reads the entire content of the file">
183  same => n,Set(foo=${FILE(/tmp/test.txt)})
184  </example>
185  <example title="Reads from the 11th byte to the end of the file (i.e. skips the first 10)">
186  same => n,Set(foo=${FILE(/tmp/test.txt,10)})
187  </example>
188  <example title="Reads from the 11th to 20th byte in the file (i.e. skip the first 10, then read 10 bytes)">
189  same => n,Set(foo=${FILE(/tmp/test.txt,10,10)})
190  </example>
191  <para>Read mode (line):</para>
192  <example title="Reads the 3rd line of the file">
193  same => n,Set(foo=${FILE(/tmp/test.txt,3,1,l)})
194  </example>
195  <example title="Reads the 3rd and 4th lines of the file">
196  same => n,Set(foo=${FILE(/tmp/test.txt,3,2,l)})
197  </example>
198  <example title="Reads from the third line to the end of the file">
199  same => n,Set(foo=${FILE(/tmp/test.txt,3,,l)})
200  </example>
201  <example title="Reads the last three lines of the file">
202  same => n,Set(foo=${FILE(/tmp/test.txt,-3,,l)})
203  </example>
204  <example title="Reads the 3rd line of a DOS-formatted file">
205  same => n,Set(foo=${FILE(/tmp/test.txt,3,1,l,d)})
206  </example>
207  <para>Write mode (byte):</para>
208  <example title="Truncate the file and write bar">
209  same => n,Set(FILE(/tmp/test.txt)=bar)
210  </example>
211  <example title="Append bar">
212  same => n,Set(FILE(/tmp/test.txt,,,a)=bar)
213  </example>
214  <example title="Replace the first byte with bar (replaces 1 character with 3)">
215  same => n,Set(FILE(/tmp/test.txt,0,1)=bar)
216  </example>
217  <example title="Replace 10 bytes beginning at the 21st byte of the file with bar">
218  same => n,Set(FILE(/tmp/test.txt,20,10)=bar)
219  </example>
220  <example title="Replace all bytes from the 21st with bar">
221  same => n,Set(FILE(/tmp/test.txt,20)=bar)
222  </example>
223  <example title="Insert bar after the 4th character">
224  same => n,Set(FILE(/tmp/test.txt,4,0)=bar)
225  </example>
226  <para>Write mode (line):</para>
227  <example title="Replace the first line of the file with bar">
228  same => n,Set(FILE(/tmp/foo.txt,0,1,l)=bar)
229  </example>
230  <example title="Replace the last line of the file with bar">
231  same => n,Set(FILE(/tmp/foo.txt,-1,,l)=bar)
232  </example>
233  <example title="Append bar to the file with a newline">
234  same => n,Set(FILE(/tmp/foo.txt,,,al)=bar)
235  </example>
236  <note>
237  <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
238  is set to <literal>no</literal>, this function can only be executed from the
239  dialplan, and not directly from external protocols.</para>
240  </note>
241  </description>
242  <see-also>
243  <ref type="function">FILE_COUNT_LINE</ref>
244  <ref type="function">FILE_FORMAT</ref>
245  </see-also>
246  </function>
247  <function name="FILE_COUNT_LINE" language="en_US">
248  <synopsis>
249  Obtains the number of lines of a text file.
250  </synopsis>
251  <syntax>
252  <parameter name="filename" required="true" />
253  <parameter name="format">
254  <para>Format may be one of the following:</para>
255  <optionlist>
256  <option name="u">
257  <para>Unix newline format.</para>
258  </option>
259  <option name="d">
260  <para>DOS newline format.</para>
261  </option>
262  <option name="m">
263  <para>Macintosh newline format.</para>
264  </option>
265  </optionlist>
266  <note><para>If not specified, an attempt will be made to determine the newline format type.</para></note>
267  </parameter>
268  </syntax>
269  <description>
270  <para>Returns the number of lines, or <literal>-1</literal> on error.</para>
271  <note>
272  <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
273  is set to <literal>no</literal>, this function can only be executed from the
274  dialplan, and not directly from external protocols.</para>
275  </note>
276  </description>
277  <see-also>
278  <ref type="function">FILE</ref>
279  <ref type="function">FILE_FORMAT</ref>
280  </see-also>
281  </function>
282  <function name="FILE_FORMAT" language="en_US">
283  <synopsis>
284  Return the newline format of a text file.
285  </synopsis>
286  <syntax>
287  <parameter name="filename" required="true" />
288  </syntax>
289  <description>
290  <para>Return the line terminator type:</para>
291  <para>'u' - Unix "\n" format</para>
292  <para>'d' - DOS "\r\n" format</para>
293  <para>'m' - Macintosh "\r" format</para>
294  <para>'x' - Cannot be determined</para>
295  <note>
296  <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
297  is set to <literal>no</literal>, this function can only be executed from the
298  dialplan, and not directly from external protocols.</para>
299  </note>
300  </description>
301  <see-also>
302  <ref type="function">FILE</ref>
303  <ref type="function">FILE_COUNT_LINE</ref>
304  </see-also>
305  </function>
306  <function name="BASENAME" language="en_US">
307  <since>
308  <version>16.21.0</version>
309  <version>18.7.0</version>
310  <version>19.0.0</version>
311  </since>
312  <synopsis>
313  Return the name of a file.
314  </synopsis>
315  <syntax>
316  <parameter name="filename" required="true" />
317  </syntax>
318  <description>
319  <para>Return the base file name, given a full file path.</para>
320  <example title="Directory name">
321  same => n,Set(basename=${BASENAME(/etc/asterisk/extensions.conf)})
322  same => n,NoOp(${basename}) ; outputs extensions.conf
323  </example>
324  </description>
325  <see-also>
326  <ref type="function">DIRNAME</ref>
327  </see-also>
328  </function>
329  <function name="DIRNAME" language="en_US">
330  <since>
331  <version>16.21.0</version>
332  <version>18.7.0</version>
333  <version>19.0.0</version>
334  </since>
335  <synopsis>
336  Return the directory of a file.
337  </synopsis>
338  <syntax>
339  <parameter name="filename" required="true" />
340  </syntax>
341  <description>
342  <para>Return the directory of a file, given a full file path.</para>
343  <example title="Directory name">
344  same => n,Set(dirname=${DIRNAME(/etc/asterisk/extensions.conf)})
345  same => n,NoOp(${dirname}) ; outputs /etc/asterisk
346  </example>
347  </description>
348  <see-also>
349  <ref type="function">BASENAME</ref>
350  </see-also>
351  </function>
352  ***/
353 
354 static int env_read(struct ast_channel *chan, const char *cmd, char *data,
355  char *buf, size_t len)
356 {
357  char *ret = NULL;
358 
359  *buf = '\0';
360 
361  if (data)
362  ret = getenv(data);
363 
364  if (ret)
365  ast_copy_string(buf, ret, len);
366 
367  return 0;
368 }
369 
370 static int env_write(struct ast_channel *chan, const char *cmd, char *data,
371  const char *value)
372 {
373  if (!ast_strlen_zero(data) && strncmp(data, "AST_", 4)) {
374  if (!ast_strlen_zero(value)) {
375  setenv(data, value, 1);
376  } else {
377  unsetenv(data);
378  }
379  }
380 
381  return 0;
382 }
383 
384 static int stat_read(struct ast_channel *chan, const char *cmd, char *data,
385  char *buf, size_t len)
386 {
387  char *action;
388  struct stat s;
389 
390  ast_copy_string(buf, "0", len);
391 
392  action = strsep(&data, ",");
393  if (stat(data, &s)) {
394  return 0;
395  } else {
396  switch (*action) {
397  case 'e':
398  strcpy(buf, "1");
399  break;
400  case 's':
401  snprintf(buf, len, "%u", (unsigned int) s.st_size);
402  break;
403  case 'f':
404  snprintf(buf, len, "%d", S_ISREG(s.st_mode) ? 1 : 0);
405  break;
406  case 'd':
407  snprintf(buf, len, "%d", S_ISDIR(s.st_mode) ? 1 : 0);
408  break;
409  case 'M':
410  snprintf(buf, len, "%d", (int) s.st_mtime);
411  break;
412  case 'A':
413  snprintf(buf, len, "%d", (int) s.st_mtime);
414  break;
415  case 'C':
416  snprintf(buf, len, "%d", (int) s.st_ctime);
417  break;
418  case 'm':
419  snprintf(buf, len, "%o", (unsigned int) s.st_mode);
420  break;
421  }
422  }
423 
424  return 0;
425 }
426 
427 enum file_format {
428  FF_UNKNOWN = -1,
429  FF_UNIX,
430  FF_DOS,
431  FF_MAC,
432 };
433 
434 static int64_t count_lines(const char *filename, enum file_format newline_format)
435 {
436  int count = 0;
437  char fbuf[4096];
438  FILE *ff;
439 
440  if (!(ff = fopen(filename, "r"))) {
441  ast_log(LOG_ERROR, "Unable to open '%s': %s\n", filename, strerror(errno));
442  return -1;
443  }
444 
445  while (fgets(fbuf, sizeof(fbuf), ff)) {
446  char *next = fbuf, *first_cr = NULL, *first_nl = NULL;
447 
448  /* Must do it this way, because if the fileformat is FF_MAC, then Unix
449  * assumptions about line-format will not come into play. */
450  while (next) {
451  if (newline_format == FF_DOS || newline_format == FF_MAC || newline_format == FF_UNKNOWN) {
452  first_cr = strchr(next, '\r');
453  }
454  if (newline_format == FF_UNIX || newline_format == FF_UNKNOWN) {
455  first_nl = strchr(next, '\n');
456  }
457 
458  /* No terminators found in buffer */
459  if (!first_cr && !first_nl) {
460  break;
461  }
462 
463  if (newline_format == FF_UNKNOWN) {
464  if ((first_cr && !first_nl) || (first_cr && first_cr < first_nl)) {
465  if (first_nl && first_nl == first_cr + 1) {
466  newline_format = FF_DOS;
467  } else if (first_cr && first_cr == &fbuf[sizeof(fbuf) - 2]) {
468  /* Get it on the next pass */
469  fseek(ff, -1, SEEK_CUR);
470  break;
471  } else {
472  newline_format = FF_MAC;
473  first_nl = NULL;
474  }
475  } else {
476  newline_format = FF_UNIX;
477  first_cr = NULL;
478  }
479  /* Jump down into next section */
480  }
481 
482  if (newline_format == FF_DOS) {
483  if (first_nl && first_cr && first_nl == first_cr + 1) {
484  next = first_nl + 1;
485  count++;
486  } else if (first_cr == &fbuf[sizeof(fbuf) - 2]) {
487  /* Get it on the next pass */
488  fseek(ff, -1, SEEK_CUR);
489  break;
490  }
491  } else if (newline_format == FF_MAC) {
492  if (first_cr) {
493  next = first_cr + 1;
494  count++;
495  }
496  } else if (newline_format == FF_UNIX) {
497  if (first_nl) {
498  next = first_nl + 1;
499  count++;
500  }
501  }
502  }
503  }
504  fclose(ff);
505 
506  return count;
507 }
508 
509 static int file_count_line(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
510 {
511  enum file_format newline_format = FF_UNKNOWN;
512  int64_t count;
514  AST_APP_ARG(filename);
515  AST_APP_ARG(format);
516  );
517 
518  AST_STANDARD_APP_ARGS(args, data);
519  if (args.argc > 1) {
520  if (tolower(args.format[0]) == 'd') {
521  newline_format = FF_DOS;
522  } else if (tolower(args.format[0]) == 'm') {
523  newline_format = FF_MAC;
524  } else if (tolower(args.format[0]) == 'u') {
525  newline_format = FF_UNIX;
526  }
527  }
528 
529  count = count_lines(args.filename, newline_format);
530  ast_str_set(buf, len, "%" PRId64, count);
531  return 0;
532 }
533 
534 #define LINE_COUNTER(cptr, term, counter) \
535  if (*cptr == '\n' && term == FF_UNIX) { \
536  counter++; \
537  } else if (*cptr == '\n' && term == FF_DOS && dos_state == 0) { \
538  dos_state = 1; \
539  } else if (*cptr == '\r' && term == FF_DOS && dos_state == 1) { \
540  dos_state = 0; \
541  counter++; \
542  } else if (*cptr == '\r' && term == FF_MAC) { \
543  counter++; \
544  } else if (term == FF_DOS) { \
545  dos_state = 0; \
546  }
547 
548 static enum file_format file2format(const char *filename)
549 {
550  FILE *ff;
551  char fbuf[4096];
552  char *first_cr, *first_nl;
553  enum file_format newline_format = FF_UNKNOWN;
554 
555  if (!(ff = fopen(filename, "r"))) {
556  ast_log(LOG_ERROR, "Cannot open '%s': %s\n", filename, strerror(errno));
557  return -1;
558  }
559 
560  while (fgets(fbuf, sizeof(fbuf), ff)) {
561  first_cr = strchr(fbuf, '\r');
562  first_nl = strchr(fbuf, '\n');
563 
564  if (!first_cr && !first_nl) {
565  continue;
566  }
567 
568  if ((first_cr && !first_nl) || (first_cr && first_cr < first_nl)) {
569 
570  if (first_nl && first_nl == first_cr + 1) {
571  newline_format = FF_DOS;
572  } else if (first_cr && first_cr == &fbuf[sizeof(fbuf) - 2]) {
573  /* Edge case: get it on the next pass */
574  fseek(ff, -1, SEEK_CUR);
575  continue;
576  } else {
577  newline_format = FF_MAC;
578  }
579  } else {
580  newline_format = FF_UNIX;
581  }
582  break;
583  }
584  fclose(ff);
585  return newline_format;
586 }
587 
588 static int file_format(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
589 {
590  enum file_format newline_format = file2format(data);
591  ast_str_set(buf, len, "%c", newline_format == FF_UNIX ? 'u' : newline_format == FF_DOS ? 'd' : newline_format == FF_MAC ? 'm' : 'x');
592  return 0;
593 }
594 
595 static int file_dirname(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
596 {
597  char *ret = NULL;
598 
599  *buf = '\0';
600 
601  if (data) {
602  ret = dirname(data);
603  }
604 
605  if (ret) {
606  ast_copy_string(buf, ret, len);
607  }
608 
609  return 0;
610 }
611 
612 static int file_basename(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
613 {
614  char *ret = NULL;
615 
616  *buf = '\0';
617 
618  if (data) {
619  ret = basename(data);
620  }
621 
622  if (ret) {
623  ast_copy_string(buf, ret, len);
624  }
625 
626  return 0;
627 }
628 
629 static int file_read(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
630 {
631  FILE *ff;
632  int64_t offset = 0, length = LLONG_MAX;
633  enum file_format format = FF_UNKNOWN;
634  char fbuf[4096];
635  int64_t flength, i; /* iterator needs to be signed, so it can go negative and terminate the loop */
636  int64_t offset_offset = -1, length_offset = -1;
637  char dos_state = 0;
639  AST_APP_ARG(filename);
640  AST_APP_ARG(offset);
641  AST_APP_ARG(length);
642  AST_APP_ARG(options);
643  AST_APP_ARG(fileformat);
644  );
645 
646  AST_STANDARD_APP_ARGS(args, data);
647 
648  if (args.argc > 1) {
649  sscanf(args.offset, "%" SCNd64, &offset);
650  }
651  if (args.argc > 2) {
652  sscanf(args.length, "%" SCNd64, &length);
653  }
654 
655  if (args.argc < 4 || !strchr(args.options, 'l')) {
656  /* Character-based mode */
657  off_t off_i;
658 
659  if (!(ff = fopen(args.filename, "r"))) {
660  ast_log(LOG_WARNING, "Cannot open file '%s' for reading: %s\n", args.filename, strerror(errno));
661  return 0;
662  }
663 
664  if (fseeko(ff, 0, SEEK_END) < 0) {
665  ast_log(LOG_ERROR, "Cannot seek to end of '%s': %s\n", args.filename, strerror(errno));
666  fclose(ff);
667  return -1;
668  }
669  flength = ftello(ff);
670 
671  if (offset < 0) {
672  fseeko(ff, offset, SEEK_END);
673  if ((offset = ftello(ff)) < 0) {
674  ast_log(AST_LOG_ERROR, "Cannot determine offset position of '%s': %s\n", args.filename, strerror(errno));
675  fclose(ff);
676  return -1;
677  }
678  }
679  if (length < 0) {
680  fseeko(ff, length, SEEK_END);
681  if ((length = ftello(ff)) - offset < 0) {
682  /* Eliminates all results */
683  fclose(ff);
684  return -1;
685  }
686  } else if (length == LLONG_MAX) {
687  fseeko(ff, 0, SEEK_END);
688  length = ftello(ff);
689  }
690 
691  ast_str_reset(*buf);
692 
693  fseeko(ff, offset, SEEK_SET);
694  for (off_i = ftello(ff); off_i < flength && off_i < offset + length; off_i += sizeof(fbuf)) {
695  /* Calculate if we need to retrieve just a portion of the file in memory */
696  size_t toappend = sizeof(fbuf);
697 
698  if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf) && !feof(ff)) {
699  ast_log(LOG_ERROR, "Short read?!!\n");
700  break;
701  }
702 
703  /* Don't go past the length requested */
704  if (off_i + toappend > offset + length) {
705  toappend = MIN(offset + length - off_i, flength - off_i);
706  }
707 
708  ast_str_append_substr(buf, len, fbuf, toappend);
709  }
710  fclose(ff);
711  return 0;
712  }
713 
714  /* Line-based read */
715  if (args.argc == 5) {
716  if (tolower(args.fileformat[0]) == 'd') {
717  format = FF_DOS;
718  } else if (tolower(args.fileformat[0]) == 'm') {
719  format = FF_MAC;
720  } else if (tolower(args.fileformat[0]) == 'u') {
721  format = FF_UNIX;
722  }
723  }
724 
725  if (format == FF_UNKNOWN) {
726  if ((format = file2format(args.filename)) == FF_UNKNOWN) {
727  ast_log(LOG_WARNING, "'%s' is not a line-based file\n", args.filename);
728  return -1;
729  }
730  }
731 
732  if (offset < 0 && length <= offset) {
733  /* Length eliminates all content */
734  return -1;
735  } else if (offset == 0) {
736  offset_offset = 0;
737  }
738 
739  if (!(ff = fopen(args.filename, "r"))) {
740  ast_log(LOG_ERROR, "Cannot open '%s': %s\n", args.filename, strerror(errno));
741  return -1;
742  }
743 
744  if (fseek(ff, 0, SEEK_END)) {
745  ast_log(LOG_ERROR, "Cannot seek to end of file '%s': %s\n", args.filename, strerror(errno));
746  fclose(ff);
747  return -1;
748  }
749 
750  flength = ftello(ff);
751 
752  if (length == LLONG_MAX) {
753  length_offset = flength;
754  }
755 
756  /* For negative offset and/or negative length */
757  if (offset < 0 || length < 0) {
758  int64_t count = 0;
759  /* Start with an even multiple of fbuf, so at the end of reading with a
760  * 0 offset, we don't try to go past the beginning of the file. */
761  for (i = (flength / sizeof(fbuf)) * sizeof(fbuf); i >= 0; i -= sizeof(fbuf)) {
762  size_t end;
763  char *pos;
764  if (fseeko(ff, i, SEEK_SET)) {
765  ast_log(LOG_ERROR, "Cannot seek to offset %" PRId64 ": %s\n", i, strerror(errno));
766  }
767  end = fread(fbuf, 1, sizeof(fbuf), ff);
768  for (pos = (end < sizeof(fbuf) ? fbuf + end - 1 : fbuf + sizeof(fbuf) - 1); pos >= fbuf; pos--) {
769  LINE_COUNTER(pos, format, count);
770 
771  if (length < 0 && count * -1 == length) {
772  length_offset = i + (pos - fbuf);
773  } else if (offset < 0 && count * -1 == (offset - 1)) {
774  /* Found our initial offset. We're done with reverse motion! */
775  if (format == FF_DOS) {
776  offset_offset = i + (pos - fbuf) + 2;
777  } else {
778  offset_offset = i + (pos - fbuf) + 1;
779  }
780  break;
781  }
782  }
783  if ((offset < 0 && offset_offset >= 0) || (offset >= 0 && length_offset >= 0)) {
784  break;
785  }
786  }
787  /* We're at the beginning, and the negative offset indicates the exact number of lines in the file */
788  if (offset < 0 && offset_offset < 0 && offset == count * -1) {
789  offset_offset = 0;
790  }
791  }
792 
793  /* Positve line offset */
794  if (offset > 0) {
795  int64_t count = 0;
796  fseek(ff, 0, SEEK_SET);
797  for (i = 0; i < flength; i += sizeof(fbuf)) {
798  char *pos;
799  if (i + sizeof(fbuf) <= flength) {
800  /* Don't let previous values influence current counts, due to short reads */
801  memset(fbuf, 0, sizeof(fbuf));
802  }
803  if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf) && !feof(ff)) {
804  ast_log(LOG_ERROR, "Short read?!!\n");
805  fclose(ff);
806  return -1;
807  }
808  for (pos = fbuf; pos < fbuf + sizeof(fbuf); pos++) {
809  LINE_COUNTER(pos, format, count);
810 
811  if (count == offset) {
812  offset_offset = i + (pos - fbuf) + 1;
813  break;
814  }
815  }
816  if (offset_offset >= 0) {
817  break;
818  }
819  }
820  }
821 
822  if (offset_offset < 0) {
823  ast_log(LOG_ERROR, "Offset '%s' refers to before the beginning of the file!\n", args.offset);
824  fclose(ff);
825  return -1;
826  }
827 
828  ast_str_reset(*buf);
829  if (fseeko(ff, offset_offset, SEEK_SET)) {
830  ast_log(LOG_ERROR, "fseeko failed: %s\n", strerror(errno));
831  }
832 
833  /* If we have both offset_offset and length_offset, then grabbing the
834  * buffer is simply a matter of just retrieving the file and adding it
835  * to buf. Otherwise, we need to run byte-by-byte forward until the
836  * length is complete. */
837  if (length_offset >= 0) {
838  ast_debug(3, "offset=%" PRId64 ", length=%" PRId64 ", offset_offset=%" PRId64 ", length_offset=%" PRId64 "\n", offset, length, offset_offset, length_offset);
839  for (i = offset_offset; i < length_offset; i += sizeof(fbuf)) {
840  if (fread(fbuf, 1, i + sizeof(fbuf) > flength ? flength - i : sizeof(fbuf), ff) < (i + sizeof(fbuf) > flength ? flength - i : sizeof(fbuf))) {
841  ast_log(LOG_ERROR, "Short read?!!\n");
842  }
843  ast_debug(3, "Appending first %" PRId64" bytes of fbuf=%s\n", (int64_t)(i + sizeof(fbuf) > length_offset ? length_offset - i : sizeof(fbuf)), fbuf);
844  ast_str_append_substr(buf, len, fbuf, i + sizeof(fbuf) > length_offset ? length_offset - i : sizeof(fbuf));
845  }
846  } else if (length == 0) {
847  /* Nothing to do */
848  } else {
849  /* Positive line offset */
850  int64_t current_length = 0;
851  char dos_state = 0;
852  ast_debug(3, "offset=%" PRId64 ", length=%" PRId64 ", offset_offset=%" PRId64 ", length_offset=%" PRId64 "\n", offset, length, offset_offset, length_offset);
853  for (i = offset_offset; i < flength; i += sizeof(fbuf)) {
854  char *pos;
855  size_t bytes_read;
856  if ((bytes_read = fread(fbuf, 1, sizeof(fbuf), ff)) < sizeof(fbuf) && !feof(ff)) {
857  ast_log(LOG_ERROR, "Short read?!!\n");
858  fclose(ff);
859  return -1;
860  }
861  for (pos = fbuf; pos < fbuf + bytes_read; pos++) {
862  LINE_COUNTER(pos, format, current_length);
863 
864  if (current_length == length) {
865  length_offset = i + (pos - fbuf) + 1;
866  break;
867  }
868  }
869  ast_debug(3, "length_offset=%" PRId64 ", length_offset - i=%" PRId64 "\n", length_offset, length_offset - i);
870  ast_str_append_substr(buf, len, fbuf, (length_offset >= 0) ? length_offset - i : (flength > i + sizeof(fbuf)) ? sizeof(fbuf) : flength - i);
871 
872  if (length_offset >= 0) {
873  break;
874  }
875  }
876  }
877 
878  fclose(ff);
879  return 0;
880 }
881 
882 const char *format2term(enum file_format f) __attribute__((const));
883 const char *format2term(enum file_format f)
884 {
885  const char *term[] = { "", "\n", "\r\n", "\r" };
886  return term[f + 1];
887 }
888 
889 static int file_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
890 {
892  AST_APP_ARG(filename);
893  AST_APP_ARG(offset);
894  AST_APP_ARG(length);
895  AST_APP_ARG(options);
896  AST_APP_ARG(format);
897  );
898  int64_t offset = 0, length = LLONG_MAX;
899  off_t flength, vlength;
900  size_t foplen = 0;
901  FILE *ff;
902 
903  AST_STANDARD_APP_ARGS(args, data);
904 
905  if (args.argc > 1) {
906  sscanf(args.offset, "%" SCNd64, &offset);
907  }
908  if (args.argc > 2) {
909  sscanf(args.length, "%" SCNd64, &length);
910  }
911 
912  vlength = strlen(value);
913 
914  if (args.argc < 4 || !strchr(args.options, 'l')) {
915  /* Character-based mode */
916 
917  if (args.argc > 3 && strchr(args.options, 'a')) {
918  /* Append mode */
919  if (!(ff = fopen(args.filename, "a"))) {
920  ast_log(LOG_WARNING, "Cannot open file '%s' for appending: %s\n", args.filename, strerror(errno));
921  return 0;
922  }
923  if (fwrite(value, 1, vlength, ff) < vlength) {
924  ast_log(LOG_ERROR, "Short write?!!\n");
925  }
926  fclose(ff);
927  return 0;
928  } else if (offset == 0 && length == LLONG_MAX) {
929  if (!(ff = fopen(args.filename, "w"))) {
930  ast_log(LOG_WARNING, "Cannot open file '%s' for writing: %s\n", args.filename, strerror(errno));
931  return 0;
932  }
933  if (fwrite(value, 1, vlength, ff) < vlength) {
934  ast_log(LOG_ERROR, "Short write?!!\n");
935  }
936  fclose(ff);
937  return 0;
938  }
939 
940  if (!(ff = fopen(args.filename, "r+"))) {
941  ast_log(LOG_WARNING, "Cannot open file '%s' for modification: %s\n", args.filename, strerror(errno));
942  return 0;
943  }
944  fseeko(ff, 0, SEEK_END);
945  flength = ftello(ff);
946 
947  if (offset < 0) {
948  if (fseeko(ff, offset, SEEK_END)) {
949  ast_log(LOG_ERROR, "Cannot seek to offset of '%s': %s\n", args.filename, strerror(errno));
950  fclose(ff);
951  return -1;
952  }
953  if ((offset = ftello(ff)) < 0) {
954  ast_log(AST_LOG_ERROR, "Cannot determine offset position of '%s': %s\n", args.filename, strerror(errno));
955  fclose(ff);
956  return -1;
957  }
958  }
959 
960  if (length < 0) {
961  length = flength - offset + length;
962  if (length < 0) {
963  ast_log(LOG_ERROR, "Length '%s' exceeds the file length. No data will be written.\n", args.length);
964  fclose(ff);
965  return -1;
966  }
967  }
968 
969  fseeko(ff, offset, SEEK_SET);
970 
971  ast_debug(3, "offset=%s/%" PRId64 ", length=%s/%" PRId64 ", vlength=%" PRId64 ", flength=%" PRId64 "\n",
972  S_OR(args.offset, "(null)"), offset, S_OR(args.length, "(null)"), length, vlength, flength);
973 
974  if (length == vlength) {
975  /* Simplest case, a straight replace */
976  if (fwrite(value, 1, vlength, ff) < vlength) {
977  ast_log(LOG_ERROR, "Short write?!!\n");
978  }
979  fclose(ff);
980  } else if (length == LLONG_MAX) {
981  /* Simple truncation */
982  if (fwrite(value, 1, vlength, ff) < vlength) {
983  ast_log(LOG_ERROR, "Short write?!!\n");
984  }
985  fclose(ff);
986  if (truncate(args.filename, offset + vlength)) {
987  ast_log(LOG_ERROR, "Unable to truncate the file: %s\n", strerror(errno));
988  }
989  } else if (length > vlength) {
990  /* More complex -- need to close a gap */
991  char fbuf[4096];
992  off_t cur;
993  if (fwrite(value, 1, vlength, ff) < vlength) {
994  ast_log(LOG_ERROR, "Short write?!!\n");
995  }
996  fseeko(ff, length - vlength, SEEK_CUR);
997  while ((cur = ftello(ff)) < flength) {
998  if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf) && !feof(ff)) {
999  ast_log(LOG_ERROR, "Short read?!!\n");
1000  }
1001  fseeko(ff, cur + vlength - length, SEEK_SET);
1002  if (fwrite(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf)) {
1003  ast_log(LOG_ERROR, "Short write?!!\n");
1004  }
1005  /* Seek to where we stopped reading */
1006  if (fseeko(ff, cur + sizeof(fbuf), SEEK_SET) < 0) {
1007  /* Only reason for seek to fail is EOF */
1008  break;
1009  }
1010  }
1011  fclose(ff);
1012  if (truncate(args.filename, flength - (length - vlength))) {
1013  ast_log(LOG_ERROR, "Unable to truncate the file: %s\n", strerror(errno));
1014  }
1015  } else {
1016  /* Most complex -- need to open a gap */
1017  char fbuf[4096];
1018  off_t lastwritten = flength + vlength - length;
1019 
1020  /* Start reading exactly the buffer size back from the end. */
1021  fseeko(ff, flength - sizeof(fbuf), SEEK_SET);
1022  while (offset < ftello(ff)) {
1023  if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf)) {
1024  ast_log(LOG_ERROR, "Short read?!!\n");
1025  fclose(ff);
1026  return -1;
1027  }
1028  /* Since the read moved our file ptr forward, we reverse, but
1029  * seek an offset equal to the amount we want to extend the
1030  * file by */
1031  fseeko(ff, vlength - length - sizeof(fbuf), SEEK_CUR);
1032 
1033  /* Note the location of this buffer -- we must not overwrite this position. */
1034  lastwritten = ftello(ff);
1035 
1036  if (fwrite(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf)) {
1037  ast_log(LOG_ERROR, "Short write?!!\n");
1038  fclose(ff);
1039  return -1;
1040  }
1041 
1042  if (lastwritten < offset + sizeof(fbuf)) {
1043  break;
1044  }
1045  /* Our file pointer is now either pointing to the end of the
1046  * file (new position) or a multiple of the fbuf size back from
1047  * that point. Move back to where we want to start reading
1048  * again. We never actually try to read beyond the end of the
1049  * file, so we don't have do deal with short reads, as we would
1050  * when we're shortening the file. */
1051  fseeko(ff, 2 * sizeof(fbuf) + vlength - length, SEEK_CUR);
1052  }
1053 
1054  /* Last part of the file that we need to preserve */
1055  if (fseeko(ff, offset + length, SEEK_SET)) {
1056  ast_log(LOG_WARNING, "Unable to seek to %" PRId64 " + %" PRId64 " != %" PRId64 "?)\n", offset, length, ftello(ff));
1057  }
1058 
1059  /* Doesn't matter how much we read -- just need to restrict the write */
1060  ast_debug(1, "Reading at %" PRId64 "\n", ftello(ff));
1061  if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf) && !feof(ff)) {
1062  ast_log(LOG_ERROR, "Short read?!!\n");
1063  }
1064  fseek(ff, offset, SEEK_SET);
1065  /* Write out the value, then write just up until where we last moved some data */
1066  if (fwrite(value, 1, vlength, ff) < vlength) {
1067  ast_log(LOG_ERROR, "Short write?!!\n");
1068  } else {
1069  off_t curpos = ftello(ff);
1070  foplen = lastwritten - curpos;
1071  if (fwrite(fbuf, 1, foplen, ff) < foplen) {
1072  ast_log(LOG_ERROR, "Short write?!!\n");
1073  }
1074  }
1075  fclose(ff);
1076  }
1077  } else {
1078  enum file_format newline_format = FF_UNKNOWN;
1079 
1080  /* Line mode */
1081  if (args.argc == 5) {
1082  if (tolower(args.format[0]) == 'u') {
1083  newline_format = FF_UNIX;
1084  } else if (tolower(args.format[0]) == 'm') {
1085  newline_format = FF_MAC;
1086  } else if (tolower(args.format[0]) == 'd') {
1087  newline_format = FF_DOS;
1088  }
1089  }
1090  if (newline_format == FF_UNKNOWN && (newline_format = file2format(args.filename)) == FF_UNKNOWN) {
1091  ast_log(LOG_ERROR, "File '%s' not in line format\n", args.filename);
1092  return -1;
1093  }
1094 
1095  if (strchr(args.options, 'a')) {
1096  /* Append to file */
1097  if (!(ff = fopen(args.filename, "a"))) {
1098  ast_log(LOG_ERROR, "Unable to open '%s' for appending: %s\n", args.filename, strerror(errno));
1099  return -1;
1100  }
1101  if (fwrite(value, 1, vlength, ff) < vlength) {
1102  ast_log(LOG_ERROR, "Short write?!!\n");
1103  } else if (!strchr(args.options, 'd') && fwrite(format2term(newline_format), 1, strlen(format2term(newline_format)), ff) < strlen(format2term(newline_format))) {
1104  ast_log(LOG_ERROR, "Short write?!!\n");
1105  }
1106  fclose(ff);
1107  } else if (offset == 0 && length == LLONG_MAX) {
1108  /* Overwrite file */
1109  off_t truncsize;
1110  if (!(ff = fopen(args.filename, "w"))) {
1111  ast_log(LOG_ERROR, "Unable to open '%s' for writing: %s\n", args.filename, strerror(errno));
1112  return -1;
1113  }
1114  if (fwrite(value, 1, vlength, ff) < vlength) {
1115  ast_log(LOG_ERROR, "Short write?!!\n");
1116  } else if (!strchr(args.options, 'd') && fwrite(format2term(newline_format), 1, strlen(format2term(newline_format)), ff) < strlen(format2term(newline_format))) {
1117  ast_log(LOG_ERROR, "Short write?!!\n");
1118  }
1119  if ((truncsize = ftello(ff)) < 0) {
1120  ast_log(AST_LOG_ERROR, "Unable to determine truncate position of '%s': %s\n", args.filename, strerror(errno));
1121  }
1122  fclose(ff);
1123  if (truncsize >= 0 && truncate(args.filename, truncsize)) {
1124  ast_log(LOG_ERROR, "Unable to truncate file '%s': %s\n", args.filename, strerror(errno));
1125  return -1;
1126  }
1127  } else {
1128  int64_t offset_offset = (offset == 0 ? 0 : -1), length_offset = -1, flength, i, current_length = 0;
1129  char dos_state = 0, fbuf[4096];
1130 
1131  if (offset < 0 && length < offset) {
1132  /* Nonsense! */
1133  ast_log(LOG_ERROR, "Length cannot specify a position prior to the offset\n");
1134  return -1;
1135  }
1136 
1137  if (!(ff = fopen(args.filename, "r+"))) {
1138  ast_log(LOG_ERROR, "Cannot open '%s' for modification: %s\n", args.filename, strerror(errno));
1139  return -1;
1140  }
1141 
1142  if (fseek(ff, 0, SEEK_END)) {
1143  ast_log(LOG_ERROR, "Cannot seek to end of file '%s': %s\n", args.filename, strerror(errno));
1144  fclose(ff);
1145  return -1;
1146  }
1147  if ((flength = ftello(ff)) < 0) {
1148  ast_log(AST_LOG_ERROR, "Cannot determine end position of file '%s': %s\n", args.filename, strerror(errno));
1149  fclose(ff);
1150  return -1;
1151  }
1152 
1153  /* For negative offset and/or negative length */
1154  if (offset < 0 || length < 0) {
1155  int64_t count = 0;
1156  for (i = (flength / sizeof(fbuf)) * sizeof(fbuf); i >= 0; i -= sizeof(fbuf)) {
1157  char *pos;
1158  if (fseeko(ff, i, SEEK_SET)) {
1159  ast_log(LOG_ERROR, "Cannot seek to offset %" PRId64 ": %s\n", i, strerror(errno));
1160  }
1161  if (i + sizeof(fbuf) >= flength) {
1162  memset(fbuf, 0, sizeof(fbuf));
1163  }
1164  if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf) && !feof(ff)) {
1165  ast_log(LOG_ERROR, "Short read: %s\n", strerror(errno));
1166  fclose(ff);
1167  return -1;
1168  }
1169  for (pos = fbuf + sizeof(fbuf) - 1; pos >= fbuf; pos--) {
1170  LINE_COUNTER(pos, newline_format, count);
1171 
1172  if (length < 0 && count * -1 == length) {
1173  length_offset = i + (pos - fbuf);
1174  } else if (offset < 0 && count * -1 == (offset - 1)) {
1175  /* Found our initial offset. We're done with reverse motion! */
1176  if (newline_format == FF_DOS) {
1177  offset_offset = i + (pos - fbuf) + 2;
1178  } else {
1179  offset_offset = i + (pos - fbuf) + 1;
1180  }
1181  break;
1182  }
1183  }
1184  if ((offset < 0 && offset_offset >= 0) || (offset >= 0 && length_offset >= 0)) {
1185  break;
1186  }
1187  }
1188  /* We're at the beginning, and the negative offset indicates the exact number of lines in the file */
1189  if (offset < 0 && offset_offset < 0 && offset == count * -1) {
1190  offset_offset = 0;
1191  }
1192  }
1193 
1194  /* Positve line offset */
1195  if (offset > 0) {
1196  int64_t count = 0;
1197  fseek(ff, 0, SEEK_SET);
1198  for (i = 0; i < flength; i += sizeof(fbuf)) {
1199  char *pos;
1200  if (i + sizeof(fbuf) >= flength) {
1201  memset(fbuf, 0, sizeof(fbuf));
1202  }
1203  if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf) && !feof(ff)) {
1204  ast_log(LOG_ERROR, "Short read?!!\n");
1205  fclose(ff);
1206  return -1;
1207  }
1208  for (pos = fbuf; pos < fbuf + sizeof(fbuf); pos++) {
1209  LINE_COUNTER(pos, newline_format, count);
1210 
1211  if (count == offset) {
1212  offset_offset = i + (pos - fbuf) + 1;
1213  break;
1214  }
1215  }
1216  if (offset_offset >= 0) {
1217  break;
1218  }
1219  }
1220  }
1221 
1222  if (offset_offset < 0) {
1223  ast_log(LOG_ERROR, "Offset '%s' refers to before the beginning of the file!\n", args.offset);
1224  fclose(ff);
1225  return -1;
1226  }
1227 
1228  if (length == 0) {
1229  length_offset = offset_offset;
1230  } else if (length == LLONG_MAX) {
1231  length_offset = flength;
1232  }
1233 
1234  /* Positive line length */
1235  if (length_offset < 0) {
1236  fseeko(ff, offset_offset, SEEK_SET);
1237  for (i = offset_offset; i < flength; i += sizeof(fbuf)) {
1238  char *pos;
1239  if (i + sizeof(fbuf) >= flength) {
1240  memset(fbuf, 0, sizeof(fbuf));
1241  }
1242  if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf) && !feof(ff)) {
1243  ast_log(LOG_ERROR, "Short read?!!\n");
1244  fclose(ff);
1245  return -1;
1246  }
1247  for (pos = fbuf; pos < fbuf + sizeof(fbuf); pos++) {
1248  LINE_COUNTER(pos, newline_format, current_length);
1249 
1250  if (current_length == length) {
1251  length_offset = i + (pos - fbuf) + 1;
1252  break;
1253  }
1254  }
1255  if (length_offset >= 0) {
1256  break;
1257  }
1258  }
1259  if (length_offset < 0) {
1260  /* Exceeds length of file */
1261  ast_debug(3, "Exceeds length of file? length=%" PRId64 ", count=%" PRId64 ", flength=%" PRId64 "\n", length, current_length, flength);
1262  length_offset = flength;
1263  }
1264  }
1265 
1266  /* Have offset_offset and length_offset now */
1267  if (length_offset - offset_offset == vlength + (strchr(args.options, 'd') ? 0 : strlen(format2term(newline_format)))) {
1268  /* Simple case - replacement of text inline */
1269  fseeko(ff, offset_offset, SEEK_SET);
1270  if (fwrite(value, 1, vlength, ff) < vlength) {
1271  ast_log(LOG_ERROR, "Short write?!!\n");
1272  } else if (!strchr(args.options, 'd') && fwrite(format2term(newline_format), 1, strlen(format2term(newline_format)), ff) < strlen(format2term(newline_format))) {
1273  ast_log(LOG_ERROR, "Short write?!!\n");
1274  }
1275  fclose(ff);
1276  } else if (length_offset - offset_offset > vlength + (strchr(args.options, 'd') ? 0 : strlen(format2term(newline_format)))) {
1277  /* More complex case - need to shorten file */
1278  off_t cur;
1279  int64_t length_length = length_offset - offset_offset;
1280  size_t vlen = vlength + (strchr(args.options, 'd') ? 0 : strlen(format2term(newline_format)));
1281 
1282  ast_debug(3, "offset=%s/%" PRId64 ", length=%s/%" PRId64 " (%" PRId64 "), vlength=%" PRId64 ", flength=%" PRId64 "\n",
1283  args.offset, offset_offset, args.length, length_offset, length_length, vlength, flength);
1284 
1285  fseeko(ff, offset_offset, SEEK_SET);
1286  if (fwrite(value, 1, vlength, ff) < vlength) {
1287  ast_log(LOG_ERROR, "Short write?!!\n");
1288  fclose(ff);
1289  return -1;
1290  } else if (!strchr(args.options, 'd') && fwrite(format2term(newline_format), 1, vlen - vlength, ff) < vlen - vlength) {
1291  ast_log(LOG_ERROR, "Short write?!!\n");
1292  fclose(ff);
1293  return -1;
1294  }
1295  while ((cur = ftello(ff)) < flength) {
1296  if (cur < 0) {
1297  ast_log(AST_LOG_ERROR, "Unable to determine last write position for '%s': %s\n", args.filename, strerror(errno));
1298  fclose(ff);
1299  return -1;
1300  }
1301  fseeko(ff, length_length - vlen, SEEK_CUR);
1302  if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf) && !feof(ff)) {
1303  ast_log(LOG_ERROR, "Short read?!!\n");
1304  fclose(ff);
1305  return -1;
1306  }
1307  /* Seek to where we last stopped writing */
1308  fseeko(ff, cur, SEEK_SET);
1309  if (fwrite(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf)) {
1310  ast_log(LOG_ERROR, "Short write?!!\n");
1311  fclose(ff);
1312  return -1;
1313  }
1314  }
1315  fclose(ff);
1316  if (truncate(args.filename, flength - (length_length - vlen))) {
1317  ast_log(LOG_ERROR, "Truncation of file failed: %s\n", strerror(errno));
1318  }
1319  } else {
1320  /* Most complex case - need to lengthen file */
1321  size_t vlen = vlength + (strchr(args.options, 'd') ? 0 : strlen(format2term(newline_format)));
1322  int64_t origlen = length_offset - offset_offset;
1323  off_t lastwritten = flength + vlen - origlen;
1324 
1325  ast_debug(3, "offset=%s/%" PRId64 ", length=%s/%" PRId64 ", vlength=%" PRId64 ", flength=%" PRId64 "\n",
1326  args.offset, offset_offset, args.length, length_offset, vlength, flength);
1327 
1328  fseeko(ff, flength - sizeof(fbuf), SEEK_SET);
1329  while (offset_offset + sizeof(fbuf) < ftello(ff)) {
1330  if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf)) {
1331  ast_log(LOG_ERROR, "Short read?!!\n");
1332  fclose(ff);
1333  return -1;
1334  }
1335  fseeko(ff, sizeof(fbuf) - vlen - origlen, SEEK_CUR);
1336  if (fwrite(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf)) {
1337  ast_log(LOG_ERROR, "Short write?!!\n");
1338  fclose(ff);
1339  return -1;
1340  }
1341  if ((lastwritten = ftello(ff) - sizeof(fbuf)) < offset_offset + sizeof(fbuf)) {
1342  break;
1343  }
1344  fseeko(ff, 2 * sizeof(fbuf) + vlen - origlen, SEEK_CUR);
1345  }
1346  fseek(ff, length_offset, SEEK_SET);
1347  if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf) && !feof(ff)) {
1348  ast_log(LOG_ERROR, "Short read?!!\n");
1349  fclose(ff);
1350  return -1;
1351  }
1352  fseek(ff, offset_offset, SEEK_SET);
1353  if (fwrite(value, 1, vlength, ff) < vlength) {
1354  ast_log(LOG_ERROR, "Short write?!!\n");
1355  fclose(ff);
1356  return -1;
1357  } else if (!strchr(args.options, 'd') && fwrite(format2term(newline_format), 1, strlen(format2term(newline_format)), ff) < strlen(format2term(newline_format))) {
1358  ast_log(LOG_ERROR, "Short write?!!\n");
1359  fclose(ff);
1360  return -1;
1361  } else {
1362  off_t curpos = ftello(ff);
1363  foplen = lastwritten - curpos;
1364  if (fwrite(fbuf, 1, foplen, ff) < foplen) {
1365  ast_log(LOG_ERROR, "Short write?!!\n");
1366  }
1367  }
1368  fclose(ff);
1369  }
1370  }
1371  }
1372 
1373  return 0;
1374 }
1375 
1376 static struct ast_custom_function env_function = {
1377  .name = "ENV",
1378  .read = env_read,
1379  .write = env_write
1380 };
1381 
1382 static struct ast_custom_function stat_function = {
1383  .name = "STAT",
1384  .read = stat_read,
1385  .read_max = 12,
1386 };
1387 
1388 static struct ast_custom_function file_function = {
1389  .name = "FILE",
1390  .read2 = file_read,
1391  .write = file_write,
1392 };
1393 
1394 static struct ast_custom_function file_count_line_function = {
1395  .name = "FILE_COUNT_LINE",
1396  .read2 = file_count_line,
1397  .read_max = 12,
1398 };
1399 
1400 static struct ast_custom_function file_format_function = {
1401  .name = "FILE_FORMAT",
1402  .read2 = file_format,
1403  .read_max = 2,
1404 };
1405 
1406 static struct ast_custom_function file_dirname_function = {
1407  .name = "DIRNAME",
1408  .read = file_dirname,
1409  .read_max = 12,
1410 };
1411 
1412 static struct ast_custom_function file_basename_function = {
1413  .name = "BASENAME",
1414  .read = file_basename,
1415  .read_max = 12,
1416 };
1417 
1418 static int unload_module(void)
1419 {
1420  int res = 0;
1421 
1422  res |= ast_custom_function_unregister(&env_function);
1423  res |= ast_custom_function_unregister(&stat_function);
1424  res |= ast_custom_function_unregister(&file_function);
1425  res |= ast_custom_function_unregister(&file_count_line_function);
1426  res |= ast_custom_function_unregister(&file_format_function);
1427  res |= ast_custom_function_unregister(&file_dirname_function);
1428  res |= ast_custom_function_unregister(&file_basename_function);
1429 
1430  return res;
1431 }
1432 
1433 static int load_module(void)
1434 {
1435  int res = 0;
1436 
1437  res |= ast_custom_function_register(&env_function);
1438  res |= ast_custom_function_register_escalating(&stat_function, AST_CFE_READ);
1439  res |= ast_custom_function_register_escalating(&file_function, AST_CFE_BOTH);
1440  res |= ast_custom_function_register_escalating(&file_count_line_function, AST_CFE_READ);
1441  res |= ast_custom_function_register_escalating(&file_format_function, AST_CFE_READ);
1442  res |= ast_custom_function_register(&file_dirname_function);
1443  res |= ast_custom_function_register(&file_basename_function);
1444 
1445  return res;
1446 }
1447 
1448 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Environment/filesystem dialplan functions");
const char * name
Definition: pbx.h:119
Main Channel structure associated with a channel.
Asterisk main include file. File version handling, generic pbx functions.
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
Generic File Format Support. Should be included by clients of the file handling routines. File service providers should instead include mod_format.h.
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
Utility functions.
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
Definition: strings.h:1113
#define ast_custom_function_register_escalating(acf, escalation)
Register a custom function which requires escalated privileges.
Definition: pbx.h:1567
General Asterisk PBX channel definitions.
char * ast_str_append_substr(struct ast_str **buf, ssize_t maxlen, const char *src, size_t maxsrc)
Append a non-NULL terminated substring to the end of a dynamic string.
Definition: strings.h:1062
Data structure associated with a custom dialplan function.
Definition: pbx.h:118
#define ast_debug(level,...)
Log a DEBUG message.
Core PBX routines and definitions.
Support for dynamic strings.
Definition: strings.h:623
void ast_str_reset(struct ast_str *buf)
Reset the content of a dynamic string. Useful before a series of ast_str_append.
Definition: strings.h:693
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
Definition: strings.h:80
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
#define ast_custom_function_register(acf)
Register a custom function.
Definition: pbx.h:1558
#define AST_APP_ARG(name)
Define an application argument.