| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573 |
- /**
- * @file file_open.c
- * @author Ambroz Bizjak <ambrop7@gmail.com>
- *
- * @section LICENSE
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the author nor the
- * names of its contributors may be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * @section DESCRIPTION
- *
- * Synopsis:
- * file_open(string filename, string mode [, map options])
- *
- * Variables:
- * string is_error - "true" if the file_open object is in error state, "false"
- * otherwise
- *
- * Options:
- * "read_size" - the maximum number of bytes that can be read by a single
- * read() call. Must be greater than zero. Greater values may improve
- * performance, but will increase memory usage. Default: 8192.
- *
- * Description:
- * Opens a file for subsequent reading or writing. The 'mode' argument must
- * be one of: "r", "w", "a", "r+", "w+", "a+"; it corresponds to the mode string
- * that will be passed to the fopen() function.
- * When the file_open() statement goes up, the error state is set depending on
- * whether opening succeeded or failed. The 'is_error' variable should be used
- * to check the error state.
- * If an error occurs afterward within read(), write() or seek(), the error state
- * is set, and the file_open() statement is toggled down and back up. This way,
- * the same piece of user code can handle all file errors.
- *
- * Synopsis:
- * file_open::read()
- *
- * Variables:
- * string (empty) - the data which was read, or an empty string if EOF was reached
- * string not_eof - "false" if EOF was reached, "true" if not
- *
- * Description:
- * Reads data from an opened file. The file must not be in error state.
- * If reading fails, this statement will never go up, the error state of the
- * file_open() statement will be set, and the file_open() statement will trigger
- * backtracking (go down and up).
- *
- * Synopsis:
- * file_open::write(string data)
- *
- * Description:
- * Writes data to an opened file. The file must not be in error state.
- * If writing fails, this statement will never go up, the error state of the
- * file_open() statement will be set, and the file_open() statement will trigger
- * backtracking (go down and up).
- *
- * Synopsis:
- * file_open::seek(string position, string whence)
- *
- * Description:
- * Sets the file position indicator. The 'position' argument must be a possibly
- * negative decimal number, and is interpreted relative to 'whence'. Here, 'whence'
- * may be one of:
- * - "set", meaning beginning of file,
- * - "cur", meaning the current position, and
- * - "end", meaning the end of file.
- * Errors are handled as in read() and write(). Note that if the position argument
- * is too small or too large to convert to off_t, this is not a seek error, and only
- * the seek command will fail.
- *
- * Synopsis:
- * file_open::close()
- *
- * Description:
- * Closes the file. The file must not be in error state.
- * Errors are handled as handled as in read() and write(), i.e. the process is
- * backtracked to file_open() with the error state set.
- * On success, the error state of the file is set (but without backtracking), and
- * the close() statement goes up .
- */
- #include <stdio.h>
- #include <stdint.h>
- #include <limits.h>
- #include <misc/debug.h>
- #include <misc/balloc.h>
- #include <misc/parse_number.h>
- #include <ncd/extra/NCDBuf.h>
- #include <ncd/module_common.h>
- #include <generated/blog_channel_ncd_file_open.h>
- #define READ_BUF_SIZE 8192
- struct open_instance {
- NCDModuleInst *i;
- FILE *fh;
- NCDBufStore store;
- };
- struct read_instance {
- NCDModuleInst *i;
- NCDBuf *buf;
- size_t length;
- };
- static int parse_mode (MemRef mr, char *out)
- {
- size_t pos = 0;
- size_t left = mr.len;
-
- if (left == 0) {
- return 0;
- }
- switch (MemRef_At(mr, pos)) {
- case 'r':
- case 'w':
- case 'a':
- *out++ = MemRef_At(mr, pos);
- pos++;
- left--;
- break;
- default:
- return 0;
- }
-
- if (left == 0) {
- goto finish;
- }
- switch (MemRef_At(mr, pos)) {
- case '+':
- *out++ = MemRef_At(mr, pos);
- pos++;
- left--;
- break;
- default:
- return 0;
- }
-
- if (left == 0) {
- goto finish;
- }
-
- return 0;
-
- finish:
- *out = '\0';
- return 1;
- }
- static void trigger_error (struct open_instance *o)
- {
- if (o->fh) {
- // close file
- if (fclose(o->fh) != 0) {
- ModuleLog(o->i, BLOG_ERROR, "fclose failed");
- }
-
- // set no file, indicating error
- o->fh = NULL;
- }
-
- // go down and up
- NCDModuleInst_Backend_Down(o->i);
- NCDModuleInst_Backend_Up(o->i);
- }
- static void open_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
- {
- struct open_instance *o = vo;
- o->i = i;
-
- // check arguments
- NCDValRef filename_arg;
- NCDValRef mode_arg;
- NCDValRef options_arg = NCDVal_NewInvalid();
- if (!NCDVal_ListRead(params->args, 2, &filename_arg, &mode_arg) &&
- !NCDVal_ListRead(params->args, 3, &filename_arg, &mode_arg, &options_arg)
- ) {
- ModuleLog(o->i, BLOG_ERROR, "wrong arity");
- goto fail0;
- }
- if (!NCDVal_IsStringNoNulls(filename_arg) || !NCDVal_IsString(mode_arg) ||
- (!NCDVal_IsInvalid(options_arg) && !NCDVal_IsMap(options_arg))
- ) {
- ModuleLog(o->i, BLOG_ERROR, "wrong type");
- goto fail0;
- }
-
- // check mode
- char mode[5];
- if (!parse_mode(NCDVal_StringMemRef(mode_arg), mode)) {
- ModuleLog(o->i, BLOG_ERROR, "wrong mode");
- goto fail0;
- }
-
- size_t read_size_opt = READ_BUF_SIZE;
-
- // parse options
- if (!NCDVal_IsInvalid(options_arg)) {
- int num_recognized = 0;
- NCDValRef value;
-
- if (!NCDVal_IsInvalid(value = NCDVal_MapGetValue(options_arg, "read_size"))) {
- uintmax_t read_size;
- if (!ncd_read_uintmax(value, &read_size) || read_size > SIZE_MAX || read_size == 0) {
- ModuleLog(o->i, BLOG_ERROR, "wrong read_size");
- goto fail0;
- }
- num_recognized++;
- read_size_opt = read_size;
- }
-
- if (NCDVal_MapCount(options_arg) > num_recognized) {
- ModuleLog(o->i, BLOG_ERROR, "unrecognized options present");
- goto fail0;
- }
- }
-
- // init store
- NCDBufStore_Init(&o->store, read_size_opt);
-
- // null terminate filename
- NCDValNullTermString filename_nts;
- if (!NCDVal_StringNullTerminate(filename_arg, &filename_nts)) {
- ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
- goto fail1;
- }
-
- // open file
- o->fh = fopen(filename_nts.data, mode);
- NCDValNullTermString_Free(&filename_nts);
- if (!o->fh) {
- ModuleLog(o->i, BLOG_ERROR, "fopen failed");
- }
-
- // go up
- NCDModuleInst_Backend_Up(i);
- return;
-
- fail1:
- NCDBufStore_Free(&o->store);
- fail0:
- NCDModuleInst_Backend_DeadError(i);
- }
- static void open_func_die (void *vo)
- {
- struct open_instance *o = vo;
-
- // close file
- if (o->fh) {
- if (fclose(o->fh) != 0) {
- ModuleLog(o->i, BLOG_ERROR, "fclose failed");
- }
- }
-
- // free store
- NCDBufStore_Free(&o->store);
-
- NCDModuleInst_Backend_Dead(o->i);
- }
- static int open_func_getvar (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
- {
- struct open_instance *o = vo;
-
- if (name == NCD_STRING_IS_ERROR) {
- *out = ncd_make_boolean(mem, !o->fh, o->i->params->iparams->string_index);
- return 1;
- }
-
- return 0;
- }
- static void read_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
- {
- struct read_instance *o = vo;
- o->i = i;
-
- // check arguments
- if (!NCDVal_ListRead(params->args, 0)) {
- ModuleLog(o->i, BLOG_ERROR, "wrong arity");
- goto fail0;
- }
-
- // get open instance
- struct open_instance *open_inst = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
-
- // make sure it's not in error
- if (!open_inst->fh) {
- ModuleLog(o->i, BLOG_ERROR, "open instance is in error");
- goto fail0;
- }
-
- // get buffer
- o->buf = NCDBufStore_GetBuf(&open_inst->store);
- if (!o->buf) {
- ModuleLog(o->i, BLOG_ERROR, "NCDBufStore_GetBuf failed");
- goto fail0;
- }
-
- // starting with empty buffer
- char *data = NCDBuf_Data(o->buf);
- size_t buf_size = NCDBufStore_BufSize(&open_inst->store);
- o->length = 0;
-
- while (o->length < buf_size) {
- // read
- size_t readed = fread(data + o->length, 1, buf_size - o->length, open_inst->fh);
- if (readed == 0) {
- break;
- }
- ASSERT(readed <= buf_size - o->length)
-
- // increment length
- o->length += readed;
- }
-
- // if we couldn't read anything due to an error, trigger
- // error in the open instance, and don't go up
- if (o->length == 0 && !feof(open_inst->fh)) {
- ModuleLog(o->i, BLOG_ERROR, "fread failed");
- trigger_error(open_inst);
- return;
- }
-
- // go up
- NCDModuleInst_Backend_Up(i);
- return;
-
- fail0:
- NCDModuleInst_Backend_DeadError(i);
- }
- static void read_func_die (void *vo)
- {
- struct read_instance *o = vo;
-
- // release buffer
- BRefTarget_Deref(NCDBuf_RefTarget(o->buf));
-
- NCDModuleInst_Backend_Dead(o->i);
- }
- static int read_func_getvar (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
- {
- struct read_instance *o = vo;
-
- if (name == NCD_STRING_EMPTY) {
- *out = NCDVal_NewExternalString(mem, NCDBuf_Data(o->buf), o->length, NCDBuf_RefTarget(o->buf));
- return 1;
- }
-
- if (name == NCD_STRING_NOT_EOF) {
- *out = ncd_make_boolean(mem, (o->length != 0), o->i->params->iparams->string_index);
- return 1;
- }
-
- return 0;
- }
- static void write_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
- {
- // check arguments
- NCDValRef data_arg;
- if (!NCDVal_ListRead(params->args, 1, &data_arg)) {
- ModuleLog(i, BLOG_ERROR, "wrong arity");
- goto fail0;
- }
- if (!NCDVal_IsString(data_arg)) {
- ModuleLog(i, BLOG_ERROR, "wrong type");
- goto fail0;
- }
-
- // get open instance
- struct open_instance *open_inst = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
-
- // make sure it's not in error
- if (!open_inst->fh) {
- ModuleLog(i, BLOG_ERROR, "open instance is in error");
- goto fail0;
- }
-
- // write all the data
- MemRef data_mr = NCDVal_StringMemRef(data_arg);
- size_t pos = 0;
- while (pos < data_mr.len) {
- size_t written = fwrite(data_mr.ptr + pos, 1, data_mr.len - pos, open_inst->fh);
- if (written == 0) {
- ModuleLog(i, BLOG_ERROR, "fwrite failed");
- trigger_error(open_inst);
- return;
- }
- ASSERT(written <= data_mr.len - pos)
- pos += written;
- }
-
- // go up
- NCDModuleInst_Backend_Up(i);
- return;
-
- fail0:
- NCDModuleInst_Backend_DeadError(i);
- }
- static void seek_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
- {
- // check arguments
- NCDValRef position_arg;
- NCDValRef whence_arg;
- if (!NCDVal_ListRead(params->args, 2, &position_arg, &whence_arg)) {
- ModuleLog(i, BLOG_ERROR, "wrong arity");
- goto fail0;
- }
- if (!NCDVal_IsString(position_arg) || !NCDVal_IsString(whence_arg)) {
- ModuleLog(i, BLOG_ERROR, "wrong type");
- goto fail0;
- }
-
- // parse position
- int position_sign;
- uintmax_t position_mag;
- if (!parse_signmag_integer(NCDVal_StringMemRef(position_arg), &position_sign, &position_mag)) {
- ModuleLog(i, BLOG_ERROR, "wrong position");
- goto fail0;
- }
-
- // parse whence
- int whence;
- if (NCDVal_StringEquals(whence_arg, "set")) {
- whence = SEEK_SET;
- }
- else if (NCDVal_StringEquals(whence_arg, "cur")) {
- whence = SEEK_CUR;
- }
- else if (NCDVal_StringEquals(whence_arg, "end")) {
- whence = SEEK_END;
- }
- else {
- ModuleLog(i, BLOG_ERROR, "wrong whence");
- goto fail0;
- }
-
- // determine min/max values of off_t (non-portable hack)
- off_t off_t_min = (sizeof(off_t) == 8 ? INT64_MIN : INT32_MIN);
- off_t off_t_max = (sizeof(off_t) == 8 ? INT64_MAX : INT32_MAX);
-
- // compute position as off_t
- off_t position;
- if (position_sign < 0 && position_mag > 0) {
- if (position_mag - 1 > -(off_t_min + 1)) {
- ModuleLog(i, BLOG_ERROR, "position underflow");
- goto fail0;
- }
- position = -(off_t)(position_mag - 1) - 1;
- } else {
- if (position_mag > off_t_max) {
- ModuleLog(i, BLOG_ERROR, "position overflow");
- goto fail0;
- }
- position = position_mag;
- }
-
- // get open instance
- struct open_instance *open_inst = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
-
- // make sure it's not in error
- if (!open_inst->fh) {
- ModuleLog(i, BLOG_ERROR, "open instance is in error");
- goto fail0;
- }
-
- // seek
- if (fseeko(open_inst->fh, position, whence) < 0) {
- ModuleLog(i, BLOG_ERROR, "fseeko failed");
- trigger_error(open_inst);
- return;
- }
-
- // go up
- NCDModuleInst_Backend_Up(i);
- return;
-
- fail0:
- NCDModuleInst_Backend_DeadError(i);
- }
- static void close_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
- {
- // check arguments
- if (!NCDVal_ListRead(params->args, 0)) {
- ModuleLog(i, BLOG_ERROR, "wrong arity");
- goto fail0;
- }
-
- // get open instance
- struct open_instance *open_inst = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
-
- // make sure it's not in error
- if (!open_inst->fh) {
- ModuleLog(i, BLOG_ERROR, "open instance is in error");
- goto fail0;
- }
-
- // close
- int res = fclose(open_inst->fh);
- open_inst->fh = NULL;
- if (res != 0) {
- ModuleLog(i, BLOG_ERROR, "fclose failed");
- trigger_error(open_inst);
- return;
- }
-
- // go up
- NCDModuleInst_Backend_Up(i);
- return;
-
- fail0:
- NCDModuleInst_Backend_DeadError(i);
- }
- static struct NCDModule modules[] = {
- {
- .type = "file_open",
- .func_new2 = open_func_new,
- .func_die = open_func_die,
- .func_getvar2 = open_func_getvar,
- .alloc_size = sizeof(struct open_instance)
- }, {
- .type = "file_open::read",
- .func_new2 = read_func_new,
- .func_die = read_func_die,
- .func_getvar2 = read_func_getvar,
- .alloc_size = sizeof(struct read_instance)
- }, {
- .type = "file_open::write",
- .func_new2 = write_func_new
- }, {
- .type = "file_open::seek",
- .func_new2 = seek_func_new
- }, {
- .type = "file_open::close",
- .func_new2 = close_func_new
- }, {
- .type = NULL
- }
- };
- const struct NCDModuleGroup ncdmodule_file_open = {
- .modules = modules
- };
|