BadVPN – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 /**
2 * @file file_open.c
3 * @author Ambroz Bizjak <ambrop7@gmail.com>
4 *
5 * @section LICENSE
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the author nor the
15 * names of its contributors may be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 *
29 * @section DESCRIPTION
30 *
31 * Synopsis:
32 * file_open(string filename, string mode [, map options])
33 *
34 * Variables:
35 * string is_error - "true" if the file_open object is in error state, "false"
36 * otherwise
37 *
38 * Options:
39 * "read_size" - the maximum number of bytes that can be read by a single
40 * read() call. Must be greater than zero. Greater values may improve
41 * performance, but will increase memory usage. Default: 8192.
42 *
43 * Description:
44 * Opens a file for subsequent reading or writing. The 'mode' argument must
45 * be one of: "r", "w", "a", "r+", "w+", "a+"; it corresponds to the mode string
46 * that will be passed to the fopen() function.
47 * When the file_open() statement goes up, the error state is set depending on
48 * whether opening succeeded or failed. The 'is_error' variable should be used
49 * to check the error state.
50 * If an error occurs afterward within read(), write() or seek(), the error state
51 * is set, and the file_open() statement is toggled down and back up. This way,
52 * the same piece of user code can handle all file errors.
53 *
54 * Synopsis:
55 * file_open::read()
56 *
57 * Variables:
58 * string (empty) - the data which was read, or an empty string if EOF was reached
59 * string not_eof - "false" if EOF was reached, "true" if not
60 *
61 * Description:
62 * Reads data from an opened file. The file must not be in error state.
63 * If reading fails, this statement will never go up, the error state of the
64 * file_open() statement will be set, and the file_open() statement will trigger
65 * backtracking (go down and up).
66 *
67 * Synopsis:
68 * file_open::write(string data)
69 *
70 * Description:
71 * Writes data to an opened file. The file must not be in error state.
72 * If writing fails, this statement will never go up, the error state of the
73 * file_open() statement will be set, and the file_open() statement will trigger
74 * backtracking (go down and up).
75 *
76 * Synopsis:
77 * file_open::seek(string position, string whence)
78 *
79 * Description:
80 * Sets the file position indicator. The 'position' argument must be a possibly
81 * negative decimal number, and is interpreted relative to 'whence'. Here, 'whence'
82 * may be one of:
83 * - "set", meaning beginning of file,
84 * - "cur", meaning the current position, and
85 * - "end", meaning the end of file.
86 * Errors are handled as in read() and write(). Note that if the position argument
87 * is too small or too large to convert to off_t, this is not a seek error, and only
88 * the seek command will fail.
89 *
90 * Synopsis:
91 * file_open::close()
92 *
93 * Description:
94 * Closes the file. The file must not be in error state.
95 * Errors are handled as handled as in read() and write(), i.e. the process is
96 * backtracked to file_open() with the error state set.
97 * On success, the error state of the file is set (but without backtracking), and
98 * the close() statement goes up .
99 */
100  
101 #include <stdio.h>
102 #include <stdint.h>
103 #include <limits.h>
104  
105 #include <misc/debug.h>
106 #include <misc/balloc.h>
107 #include <misc/parse_number.h>
108 #include <ncd/extra/NCDBuf.h>
109  
110 #include <ncd/module_common.h>
111  
112 #include <generated/blog_channel_ncd_file_open.h>
113  
114 #define READ_BUF_SIZE 8192
115  
116 struct open_instance {
117 NCDModuleInst *i;
118 FILE *fh;
119 NCDBufStore store;
120 };
121  
122 struct read_instance {
123 NCDModuleInst *i;
124 NCDBuf *buf;
125 size_t length;
126 };
127  
128 static int parse_mode (MemRef mr, char *out)
129 {
130 size_t pos = 0;
131 size_t left = mr.len;
132  
133 if (left == 0) {
134 return 0;
135 }
136 switch (MemRef_At(mr, pos)) {
137 case 'r':
138 case 'w':
139 case 'a':
140 *out++ = MemRef_At(mr, pos);
141 pos++;
142 left--;
143 break;
144 default:
145 return 0;
146 }
147  
148 if (left == 0) {
149 goto finish;
150 }
151 switch (MemRef_At(mr, pos)) {
152 case '+':
153 *out++ = MemRef_At(mr, pos);
154 pos++;
155 left--;
156 break;
157 default:
158 return 0;
159 }
160  
161 if (left == 0) {
162 goto finish;
163 }
164  
165 return 0;
166  
167 finish:
168 *out = '\0';
169 return 1;
170 }
171  
172 static void trigger_error (struct open_instance *o)
173 {
174 if (o->fh) {
175 // close file
176 if (fclose(o->fh) != 0) {
177 ModuleLog(o->i, BLOG_ERROR, "fclose failed");
178 }
179  
180 // set no file, indicating error
181 o->fh = NULL;
182 }
183  
184 // go down and up
185 NCDModuleInst_Backend_Down(o->i);
186 NCDModuleInst_Backend_Up(o->i);
187 }
188  
189 static void open_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
190 {
191 struct open_instance *o = vo;
192 o->i = i;
193  
194 // check arguments
195 NCDValRef filename_arg;
196 NCDValRef mode_arg;
197 NCDValRef options_arg = NCDVal_NewInvalid();
198 if (!NCDVal_ListRead(params->args, 2, &filename_arg, &mode_arg) &&
199 !NCDVal_ListRead(params->args, 3, &filename_arg, &mode_arg, &options_arg)
200 ) {
201 ModuleLog(o->i, BLOG_ERROR, "wrong arity");
202 goto fail0;
203 }
204 if (!NCDVal_IsStringNoNulls(filename_arg) || !NCDVal_IsString(mode_arg) ||
205 (!NCDVal_IsInvalid(options_arg) && !NCDVal_IsMap(options_arg))
206 ) {
207 ModuleLog(o->i, BLOG_ERROR, "wrong type");
208 goto fail0;
209 }
210  
211 // check mode
212 char mode[5];
213 if (!parse_mode(NCDVal_StringMemRef(mode_arg), mode)) {
214 ModuleLog(o->i, BLOG_ERROR, "wrong mode");
215 goto fail0;
216 }
217  
218 size_t read_size_opt = READ_BUF_SIZE;
219  
220 // parse options
221 if (!NCDVal_IsInvalid(options_arg)) {
222 int num_recognized = 0;
223 NCDValRef value;
224  
225 if (!NCDVal_IsInvalid(value = NCDVal_MapGetValue(options_arg, "read_size"))) {
226 uintmax_t read_size;
227 if (!ncd_read_uintmax(value, &read_size) || read_size > SIZE_MAX || read_size == 0) {
228 ModuleLog(o->i, BLOG_ERROR, "wrong read_size");
229 goto fail0;
230 }
231 num_recognized++;
232 read_size_opt = read_size;
233 }
234  
235 if (NCDVal_MapCount(options_arg) > num_recognized) {
236 ModuleLog(o->i, BLOG_ERROR, "unrecognized options present");
237 goto fail0;
238 }
239 }
240  
241 // init store
242 NCDBufStore_Init(&o->store, read_size_opt);
243  
244 // null terminate filename
245 NCDValNullTermString filename_nts;
246 if (!NCDVal_StringNullTerminate(filename_arg, &filename_nts)) {
247 ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
248 goto fail1;
249 }
250  
251 // open file
252 o->fh = fopen(filename_nts.data, mode);
253 NCDValNullTermString_Free(&filename_nts);
254 if (!o->fh) {
255 ModuleLog(o->i, BLOG_ERROR, "fopen failed");
256 }
257  
258 // go up
259 NCDModuleInst_Backend_Up(i);
260 return;
261  
262 fail1:
263 NCDBufStore_Free(&o->store);
264 fail0:
265 NCDModuleInst_Backend_DeadError(i);
266 }
267  
268 static void open_func_die (void *vo)
269 {
270 struct open_instance *o = vo;
271  
272 // close file
273 if (o->fh) {
274 if (fclose(o->fh) != 0) {
275 ModuleLog(o->i, BLOG_ERROR, "fclose failed");
276 }
277 }
278  
279 // free store
280 NCDBufStore_Free(&o->store);
281  
282 NCDModuleInst_Backend_Dead(o->i);
283 }
284  
285 static int open_func_getvar (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
286 {
287 struct open_instance *o = vo;
288  
289 if (name == NCD_STRING_IS_ERROR) {
290 *out = ncd_make_boolean(mem, !o->fh);
291 return 1;
292 }
293  
294 return 0;
295 }
296  
297 static void read_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
298 {
299 struct read_instance *o = vo;
300 o->i = i;
301  
302 // check arguments
303 if (!NCDVal_ListRead(params->args, 0)) {
304 ModuleLog(o->i, BLOG_ERROR, "wrong arity");
305 goto fail0;
306 }
307  
308 // get open instance
309 struct open_instance *open_inst = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
310  
311 // make sure it's not in error
312 if (!open_inst->fh) {
313 ModuleLog(o->i, BLOG_ERROR, "open instance is in error");
314 goto fail0;
315 }
316  
317 // get buffer
318 o->buf = NCDBufStore_GetBuf(&open_inst->store);
319 if (!o->buf) {
320 ModuleLog(o->i, BLOG_ERROR, "NCDBufStore_GetBuf failed");
321 goto fail0;
322 }
323  
324 // starting with empty buffer
325 char *data = NCDBuf_Data(o->buf);
326 size_t buf_size = NCDBufStore_BufSize(&open_inst->store);
327 o->length = 0;
328  
329 while (o->length < buf_size) {
330 // read
331 size_t readed = fread(data + o->length, 1, buf_size - o->length, open_inst->fh);
332 if (readed == 0) {
333 break;
334 }
335 ASSERT(readed <= buf_size - o->length)
336  
337 // increment length
338 o->length += readed;
339 }
340  
341 // if we couldn't read anything due to an error, trigger
342 // error in the open instance, and don't go up
343 if (o->length == 0 && !feof(open_inst->fh)) {
344 ModuleLog(o->i, BLOG_ERROR, "fread failed");
345 trigger_error(open_inst);
346 return;
347 }
348  
349 // go up
350 NCDModuleInst_Backend_Up(i);
351 return;
352  
353 fail0:
354 NCDModuleInst_Backend_DeadError(i);
355 }
356  
357 static void read_func_die (void *vo)
358 {
359 struct read_instance *o = vo;
360  
361 // release buffer
362 BRefTarget_Deref(NCDBuf_RefTarget(o->buf));
363  
364 NCDModuleInst_Backend_Dead(o->i);
365 }
366  
367 static int read_func_getvar (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
368 {
369 struct read_instance *o = vo;
370  
371 if (name == NCD_STRING_EMPTY) {
372 *out = NCDVal_NewExternalString(mem, NCDBuf_Data(o->buf), o->length, NCDBuf_RefTarget(o->buf));
373 return 1;
374 }
375  
376 if (name == NCD_STRING_NOT_EOF) {
377 *out = ncd_make_boolean(mem, (o->length != 0));
378 return 1;
379 }
380  
381 return 0;
382 }
383  
384 static void write_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
385 {
386 // check arguments
387 NCDValRef data_arg;
388 if (!NCDVal_ListRead(params->args, 1, &data_arg)) {
389 ModuleLog(i, BLOG_ERROR, "wrong arity");
390 goto fail0;
391 }
392 if (!NCDVal_IsString(data_arg)) {
393 ModuleLog(i, BLOG_ERROR, "wrong type");
394 goto fail0;
395 }
396  
397 // get open instance
398 struct open_instance *open_inst = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
399  
400 // make sure it's not in error
401 if (!open_inst->fh) {
402 ModuleLog(i, BLOG_ERROR, "open instance is in error");
403 goto fail0;
404 }
405  
406 // write all the data
407 MemRef data_mr = NCDVal_StringMemRef(data_arg);
408 size_t pos = 0;
409 while (pos < data_mr.len) {
410 size_t written = fwrite(data_mr.ptr + pos, 1, data_mr.len - pos, open_inst->fh);
411 if (written == 0) {
412 ModuleLog(i, BLOG_ERROR, "fwrite failed");
413 trigger_error(open_inst);
414 return;
415 }
416 ASSERT(written <= data_mr.len - pos)
417 pos += written;
418 }
419  
420 // go up
421 NCDModuleInst_Backend_Up(i);
422 return;
423  
424 fail0:
425 NCDModuleInst_Backend_DeadError(i);
426 }
427  
428 static void seek_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
429 {
430 // check arguments
431 NCDValRef position_arg;
432 NCDValRef whence_arg;
433 if (!NCDVal_ListRead(params->args, 2, &position_arg, &whence_arg)) {
434 ModuleLog(i, BLOG_ERROR, "wrong arity");
435 goto fail0;
436 }
437 if (!NCDVal_IsString(position_arg) || !NCDVal_IsString(whence_arg)) {
438 ModuleLog(i, BLOG_ERROR, "wrong type");
439 goto fail0;
440 }
441  
442 // parse position
443 int position_sign;
444 uintmax_t position_mag;
445 if (!parse_signmag_integer(NCDVal_StringMemRef(position_arg), &position_sign, &position_mag)) {
446 ModuleLog(i, BLOG_ERROR, "wrong position");
447 goto fail0;
448 }
449  
450 // parse whence
451 int whence;
452 if (NCDVal_StringEquals(whence_arg, "set")) {
453 whence = SEEK_SET;
454 }
455 else if (NCDVal_StringEquals(whence_arg, "cur")) {
456 whence = SEEK_CUR;
457 }
458 else if (NCDVal_StringEquals(whence_arg, "end")) {
459 whence = SEEK_END;
460 }
461 else {
462 ModuleLog(i, BLOG_ERROR, "wrong whence");
463 goto fail0;
464 }
465  
466 // determine min/max values of off_t (non-portable hack)
467 off_t off_t_min = (sizeof(off_t) == 8 ? INT64_MIN : INT32_MIN);
468 off_t off_t_max = (sizeof(off_t) == 8 ? INT64_MAX : INT32_MAX);
469  
470 // compute position as off_t
471 off_t position;
472 if (position_sign < 0 && position_mag > 0) {
473 if (position_mag - 1 > -(off_t_min + 1)) {
474 ModuleLog(i, BLOG_ERROR, "position underflow");
475 goto fail0;
476 }
477 position = -(off_t)(position_mag - 1) - 1;
478 } else {
479 if (position_mag > off_t_max) {
480 ModuleLog(i, BLOG_ERROR, "position overflow");
481 goto fail0;
482 }
483 position = position_mag;
484 }
485  
486 // get open instance
487 struct open_instance *open_inst = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
488  
489 // make sure it's not in error
490 if (!open_inst->fh) {
491 ModuleLog(i, BLOG_ERROR, "open instance is in error");
492 goto fail0;
493 }
494  
495 // seek
496 if (fseeko(open_inst->fh, position, whence) < 0) {
497 ModuleLog(i, BLOG_ERROR, "fseeko failed");
498 trigger_error(open_inst);
499 return;
500 }
501  
502 // go up
503 NCDModuleInst_Backend_Up(i);
504 return;
505  
506 fail0:
507 NCDModuleInst_Backend_DeadError(i);
508 }
509  
510 static void close_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
511 {
512 // check arguments
513 if (!NCDVal_ListRead(params->args, 0)) {
514 ModuleLog(i, BLOG_ERROR, "wrong arity");
515 goto fail0;
516 }
517  
518 // get open instance
519 struct open_instance *open_inst = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
520  
521 // make sure it's not in error
522 if (!open_inst->fh) {
523 ModuleLog(i, BLOG_ERROR, "open instance is in error");
524 goto fail0;
525 }
526  
527 // close
528 int res = fclose(open_inst->fh);
529 open_inst->fh = NULL;
530 if (res != 0) {
531 ModuleLog(i, BLOG_ERROR, "fclose failed");
532 trigger_error(open_inst);
533 return;
534 }
535  
536 // go up
537 NCDModuleInst_Backend_Up(i);
538 return;
539  
540 fail0:
541 NCDModuleInst_Backend_DeadError(i);
542 }
543  
544 static struct NCDModule modules[] = {
545 {
546 .type = "file_open",
547 .func_new2 = open_func_new,
548 .func_die = open_func_die,
549 .func_getvar2 = open_func_getvar,
550 .alloc_size = sizeof(struct open_instance)
551 }, {
552 .type = "file_open::read",
553 .func_new2 = read_func_new,
554 .func_die = read_func_die,
555 .func_getvar2 = read_func_getvar,
556 .alloc_size = sizeof(struct read_instance)
557 }, {
558 .type = "file_open::write",
559 .func_new2 = write_func_new
560 }, {
561 .type = "file_open::seek",
562 .func_new2 = seek_func_new
563 }, {
564 .type = "file_open::close",
565 .func_new2 = close_func_new
566 }, {
567 .type = NULL
568 }
569 };
570  
571 const struct NCDModuleGroup ncdmodule_file_open = {
572 .modules = modules
573 };