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