Merge #802 'implement VimL libcall with os_libcall'

This commit is contained in:
Justin M. Keyes
2014-06-28 14:44:34 -04:00
7 changed files with 137 additions and 255 deletions

View File

@@ -72,6 +72,7 @@
#include "nvim/os/channel.h"
#include "nvim/api/private/helpers.h"
#include "nvim/os/msgpack_rpc_helpers.h"
#include "nvim/os/dl.h"
#define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */
@@ -9673,9 +9674,7 @@ static void f_has(typval_T *argvars, typval_T *rettv)
"jumplist",
"keymap",
"langmap",
#ifdef FEAT_LIBCALL
"libcall",
#endif
"linebreak",
"lispindent",
"listcmds",
@@ -10623,42 +10622,49 @@ static void f_len(typval_T *argvars, typval_T *rettv)
}
}
static void libcall_common(typval_T *argvars, typval_T *rettv, int type)
static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type)
{
#ifdef FEAT_LIBCALL
char_u *string_in;
char_u **string_result;
int nr_result;
#endif
rettv->v_type = type;
if (type != VAR_NUMBER)
rettv->v_type = out_type;
if (out_type != VAR_NUMBER) {
rettv->vval.v_string = NULL;
if (check_restricted() || check_secure())
return;
#ifdef FEAT_LIBCALL
/* The first two args must be strings, otherwise its meaningless */
if (argvars[0].v_type == VAR_STRING && argvars[1].v_type == VAR_STRING) {
string_in = NULL;
if (argvars[2].v_type == VAR_STRING)
string_in = argvars[2].vval.v_string;
if (type == VAR_NUMBER)
string_result = NULL;
else
string_result = &rettv->vval.v_string;
if (mch_libcall(argvars[0].vval.v_string,
argvars[1].vval.v_string,
string_in,
argvars[2].vval.v_number,
string_result,
&nr_result) == OK
&& type == VAR_NUMBER)
rettv->vval.v_number = nr_result;
}
#endif
if (check_restricted() || check_secure()) {
return;
}
// The first two args (libname and funcname) must be strings
if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) {
return;
}
const char *libname = (char *) argvars[0].vval.v_string;
const char *funcname = (char *) argvars[1].vval.v_string;
int in_type = argvars[2].v_type;
// input variables
char *str_in = (in_type == VAR_STRING)
? (char *) argvars[2].vval.v_string : NULL;
int64_t int_in = argvars[2].vval.v_number;
// output variables
char **str_out = (out_type == VAR_STRING)
? (char **) &rettv->vval.v_string : NULL;
int64_t int_out = 0;
bool success = os_libcall(libname, funcname,
str_in, int_in,
str_out, &int_out);
if (!success) {
EMSG2(_(e_libcall), funcname);
return;
}
if (out_type == VAR_NUMBER) {
rettv->vval.v_number = (int) int_out;
}
}
/*

View File

@@ -599,19 +599,6 @@ EXTERN int orig_line_count INIT(= 0); /* Line count when "gR" started */
EXTERN int vr_lines_changed INIT(= 0); /* #Lines changed by "gR" so far */
/*
* Stuff for setjmp() and longjmp().
* Used to protect areas where we could crash.
*/
EXTERN JMP_BUF lc_jump_env; /* argument to SETJMP() */
# ifdef SIGHASARG
/* volatile because it is used in signal handlers. */
EXTERN volatile int lc_signal; /* caught signal number, 0 when no was signal
caught; used for mch_libcall() */
# endif
/* volatile because it is used in signal handler deathtrap(). */
EXTERN volatile int lc_active INIT(= FALSE); /* TRUE when lc_jump_env is valid. */
/*
* These flags are set based upon 'fileencoding'.
* Note that "enc_utf8" is also set for "unicode", because the characters are
@@ -1019,9 +1006,7 @@ EXTERN char_u e_isadir2[] INIT(= N_("E17: \"%s\" is a directory"));
EXTERN char_u e_invjob[] INIT(= N_("E900: Invalid job id"));
EXTERN char_u e_jobtblfull[] INIT(= N_("E901: Job table is full"));
EXTERN char_u e_jobexe[] INIT(= N_("E902: \"%s\" is not an executable"));
#ifdef FEAT_LIBCALL
EXTERN char_u e_libcall[] INIT(= N_("E364: Library call failed for \"%s()\""));
#endif
EXTERN char_u e_markinval[] INIT(= N_("E19: Mark has invalid line number"));
EXTERN char_u e_marknotset[] INIT(= N_("E20: Mark not set"));
EXTERN char_u e_modifiable[] INIT(= N_(

86
src/nvim/os/dl.c Normal file
View File

@@ -0,0 +1,86 @@
/// Functions for using external native libraries
#include <stdint.h>
#include <uv.h>
#include "nvim/os/os.h"
#include "nvim/memory.h"
#include "nvim/message.h"
/// possible function prototypes that can be called by os_libcall()
/// int -> int
/// int -> string
/// string -> string
/// string -> int
typedef void (*gen_fn)();
typedef const char *(*str_str_fn)(const char *str);
typedef int64_t (*str_int_fn)(const char *str);
typedef const char *(*int_str_fn)(int64_t i);
typedef int64_t (*int_int_fn)(int64_t i);
/// os_libcall - call a function in a dynamic loadable library
///
/// an example of calling a function that takes a string and returns an int:
///
/// int64_t int_out = 0;
/// os_libcall("mylib.so", "somefn", "string-argument", 0, NULL, &int_out);
///
/// @param libname the name of the library to load (e.g.: libsomething.so)
/// @param funcname the name of the library function (e.g.: myfunc)
/// @param argv the input string, NULL when using `argi`
/// @param argi the input integer, not used when using `argv` != NULL
/// @param[out] str_out an allocated output string, caller must free if
/// not NULL. NULL when using `int_out`.
/// @param[out] int_out the output integer param
/// @return true on success, false on failure
bool os_libcall(const char *libname,
const char *funcname,
const char *argv,
int64_t argi,
char **str_out,
int64_t *int_out)
{
if (!libname || !funcname) {
return false;
}
uv_lib_t lib;
// open the dynamic loadable library
if (uv_dlopen(libname, &lib)) {
EMSG2(_("dlerror = \"%s\""), uv_dlerror(&lib));
return false;
}
// find and load the requested function in the library
gen_fn fn;
if (uv_dlsym(&lib, funcname, (void **) &fn)) {
EMSG2(_("dlerror = \"%s\""), uv_dlerror(&lib));
uv_dlclose(&lib);
return false;
}
// call the library and save the result
// TODO(aktau): catch signals and use jmp (if available) to handle
// exceptions. jmp's on UNIX seem to interact trickily with signals as
// well. So for now we only support those libraries that are well-behaved.
if (str_out) {
str_str_fn sfn = (str_str_fn) fn;
int_str_fn ifn = (int_str_fn) fn;
const char *res = argv ? sfn(argv) : ifn(argi);
// assume that ptr values of NULL, 1 or -1 are illegal
*str_out = (res && (intptr_t) res != 1 && (intptr_t) res != -1)
? xstrdup(res) : NULL;
} else {
str_int_fn sfn = (str_int_fn) fn;
int_int_fn ifn = (int_int_fn) fn;
*int_out = argv ? sfn(argv) : ifn(argi);
}
// free the library
uv_dlclose(&lib);
return true;
}

10
src/nvim/os/dl.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef NVIM_OS_DL_H
#define NVIM_OS_DL_H
#include <stdbool.h>
#include <stdint.h>
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/dl.h.generated.h"
#endif
#endif // NVIM_OS_DL_H

View File

@@ -70,11 +70,6 @@
# include <termios.h>
#endif
/* shared library access */
#if defined(HAVE_DLFCN_H) && defined(USE_DLOPEN)
# include <dlfcn.h>
#endif
#ifdef HAVE_SELINUX
# include <selinux/selinux.h>
static int selinux_enabled = -1;
@@ -101,36 +96,6 @@ void mch_write(char_u *s, int len)
os_microdelay(p_wd, false);
}
/*
* A simplistic version of setjmp() that only allows one level of using.
* Don't call twice before calling mch_endjmp()!.
* Usage:
* mch_startjmp();
* if (SETJMP(lc_jump_env) != 0)
* {
* mch_didjmp();
* EMSG("crash!");
* }
* else
* {
* do_the_work;
* mch_endjmp();
* }
* Note: Can't move SETJMP() here, because a function calling setjmp() must
* not return before the saved environment is used.
* Returns OK for normal return, FAIL when the protected code caused a
* problem and LONGJMP() was used.
*/
void mch_startjmp()
{
lc_active = TRUE;
}
void mch_endjmp()
{
lc_active = FALSE;
}
/*
* If the machine has job control, use it to suspend the program,
* otherwise fake it by starting a new shell.
@@ -1490,158 +1455,3 @@ static int have_dollars(int num, char_u **file)
return TRUE;
return FALSE;
}
#if defined(FEAT_LIBCALL) || defined(PROTO)
typedef char_u * (*STRPROCSTR)(char_u *);
typedef char_u * (*INTPROCSTR)(int);
typedef int (*STRPROCINT)(char_u *);
typedef int (*INTPROCINT)(int);
/*
* Call a DLL routine which takes either a string or int param
* and returns an allocated string.
*/
int mch_libcall(char_u *libname,
char_u *funcname,
char_u *argstring, /* NULL when using an argint */
int argint,
char_u **string_result, /* NULL when using number_result */
int *number_result)
{
# if defined(USE_DLOPEN)
void *hinstLib;
char *dlerr = NULL;
# else
shl_t hinstLib;
# endif
STRPROCSTR ProcAdd;
INTPROCSTR ProcAddI;
char_u *retval_str = NULL;
int retval_int = 0;
int success = FALSE;
/*
* Get a handle to the DLL module.
*/
# if defined(USE_DLOPEN)
/* First clear any error, it's not cleared by the dlopen() call. */
(void)dlerror();
hinstLib = dlopen((char *)libname, RTLD_LAZY
# ifdef RTLD_LOCAL
| RTLD_LOCAL
# endif
);
if (hinstLib == NULL) {
/* "dlerr" must be used before dlclose() */
dlerr = (char *)dlerror();
if (dlerr != NULL)
EMSG2(_("dlerror = \"%s\""), dlerr);
}
# else
hinstLib = shl_load((const char*)libname, BIND_IMMEDIATE|BIND_VERBOSE, 0L);
# endif
/* If the handle is valid, try to get the function address. */
if (hinstLib != NULL) {
/*
* Catch a crash when calling the library function. For example when
* using a number where a string pointer is expected.
*/
mch_startjmp();
if (SETJMP(lc_jump_env) != 0) {
success = FALSE;
# if defined(USE_DLOPEN)
dlerr = NULL;
# endif
} else
{
retval_str = NULL;
retval_int = 0;
if (argstring != NULL) {
# if defined(USE_DLOPEN)
ProcAdd = (STRPROCSTR)dlsym(hinstLib, (const char *)funcname);
dlerr = (char *)dlerror();
# else
if (shl_findsym(&hinstLib, (const char *)funcname,
TYPE_PROCEDURE, (void *)&ProcAdd) < 0)
ProcAdd = NULL;
# endif
if ((success = (ProcAdd != NULL
# if defined(USE_DLOPEN)
&& dlerr == NULL
# endif
))) {
if (string_result == NULL)
retval_int = ((STRPROCINT)ProcAdd)(argstring);
else
retval_str = (ProcAdd)(argstring);
}
} else {
# if defined(USE_DLOPEN)
ProcAddI = (INTPROCSTR)dlsym(hinstLib, (const char *)funcname);
dlerr = (char *)dlerror();
# else
if (shl_findsym(&hinstLib, (const char *)funcname,
TYPE_PROCEDURE, (void *)&ProcAddI) < 0)
ProcAddI = NULL;
# endif
if ((success = (ProcAddI != NULL
# if defined(USE_DLOPEN)
&& dlerr == NULL
# endif
))) {
if (string_result == NULL)
retval_int = ((INTPROCINT)ProcAddI)(argint);
else
retval_str = (ProcAddI)(argint);
}
}
/* Save the string before we free the library. */
/* Assume that a "1" or "-1" result is an illegal pointer. */
if (string_result == NULL)
*number_result = retval_int;
else if (retval_str != NULL
&& retval_str != (char_u *)1
&& retval_str != (char_u *)-1)
*string_result = vim_strsave(retval_str);
}
mch_endjmp();
# ifdef SIGHASARG
if (lc_signal != 0) {
int i;
/* try to find the name of this signal */
for (i = 0; signal_info[i].sig != -1; i++)
if (lc_signal == signal_info[i].sig)
break;
EMSG2("E368: got SIG%s in libcall()", signal_info[i].name);
}
# endif
# if defined(USE_DLOPEN)
/* "dlerr" must be used before dlclose() */
if (dlerr != NULL)
EMSG2(_("dlerror = \"%s\""), dlerr);
/* Free the DLL module. */
(void)dlclose(hinstLib);
# else
(void)shl_unload(hinstLib);
# endif
}
if (!success) {
EMSG2(_(e_libcall), funcname);
return FAIL;
}
return OK;
}
#endif

View File

@@ -262,17 +262,6 @@
# include <strings.h>
#endif
#include <setjmp.h>
#ifdef HAVE_SIGSETJMP
# define JMP_BUF sigjmp_buf
# define SETJMP(x) sigsetjmp((x), 1)
# define LONGJMP siglongjmp
#else
# define JMP_BUF jmp_buf
# define SETJMP(x) setjmp(x)
# define LONGJMP longjmp
#endif
#define HAVE_DUP /* have dup() */
/* We have three kinds of ACL support. */

View File

@@ -98,11 +98,7 @@ static char *(features[]) = {
"+jumplist",
"+keymap",
"+langmap",
#ifdef FEAT_LIBCALL
"+libcall",
#else // ifdef FEAT_LIBCALL
"-libcall",
#endif // ifdef FEAT_LIBCALL
"+linebreak",
"+lispindent",
"+listcmds",