/* * file.c * vim:tw=80:expandtab * * Copyright (c) 2011 Clemens Lang * All rights reserved. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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. */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "file.h" #include "fileobj.h" #include "registry.h" #include "util.h" /** * Converts a command name into a `reg_file`. * * @param [in] interp Tcl interpreter to check within * @param [in] name name of file to get * @param [out] errPtr description of error if the file can't be found * @return a file, or NULL if one couldn't be found * @see get_object */ static reg_file* get_file(Tcl_Interp* interp, char* name, reg_error* errPtr) { return (reg_file*)get_object(interp, name, "file", file_obj_cmd, errPtr); } /** * Removes the file from the Tcl interpreter. Doesn't actually delete it since * that's the registry's job. This is written to be used as the * `Tcl_CmdDeleteProc` for an file object command. * * @param [in] clientData address of a reg_file to remove */ void delete_file(ClientData clientData) { reg_file* file = (reg_file*)clientData; free(file->proc); file->proc = NULL; /* Not freeing file->key.path here. As long as 'file' exists in reg->open_files, cregistry assumes it's valid and can return it in future. We can generate a new proc in that case but can't figure out the path if it's gone. */ } /** * registry::file open portid path * * Opens a file matching the given parameters. */ static int file_open(Tcl_Interp* interp, int objc, Tcl_Obj* const objv[]) { reg_registry* reg = registry_for(interp, reg_attached); if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "open portid path"); return TCL_ERROR; } else if (reg == NULL) { return TCL_ERROR; } else { char* id = Tcl_GetString(objv[2]); char* path = Tcl_GetString(objv[3]); reg_error error; reg_file* file = reg_file_open(reg, id, path, &error); if (file != NULL) { Tcl_Obj* result; if (file_to_obj(interp, &result, file, NULL, &error)) { Tcl_SetObjResult(interp, result); return TCL_OK; } } return registry_failed(interp, &error); } return TCL_ERROR; } /** * registry::file close file * * Closes a file. It will remain in the registry. */ static int file_close(Tcl_Interp* interp, int objc, Tcl_Obj* const objv[]) { if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "close file"); return TCL_ERROR; } else { reg_error error; char* proc = Tcl_GetString(objv[2]); reg_file* file = get_file(interp, proc, &error); if (file == NULL) { return registry_failed(interp, &error); } else { Tcl_DeleteCommand(interp, proc); return TCL_OK; } } } typedef struct { char* name; reg_strategy strategy; } strategy_type; static strategy_type strategies[] = { { "-exact", reg_strategy_exact }, { "-glob", reg_strategy_glob }, { "-regexp", reg_strategy_regexp }, { "-null", reg_strategy_null }, { "--", reg_strategy_exact }, { NULL, 0 } }; /* * registry::file search ?key value ...? * * Searches the registry for files for which each key's value is equal to the * given value. To find all files, call `file search` with no key-value pairs. * For each key, can be given an option of -exact, -glob, -regexp or -null to * specify the matching strategy; defaults to exact. */ static int file_search(Tcl_Interp* interp, int objc, Tcl_Obj* const objv[]) { int i, j; reg_registry* reg = registry_for(interp, reg_attached); if (reg == NULL) { return TCL_ERROR; } else { char** keys; char** vals; int* strats; int key_count = 0; reg_file** files; reg_error error; int file_count; for (i = 2; i < objc;) { int index, strat_index, val_length; if (Tcl_GetIndexFromObj(interp, objv[i], file_props, "search key", 0, &index) != TCL_OK) { return TCL_ERROR; } /* we ate the key value */ i++; /* check whether there's a strategy */ if (Tcl_GetString(objv[i])[0] == '-' && Tcl_GetIndexFromObjStruct(interp, objv[i], strategies, sizeof(strategy_type), "option", 0, &strat_index) != TCL_ERROR) { /* this key has a strategy specified, eat the strategy parameter */ i++; if (strategies[strat_index].strategy != reg_strategy_null) { /* this key must also have a value */ if (Tcl_GetStringFromObj(objv[i], &val_length) == NULL || val_length == 0) { Tcl_WrongNumArgs(interp, 2, objv, "search ?key ?options? value ...?"); return TCL_ERROR; } i++; } } else { /* this key must also have a value */ if (Tcl_GetStringFromObj(objv[i], &val_length) == NULL || val_length == 0) { Tcl_WrongNumArgs(interp, 2, objv, "search ?key ?options? value ...?"); return TCL_ERROR; } i++; } key_count++; } keys = malloc(key_count * sizeof(char*)); vals = malloc(key_count * sizeof(char*)); strats = malloc(key_count * sizeof(int)); if (!keys || !vals || !strats) { if (keys) { free(keys); } if (vals) { free(vals); } if (strats) { free(strats); } return TCL_ERROR; } for (i = 2, j = 0; i < objc && j < key_count; j++) { int strat_index; keys[j] = Tcl_GetString(objv[i++]); /* try to get the strategy */ if (Tcl_GetString(objv[i])[0] == '-' && Tcl_GetIndexFromObjStruct(interp, objv[i], strategies, sizeof(strategy_type), "option", 0, &strat_index) != TCL_ERROR) { /* this key has a strategy specified */ i++; strats[j] = strategies[strat_index].strategy; } else { /* use default strategy */ strats[j] = reg_strategy_exact; } if (strats[j] != reg_strategy_null) { vals[j] = Tcl_GetString(objv[i++]); } else { vals[j] = NULL; } } file_count = reg_file_search(reg, keys, vals, strats, key_count, &files, &error); free(keys); free(vals); free(strats); if (file_count >= 0) { int retval; Tcl_Obj* resultObj; Tcl_Obj** objs; if (list_file_to_obj(interp, &objs, files, file_count, &error)){ resultObj = Tcl_NewListObj(file_count, objs); Tcl_SetObjResult(interp, resultObj); free(objs); retval = TCL_OK; } else { retval = registry_failed(interp, &error); } free(files); return retval; } return registry_failed(interp, &error); } } typedef struct { char* name; int (*function)(Tcl_Interp* interp, int objc, Tcl_Obj* const objv[]); } file_cmd_type; static file_cmd_type file_cmds[] = { /* Global commands */ { "open", file_open }, { "close", file_close }, { "search", file_search }, { NULL, NULL } }; /* * registry::file cmd ?arg ...? * * Commands manipulating file entries in the registry. This can be called `registry::file` */ int file_cmd(ClientData clientData UNUSED, Tcl_Interp* interp, int objc, Tcl_Obj* const objv[]) { int cmd_index; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "cmd ?arg ...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObjStruct(interp, objv[1], file_cmds, sizeof(file_cmd_type), "cmd", 0, &cmd_index) == TCL_OK) { file_cmd_type* cmd = &file_cmds[cmd_index]; return cmd->function(interp, objc, objv); } return TCL_ERROR; }