Oops, XML documentation fix.
[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                         args.offset, offset, args.length, 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                         }
825                         fclose(ff);
826                         if (truncate(args.filename, flength - (length - vlength))) {
827                                 ast_log(LOG_ERROR, "Unable to truncate the file: %s\n", strerror(errno));
828                         }
829                 } else {
830                         /* Most complex -- need to open a gap */
831                         char fbuf[4096];
832                         off_t lastwritten = flength + vlength - length;
833
834                         /* Start reading exactly the buffer size back from the end. */
835                         fseeko(ff, flength - sizeof(fbuf), SEEK_SET);
836                         while (offset < ftello(ff)) {
837                                 if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf)) {
838                                         ast_log(LOG_ERROR, "Short read?!!\n");
839                                         fclose(ff);
840                                         return -1;
841                                 }
842                                 /* Since the read moved our file ptr forward, we reverse, but
843                                  * seek an offset equal to the amount we want to extend the
844                                  * file by */
845                                 fseeko(ff, vlength - length - sizeof(fbuf), SEEK_CUR);
846
847                                 /* Note the location of this buffer -- we must not overwrite this position. */
848                                 lastwritten = ftello(ff);
849
850                                 if (fwrite(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf)) {
851                                         ast_log(LOG_ERROR, "Short write?!!\n");
852                                         fclose(ff);
853                                         return -1;
854                                 }
855
856                                 if (lastwritten < offset + sizeof(fbuf)) {
857                                         break;
858                                 }
859                                 /* Our file pointer is now either pointing to the end of the
860                                  * file (new position) or a multiple of the fbuf size back from
861                                  * that point.  Move back to where we want to start reading
862                                  * again.  We never actually try to read beyond the end of the
863                                  * file, so we don't have do deal with short reads, as we would
864                                  * when we're shortening the file. */
865                                 fseeko(ff, 2 * sizeof(fbuf) + vlength - length, SEEK_CUR);
866                         }
867
868                         /* Last part of the file that we need to preserve */
869                         if (fseeko(ff, offset + length, SEEK_SET)) {
870                                 ast_log(LOG_WARNING, "Unable to seek to %" PRId64 " + %" PRId64 " != %" PRId64 "?)\n", offset, length, ftello(ff));
871                         }
872
873                         /* Doesn't matter how much we read -- just need to restrict the write */
874                         ast_debug(1, "Reading at %" PRId64 "\n", ftello(ff));
875                         if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf) && !feof(ff)) {
876                                 ast_log(LOG_ERROR, "Short read?!!\n");
877                         }
878                         fseek(ff, offset, SEEK_SET);
879                         /* Write out the value, then write just up until where we last moved some data */
880                         if (fwrite(value, 1, vlength, ff) < vlength) {
881                                 ast_log(LOG_ERROR, "Short write?!!\n");
882                         } else {
883                                 off_t curpos = ftello(ff);
884                                 foplen = lastwritten - curpos;
885                                 if (fwrite(fbuf, 1, foplen, ff) < foplen) {
886                                         ast_log(LOG_ERROR, "Short write?!!\n");
887                                 }
888                         }
889                         fclose(ff);
890                 }
891         } else {
892                 enum file_format newline_format = FF_UNKNOWN;
893
894                 /* Line mode */
895                 if (args.argc == 5) {
896                         if (tolower(args.format[0]) == 'u') {
897                                 newline_format = FF_UNIX;
898                         } else if (tolower(args.format[0]) == 'm') {
899                                 newline_format = FF_MAC;
900                         } else if (tolower(args.format[0]) == 'd') {
901                                 newline_format = FF_DOS;
902                         }
903                 }
904                 if (newline_format == FF_UNKNOWN && (newline_format = file2format(args.filename)) == FF_UNKNOWN) {
905                         ast_log(LOG_ERROR, "File '%s' not in line format\n", args.filename);
906                         return -1;
907                 }
908
909                 if (strchr(args.options, 'a')) {
910                         /* Append to file */
911                         if (!(ff = fopen(args.filename, "a"))) {
912                                 ast_log(LOG_ERROR, "Unable to open '%s' for appending: %s\n", args.filename, strerror(errno));
913                                 return -1;
914                         }
915                         if (fwrite(value, 1, vlength, ff) < vlength) {
916                                 ast_log(LOG_ERROR, "Short write?!!\n");
917                         } else if (!strchr(args.options, 'd') && fwrite(format2term(newline_format), 1, strlen(format2term(newline_format)), ff) < strlen(format2term(newline_format))) {
918                                 ast_log(LOG_ERROR, "Short write?!!\n");
919                         }
920                         fclose(ff);
921                 } else if (offset == 0 && length == LLONG_MAX) {
922                         /* Overwrite file */
923                         off_t truncsize;
924                         if (!(ff = fopen(args.filename, "w"))) {
925                                 ast_log(LOG_ERROR, "Unable to open '%s' for writing: %s\n", args.filename, strerror(errno));
926                                 return -1;
927                         }
928                         if (fwrite(value, 1, vlength, ff) < vlength) {
929                                 ast_log(LOG_ERROR, "Short write?!!\n");
930                         } else if (!strchr(args.options, 'd') && fwrite(format2term(newline_format), 1, strlen(format2term(newline_format)), ff) < strlen(format2term(newline_format))) {
931                                 ast_log(LOG_ERROR, "Short write?!!\n");
932                         }
933                         truncsize = ftello(ff);
934                         fclose(ff);
935                         if (truncate(args.filename, truncsize)) {
936                                 ast_log(LOG_ERROR, "Unable to truncate file: %s\n", strerror(errno));
937                         }
938                 } else {
939                         int64_t offset_offset = (offset == 0 ? 0 : -1), length_offset = -1, flength, i, current_length = 0;
940                         char dos_state = 0, fbuf[4096];
941
942                         if (offset < 0 && length < offset) {
943                                 /* Nonsense! */
944                                 ast_log(LOG_ERROR, "Length cannot specify a position prior to the offset\n");
945                                 return -1;
946                         }
947
948                         if (!(ff = fopen(args.filename, "r+"))) {
949                                 ast_log(LOG_ERROR, "Cannot open '%s' for modification: %s\n", args.filename, strerror(errno));
950                                 return -1;
951                         }
952
953                         if (fseek(ff, 0, SEEK_END)) {
954                                 ast_log(LOG_ERROR, "Cannot seek to end of file '%s': %s\n", args.filename, strerror(errno));
955                                 fclose(ff);
956                                 return -1;
957                         }
958                         flength = ftello(ff);
959
960                         /* For negative offset and/or negative length */
961                         if (offset < 0 || length < 0) {
962                                 int64_t count = 0;
963                                 for (i = (flength / sizeof(fbuf)) * sizeof(fbuf); i >= 0; i -= sizeof(fbuf)) {
964                                         char *pos;
965                                         if (fseeko(ff, i, SEEK_SET)) {
966                                                 ast_log(LOG_ERROR, "Cannot seek to offset %" PRId64 ": %s\n", i, strerror(errno));
967                                         }
968                                         if (i + sizeof(fbuf) >= flength) {
969                                                 memset(fbuf, 0, sizeof(fbuf));
970                                         }
971                                         if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf) && !feof(ff)) {
972                                                 ast_log(LOG_ERROR, "Short read: %s\n", strerror(errno));
973                                                 fclose(ff);
974                                                 return -1;
975                                         }
976                                         for (pos = fbuf + sizeof(fbuf) - 1; pos > fbuf - 1; pos--) {
977                                                 LINE_COUNTER(pos, newline_format, count);
978
979                                                 if (length < 0 && count * -1 == length) {
980                                                         length_offset = i + (pos - fbuf);
981                                                 } else if (offset < 0 && count * -1 == (offset - 1)) {
982                                                         /* Found our initial offset.  We're done with reverse motion! */
983                                                         if (newline_format == FF_DOS) {
984                                                                 offset_offset = i + (pos - fbuf) + 2;
985                                                         } else {
986                                                                 offset_offset = i + (pos - fbuf) + 1;
987                                                         }
988                                                         break;
989                                                 }
990                                         }
991                                         if ((offset < 0 && offset_offset >= 0) || (offset >= 0 && length_offset >= 0)) {
992                                                 break;
993                                         }
994                                 }
995                                 /* We're at the beginning, and the negative offset indicates the exact number of lines in the file */
996                                 if (offset < 0 && offset_offset < 0 && offset == count * -1) {
997                                         offset_offset = 0;
998                                 }
999                         }
1000
1001                         /* Positve line offset */
1002                         if (offset > 0) {
1003                                 int64_t count = 0;
1004                                 fseek(ff, 0, SEEK_SET);
1005                                 for (i = 0; i < flength; i += sizeof(fbuf)) {
1006                                         char *pos;
1007                                         if (i + sizeof(fbuf) >= flength) {
1008                                                 memset(fbuf, 0, sizeof(fbuf));
1009                                         }
1010                                         if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf) && !feof(ff)) {
1011                                                 ast_log(LOG_ERROR, "Short read?!!\n");
1012                                                 fclose(ff);
1013                                                 return -1;
1014                                         }
1015                                         for (pos = fbuf; pos < fbuf + sizeof(fbuf); pos++) {
1016                                                 LINE_COUNTER(pos, newline_format, count);
1017
1018                                                 if (count == offset) {
1019                                                         offset_offset = i + (pos - fbuf) + 1;
1020                                                         break;
1021                                                 }
1022                                         }
1023                                         if (offset_offset >= 0) {
1024                                                 break;
1025                                         }
1026                                 }
1027                         }
1028
1029                         if (offset_offset < 0) {
1030                                 ast_log(LOG_ERROR, "Offset '%s' refers to before the beginning of the file!\n", args.offset);
1031                                 fclose(ff);
1032                                 return -1;
1033                         }
1034
1035                         if (length == 0) {
1036                                 length_offset = offset_offset;
1037                         } else if (length == LLONG_MAX) {
1038                                 length_offset = flength;
1039                         }
1040
1041                         /* Positive line length */
1042                         if (length_offset < 0) {
1043                                 fseeko(ff, offset_offset, SEEK_SET);
1044                                 for (i = offset_offset; i < flength; i += sizeof(fbuf)) {
1045                                         char *pos;
1046                                         if (i + sizeof(fbuf) >= flength) {
1047                                                 memset(fbuf, 0, sizeof(fbuf));
1048                                         }
1049                                         if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf) && !feof(ff)) {
1050                                                 ast_log(LOG_ERROR, "Short read?!!\n");
1051                                                 fclose(ff);
1052                                                 return -1;
1053                                         }
1054                                         for (pos = fbuf; pos < fbuf + sizeof(fbuf); pos++) {
1055                                                 LINE_COUNTER(pos, newline_format, current_length);
1056
1057                                                 if (current_length == length) {
1058                                                         length_offset = i + (pos - fbuf) + 1;
1059                                                         break;
1060                                                 }
1061                                         }
1062                                         if (length_offset >= 0) {
1063                                                 break;
1064                                         }
1065                                 }
1066                                 if (length_offset < 0) {
1067                                         /* Exceeds length of file */
1068                                         ast_debug(3, "Exceeds length of file? length=%" PRId64 ", count=%" PRId64 ", flength=%" PRId64 "\n", length, current_length, flength);
1069                                         length_offset = flength;
1070                                 }
1071                         }
1072
1073                         /* Have offset_offset and length_offset now */
1074                         if (length_offset - offset_offset == vlength + (strchr(args.options, 'd') ? 0 : strlen(format2term(newline_format)))) {
1075                                 /* Simple case - replacement of text inline */
1076                                 fseeko(ff, offset_offset, SEEK_SET);
1077                                 if (fwrite(value, 1, vlength, ff) < vlength) {
1078                                         ast_log(LOG_ERROR, "Short write?!!\n");
1079                                 } else if (!strchr(args.options, 'd') && fwrite(format2term(newline_format), 1, strlen(format2term(newline_format)), ff) < strlen(format2term(newline_format))) {
1080                                         ast_log(LOG_ERROR, "Short write?!!\n");
1081                                 }
1082                                 fclose(ff);
1083                         } else if (length_offset - offset_offset > vlength + (strchr(args.options, 'd') ? 0 : strlen(format2term(newline_format)))) {
1084                                 /* More complex case - need to shorten file */
1085                                 off_t cur;
1086                                 int64_t length_length = length_offset - offset_offset;
1087                                 size_t vlen = vlength + (strchr(args.options, 'd') ? 0 : strlen(format2term(newline_format)));
1088
1089                                 ast_debug(3, "offset=%s/%" PRId64 ", length=%s/%" PRId64 " (%" PRId64 "), vlength=%" PRId64 ", flength=%" PRId64 "\n",
1090                                         args.offset, offset_offset, args.length, length_offset, length_length, vlength, flength);
1091
1092                                 fseeko(ff, offset_offset, SEEK_SET);
1093                                 if (fwrite(value, 1, vlength, ff) < vlength) {
1094                                         ast_log(LOG_ERROR, "Short write?!!\n");
1095                                         fclose(ff);
1096                                         return -1;
1097                                 } else if (!strchr(args.options, 'd') && fwrite(format2term(newline_format), 1, vlen - vlength, ff) < vlen - vlength) {
1098                                         ast_log(LOG_ERROR, "Short write?!!\n");
1099                                         fclose(ff);
1100                                         return -1;
1101                                 }
1102                                 while ((cur = ftello(ff)) < flength) {
1103                                         fseeko(ff, length_length - vlen, SEEK_CUR);
1104                                         if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf) && !feof(ff)) {
1105                                                 ast_log(LOG_ERROR, "Short read?!!\n");
1106                                                 fclose(ff);
1107                                                 return -1;
1108                                         }
1109                                         /* Seek to where we last stopped writing */
1110                                         fseeko(ff, cur, SEEK_SET);
1111                                         if (fwrite(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf)) {
1112                                                 ast_log(LOG_ERROR, "Short write?!!\n");
1113                                                 fclose(ff);
1114                                                 return -1;
1115                                         }
1116                                 }
1117                                 fclose(ff);
1118                                 if (truncate(args.filename, flength - (length_length - vlen))) {
1119                                         ast_log(LOG_ERROR, "Truncation of file failed: %s\n", strerror(errno));
1120                                 }
1121                         } else {
1122                                 /* Most complex case - need to lengthen file */
1123                                 size_t vlen = vlength + (strchr(args.options, 'd') ? 0 : strlen(format2term(newline_format)));
1124                                 int64_t origlen = length_offset - offset_offset;
1125                                 off_t lastwritten = flength + vlen - origlen;
1126
1127                                 ast_debug(3, "offset=%s/%" PRId64 ", length=%s/%" PRId64 ", vlength=%" PRId64 ", flength=%" PRId64 "\n",
1128                                         args.offset, offset_offset, args.length, length_offset, vlength, flength);
1129
1130                                 fseeko(ff, flength - sizeof(fbuf), SEEK_SET);
1131                                 while (offset_offset + sizeof(fbuf) < ftello(ff)) {
1132                                         if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf)) {
1133                                                 ast_log(LOG_ERROR, "Short read?!!\n");
1134                                                 fclose(ff);
1135                                                 return -1;
1136                                         }
1137                                         fseeko(ff, sizeof(fbuf) - vlen - origlen, SEEK_CUR);
1138                                         if (fwrite(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf)) {
1139                                                 ast_log(LOG_ERROR, "Short write?!!\n");
1140                                                 fclose(ff);
1141                                                 return -1;
1142                                         }
1143                                         if ((lastwritten = ftello(ff) - sizeof(fbuf)) < offset_offset + sizeof(fbuf)) {
1144                                                 break;
1145                                         }
1146                                         fseeko(ff, 2 * sizeof(fbuf) + vlen - origlen, SEEK_CUR);
1147                                 }
1148                                 fseek(ff, length_offset, SEEK_SET);
1149                                 if (fread(fbuf, 1, sizeof(fbuf), ff) < sizeof(fbuf) && !feof(ff)) {
1150                                         ast_log(LOG_ERROR, "Short read?!!\n");
1151                                         fclose(ff);
1152                                         return -1;
1153                                 }
1154                                 fseek(ff, offset_offset, SEEK_SET);
1155                                 if (fwrite(value, 1, vlength, ff) < vlength) {
1156                                         ast_log(LOG_ERROR, "Short write?!!\n");
1157                                         fclose(ff);
1158                                         return -1;
1159                                 } else if (!strchr(args.options, 'd') && fwrite(format2term(newline_format), 1, strlen(format2term(newline_format)), ff) < strlen(format2term(newline_format))) {
1160                                         ast_log(LOG_ERROR, "Short write?!!\n");
1161                                         fclose(ff);
1162                                         return -1;
1163                                 } else {
1164                                         off_t curpos = ftello(ff);
1165                                         foplen = lastwritten - curpos;
1166                                         if (fwrite(fbuf, 1, foplen, ff) < foplen) {
1167                                                 ast_log(LOG_ERROR, "Short write?!!\n");
1168                                         }
1169                                 }
1170                                 fclose(ff);
1171                         }
1172                 }
1173         }
1174
1175         return 0;
1176 }
1177
1178 static struct ast_custom_function env_function = {
1179         .name = "ENV",
1180         .read = env_read,
1181         .write = env_write
1182 };
1183
1184 static struct ast_custom_function stat_function = {
1185         .name = "STAT",
1186         .read = stat_read,
1187         .read_max = 12,
1188 };
1189
1190 static struct ast_custom_function file_function = {
1191         .name = "FILE",
1192         .read2 = file_read,
1193         .write = file_write,
1194 };
1195
1196 static struct ast_custom_function file_count_line_function = {
1197         .name = "FILE_COUNT_LINE",
1198         .read2 = file_count_line,
1199         .read_max = 12,
1200 };
1201
1202 static struct ast_custom_function file_format_function = {
1203         .name = "FILE_FORMAT",
1204         .read2 = file_format,
1205         .read_max = 2,
1206 };
1207
1208 static int unload_module(void)
1209 {
1210         int res = 0;
1211
1212         res |= ast_custom_function_unregister(&env_function);
1213         res |= ast_custom_function_unregister(&stat_function);
1214         res |= ast_custom_function_unregister(&file_function);
1215         res |= ast_custom_function_unregister(&file_count_line_function);
1216         res |= ast_custom_function_unregister(&file_format_function);
1217
1218         return res;
1219 }
1220
1221 static int load_module(void)
1222 {
1223         int res = 0;
1224
1225         res |= ast_custom_function_register(&env_function);
1226         res |= ast_custom_function_register(&stat_function);
1227         res |= ast_custom_function_register(&file_function);
1228         res |= ast_custom_function_register(&file_count_line_function);
1229         res |= ast_custom_function_register(&file_format_function);
1230
1231         return res;
1232 }
1233
1234 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Environment/filesystem dialplan functions");