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