From 715cc5ea3bf01ea99b2516c0d3d48e2a5ccb8174 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 1 Jul 2010 20:54:01 +1000 Subject: [PATCH] New configure option: --enable-nif --- src/Makefile.in | 7 +- src/configure.ac | 9 ++ src/ejabberd_app.erl | 1 + src/ejabberd_c2s.erl | 8 +- src/ejabberd_s2s_in.erl | 4 +- src/ejabberd_s2s_out.erl | 2 +- src/ejabberd_service.erl | 4 +- src/mod_muc/mod_muc_room.erl | 11 +- src/mod_offline_odbc.erl | 3 +- src/web/ejabberd_http_bind.erl | 16 +-- src/xml.c | 230 +++++++++++++++++++++++++++++++++ src/xml.erl | 19 +++ 12 files changed, 290 insertions(+), 24 deletions(-) create mode 100644 src/xml.c diff --git a/src/Makefile.in b/src/Makefile.in index 94fa8b1ac..174084f64 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -55,6 +55,11 @@ ifeq (@full_xml@, true) EFLAGS+=-DFULL_XML_SUPPORT endif +ifeq (@nif@, true) + EFLAGS+=-DNIF + ERLSHLIBS=xml.so +endif + ifeq (@transient_supervisors@, false) EFLAGS+=-DNO_TRANSIENT_SUPERVISORS endif @@ -68,7 +73,7 @@ prefix = @prefix@ exec_prefix = @exec_prefix@ SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @mod_proxy65@ @eldap@ @pam@ @web@ stringprep stun @tls@ @odbc@ @ejabberd_zlib@ -ERLSHLIBS = expat_erl.so +ERLSHLIBS += expat_erl.so ERLBEHAVS = cyrsasl.erl gen_mod.erl p1_fsm.erl SOURCES_ALL = $(wildcard *.erl) SOURCES_MISC = $(ERLBEHAVS) $(DEBUGTOOLS) diff --git a/src/configure.ac b/src/configure.ac index 20bfdf6f1..70a6fb43c 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -94,6 +94,15 @@ AC_ARG_ENABLE(full_xml, esac],[full_xml=false]) AC_SUBST(full_xml) +AC_ARG_ENABLE(nif, +[AC_HELP_STRING([--enable-nif], [replace some functions with C equivalents. You should have Erlang R13B04 or higher (default: no)])], +[case "${enableval}" in + yes) nif=true ;; + no) nif=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-nif) ;; +esac],[nif=false]) +AC_SUBST(nif) + AC_CONFIG_FILES([Makefile $make_mod_irc $make_mod_muc diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl index 7a54fcc37..97ae7fedd 100644 --- a/src/ejabberd_app.erl +++ b/src/ejabberd_app.erl @@ -46,6 +46,7 @@ start(normal, _Args) -> db_init(), sha:start(), stringprep_sup:start_link(), + xml:start(), start(), translate:start(), acl:start(), diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 6238cdae5..d4b68096e 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -627,7 +627,7 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) -> Socket = StateData#state.socket, TLSSocket = (StateData#state.sockmod):starttls( Socket, TLSOpts, - xml:element_to_string( + xml:element_to_binary( {xmlelement, "proceed", [{"xmlns", ?NS_TLS}], []})), fsm_next_state(wait_for_stream, StateData#state{socket = TLSSocket, @@ -650,7 +650,7 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) -> Socket = StateData#state.socket, ZlibSocket = (StateData#state.sockmod):compress( Socket, - xml:element_to_string( + xml:element_to_binary( {xmlelement, "compressed", [{"xmlns", ?NS_COMPRESS}], []})), fsm_next_state(wait_for_stream, @@ -1453,14 +1453,14 @@ change_shaper(StateData, JID) -> (StateData#state.sockmod):change_shaper(StateData#state.socket, Shaper). send_text(StateData, Text) -> - ?DEBUG("Send XML on stream = ~p", [lists:flatten(Text)]), + ?DEBUG("Send XML on stream = ~p", [Text]), (StateData#state.sockmod):send(StateData#state.socket, Text). send_element(StateData, El) when StateData#state.xml_socket -> (StateData#state.sockmod):send_xml(StateData#state.socket, {xmlstreamelement, El}); send_element(StateData, El) -> - send_text(StateData, xml:element_to_string(El)). + send_text(StateData, xml:element_to_binary(El)). send_header(StateData, Server, Version, Lang) when StateData#state.xml_socket -> diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index 9b378338c..179dc08f1 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -263,7 +263,7 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) -> TLSOpts = StateData#state.tls_options, TLSSocket = (StateData#state.sockmod):starttls( Socket, TLSOpts, - xml:element_to_string( + xml:element_to_binary( {xmlelement, "proceed", [{"xmlns", ?NS_TLS}], []})), {next_state, wait_for_stream, StateData#state{socket = TLSSocket, @@ -618,7 +618,7 @@ send_text(StateData, Text) -> (StateData#state.sockmod):send(StateData#state.socket, Text). send_element(StateData, El) -> - send_text(StateData, xml:element_to_string(El)). + send_text(StateData, xml:element_to_binary(El)). change_shaper(StateData, Host, JID) -> diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl index 69cbfdd22..01ab8d8aa 100644 --- a/src/ejabberd_s2s_out.erl +++ b/src/ejabberd_s2s_out.erl @@ -892,7 +892,7 @@ send_text(StateData, Text) -> ejabberd_socket:send(StateData#state.socket, Text). send_element(StateData, El) -> - send_text(StateData, xml:element_to_string(El)). + send_text(StateData, xml:element_to_binary(El)). send_queue(StateData, Q) -> case queue:out(Q) of diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl index 44bca0b9e..bd07bfea8 100644 --- a/src/ejabberd_service.erl +++ b/src/ejabberd_service.erl @@ -347,7 +347,7 @@ handle_info({route, From, To, Packet}, StateName, StateData) -> Attrs2 = jlib:replace_from_to_attrs(jlib:jid_to_string(From), jlib:jid_to_string(To), Attrs), - Text = xml:element_to_string({xmlelement, Name, Attrs2, Els}), + Text = xml:element_to_binary({xmlelement, Name, Attrs2, Els}), send_text(StateData, Text); deny -> Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED), @@ -391,7 +391,7 @@ send_text(StateData, Text) -> (StateData#state.sockmod):send(StateData#state.socket, Text). send_element(StateData, El) -> - send_text(StateData, xml:element_to_string(El)). + send_text(StateData, xml:element_to_binary(El)). new_id() -> randoms:get_string(). diff --git a/src/mod_muc/mod_muc_room.erl b/src/mod_muc/mod_muc_room.erl index 8798ace2d..b0a07178b 100644 --- a/src/mod_muc/mod_muc_room.erl +++ b/src/mod_muc/mod_muc_room.erl @@ -162,7 +162,7 @@ normal_state({route, From, "", trunc(gen_mod:get_module_opt( StateData#state.server_host, mod_muc, min_message_interval, 0) * 1000000), - Size = iolist_size(xml:element_to_string(Packet)), + Size = element_size(Packet), {MessageShaper, MessageShaperInterval} = shaper:update(Activity#activity.message_shaper, Size), if @@ -1394,7 +1394,7 @@ prepare_room_queue(StateData) -> {{value, {message, From}}, _RoomQueue} -> Activity = get_user_activity(From, StateData), Packet = Activity#activity.message, - Size = iolist_size(xml:element_to_string(Packet)), + Size = element_size(Packet), {RoomShaper, RoomShaperInterval} = shaper:update(StateData#state.room_shaper, Size), erlang:send_after( @@ -1405,7 +1405,7 @@ prepare_room_queue(StateData) -> {{value, {presence, From}}, _RoomQueue} -> Activity = get_user_activity(From, StateData), {_Nick, Packet} = Activity#activity.presence, - Size = iolist_size(xml:element_to_string(Packet)), + Size = element_size(Packet), {RoomShaper, RoomShaperInterval} = shaper:update(StateData#state.room_shaper, Size), erlang:send_after( @@ -2068,7 +2068,7 @@ add_message_to_history(FromNick, FromJID, Packet, StateData) -> jlib:jid_replace_resource(StateData#state.jid, FromNick), StateData#state.jid, TSPacket), - Size = iolist_size(xml:element_to_string(SPacket)), + Size = element_size(SPacket), Q1 = lqueue_in({FromNick, TSPacket, HaveSubject, TimeStamp, Size}, StateData#state.history), add_to_log(text, {FromNick, Packet}, StateData), @@ -3582,3 +3582,6 @@ tab_count_user(JID) -> _ -> 0 end. + +element_size(El) -> + size(xml:element_to_binary(El)). diff --git a/src/mod_offline_odbc.erl b/src/mod_offline_odbc.erl index c89beda4e..063ea709e 100644 --- a/src/mod_offline_odbc.erl +++ b/src/mod_offline_odbc.erl @@ -123,8 +123,7 @@ loop(Host, AccessMaxOfflineMsgs) -> M#offline_msg.timestamp))]}, XML = ejabberd_odbc:escape( - lists:flatten( - xml:element_to_string(Packet))), + xml:element_to_binary(Packet)), odbc_queries:add_spool_sql(Username, XML) end, Msgs), case catch odbc_queries:add_spool(Host, Query) of diff --git a/src/web/ejabberd_http_bind.erl b/src/web/ejabberd_http_bind.erl index 80ddaf3ea..27ea3c089 100644 --- a/src/web/ejabberd_http_bind.erl +++ b/src/web/ejabberd_http_bind.erl @@ -790,7 +790,7 @@ handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) -> ?DEBUG("Trafic Shaper: Delaying request ~p", [Rid]), timer:sleep(Pause), %{200, ?HEADER, - % xml:element_to_string( + % xml:element_to_binary( % {xmlelement, "body", % [{"xmlns", ?NS_HTTP_BIND}, % {"type", "error"}], []})}; @@ -827,21 +827,21 @@ handle_http_put_error(Reason, #http_bind{pid=FsmRef, version=Version}) case Reason of not_exists -> {200, ?HEADER, - xml:element_to_string( + xml:element_to_binary( {xmlelement, "body", [{"xmlns", ?NS_HTTP_BIND}, {"type", "terminate"}, {"condition", "item-not-found"}], []})}; bad_key -> {200, ?HEADER, - xml:element_to_string( + xml:element_to_binary( {xmlelement, "body", [{"xmlns", ?NS_HTTP_BIND}, {"type", "terminate"}, {"condition", "item-not-found"}], []})}; polling_too_frequently -> {200, ?HEADER, - xml:element_to_string( + xml:element_to_binary( {xmlelement, "body", [{"xmlns", ?NS_HTTP_BIND}, {"type", "terminate"}, @@ -986,7 +986,7 @@ prepare_outpacket_response(#http_bind{id=Sid, wait=Wait, MaxInactivity = get_max_inactivity(To, ?MAX_INACTIVITY), MaxPause = get_max_pause(To), {200, ?HEADER, - xml:element_to_string( + xml:element_to_binary( {xmlelement,"body", [{"xmlns", ?NS_HTTP_BIND}, @@ -1041,7 +1041,7 @@ send_outpacket(#http_bind{pid = FsmRef}, OutPacket) -> true -> TypedEls = [check_default_xmlns(OEl) || {xmlstreamelement, OEl} <- OutPacket], - Body = xml:element_to_string( + Body = xml:element_to_binary( {xmlelement,"body", [{"xmlns", ?NS_HTTP_BIND}], @@ -1075,7 +1075,7 @@ send_outpacket(#http_bind{pid = FsmRef}, OutPacket) -> StreamTail] end, {200, ?HEADER, - xml:element_to_string( + xml:element_to_binary( {xmlelement,"body", [{"xmlns", ?NS_HTTP_BIND}], @@ -1191,7 +1191,7 @@ set_inactivity_timer(_Pause, MaxInactivity) -> elements_to_string([]) -> []; elements_to_string([El | Els]) -> - xml:element_to_string(El) ++ elements_to_string(Els). + [xml:element_to_binary(El)|elements_to_string(Els)]. %% @spec (To, Default::integer()) -> integer() %% where To = [] | {Host::string(), Version::string()} diff --git a/src/xml.c b/src/xml.c new file mode 100644 index 000000000..d1b2b7f44 --- /dev/null +++ b/src/xml.c @@ -0,0 +1,230 @@ +#include +#include +#include + +struct buf { + int limit; + int len; + unsigned char *b; +}; + +static int make_element(ErlNifEnv* env, struct buf *rbuf, ERL_NIF_TERM el); + +static struct buf *init_buf(ErlNifEnv* env) +{ + struct buf *rbuf = enif_alloc(env, sizeof(struct buf)); + rbuf->limit = 1024; + rbuf->len = 0; + rbuf->b = enif_alloc(env, rbuf->limit); + return rbuf; +} + +static void destroy_buf(ErlNifEnv* env, struct buf *rbuf) +{ + if (rbuf) { + if (rbuf->b) { + enif_free(env, rbuf->b); + }; + enif_free(env, rbuf); + }; +} + +inline void resize_buf(ErlNifEnv* env, struct buf *rbuf, int len_to_add) +{ + int new_len = rbuf->len + len_to_add; + + if (new_len >= rbuf->limit) { + rbuf->limit = ((new_len / 1024) + 1) * 1024; + rbuf->b = enif_realloc(env, rbuf->b, rbuf->limit); + }; +} + +static void buf_add_char(ErlNifEnv* env, struct buf *rbuf, unsigned char c) +{ + resize_buf(env, rbuf, 1); + (rbuf->b)[rbuf->len] = c; + rbuf->len += 1; +} + +static void buf_add_str(ErlNifEnv* env, struct buf *rbuf, char *data, int len) +{ + resize_buf(env, rbuf, len); + memcpy(rbuf->b + rbuf->len, data, len); + rbuf->len += len; +} + +inline void crypt(ErlNifEnv* env, struct buf *rbuf, unsigned char *data, int len) +{ + int i; + + for (i = 0; i < len; i++) { + switch (data[i]) { + case '&': + buf_add_str(env, rbuf, "&", 5); + break; + case '<': + buf_add_str(env, rbuf, "<", 4); + break; + case '>': + buf_add_str(env, rbuf, ">", 4); + break; + case '"': + buf_add_str(env, rbuf, """, 6); + break; + case '\'': + buf_add_str(env, rbuf, "'", 6); + break; + default: + buf_add_char(env, rbuf, data[i]); + break; + }; + }; +} + +static int make_elements(ErlNifEnv* env, struct buf *rbuf, ERL_NIF_TERM els) +{ + ERL_NIF_TERM head, tail; + int ret = 0; + + while (enif_get_list_cell(env, els, &head, &tail)) { + ret = make_element(env, rbuf, head); + if (ret) { + els = tail; + } else { + break; + }; + }; + + return ret; +} + +static int make_attrs(ErlNifEnv* env, struct buf *rbuf, ERL_NIF_TERM attrs) +{ + ErlNifBinary name, data; + ERL_NIF_TERM head, tail; + const ERL_NIF_TERM *tuple; + int arity, ret = 1; + + while (enif_get_list_cell(env, attrs, &head, &tail)) { + if (enif_get_tuple(env, head, &arity, &tuple)) { + if (arity == 2) { + if (enif_inspect_iolist_as_binary(env, tuple[0], &name) && + enif_inspect_iolist_as_binary(env, tuple[1], &data)) { + buf_add_char(env, rbuf, ' '); + buf_add_str(env, rbuf, (char *)name.data, name.size); + buf_add_str(env, rbuf, "='", 2); + crypt(env, rbuf, data.data, data.size); + buf_add_char(env, rbuf, '\''); + attrs = tail; + } else { + ret = 0; + break; + }; + } else { + ret = 0; + break; + }; + } else { + ret = 0; + break; + }; + }; + + return ret; +} + +static int make_element(ErlNifEnv* env, struct buf *rbuf, ERL_NIF_TERM el) +{ + ErlNifBinary cdata, name; + const ERL_NIF_TERM *tuple; + int arity, ret = 0; + + if (enif_get_tuple(env, el, &arity, &tuple)) { + if (arity == 2) { + if (!enif_compare(env, tuple[0], enif_make_atom(env, "xmlcdata"))) { + if (enif_inspect_iolist_as_binary(env, tuple[1], &cdata)) { + crypt(env, rbuf, cdata.data, cdata.size); + ret = 1; + }; + }; + }; + if (arity == 4) { + if (!enif_compare(env, tuple[0], enif_make_atom(env, "xmlelement"))) { + if (enif_inspect_iolist_as_binary(env, tuple[1], &name)) { + buf_add_char(env, rbuf, '<'); + buf_add_str(env, rbuf, (char *)name.data, name.size); + ret = make_attrs(env, rbuf, tuple[2]); + if (ret) { + if (enif_is_empty_list(env, tuple[3])) { + buf_add_str(env, rbuf, "/>", 2); + } else { + buf_add_char(env, rbuf, '>'); + ret = make_elements(env, rbuf, tuple[3]); + if (ret) { + buf_add_str(env, rbuf, "'); + }; + }; + }; + }; + }; + }; + }; + + return ret; +} + +static ERL_NIF_TERM element_to(ErlNifEnv* env, int argc, + const ERL_NIF_TERM argv[], + int as_string) +{ + ErlNifBinary output; + ERL_NIF_TERM result; + struct buf *rbuf; + + if (argc == 1) { + rbuf = init_buf(env); + if (make_element(env, rbuf, argv[0])) { + if (as_string) { + (rbuf->b)[rbuf->len] = 0; + result = enif_make_string(env, (char *) rbuf->b, ERL_NIF_LATIN1); + destroy_buf(env, rbuf); + return result; + } else { + if (enif_alloc_binary(env, rbuf->len, &output)) { + memcpy(output.data, rbuf->b, rbuf->len); + result = enif_make_binary(env, &output); + destroy_buf(env, rbuf); + return result; + }; + }; + }; + destroy_buf(env, rbuf); + }; + + return enif_make_badarg(env); +} + +/* static ERL_NIF_TERM element_to_string(ErlNifEnv* env, int argc, */ +/* const ERL_NIF_TERM argv[]) */ +/* { */ +/* return element_to(env, argc, argv, 1); */ +/* } */ + +static ERL_NIF_TERM element_to_binary(ErlNifEnv* env, int argc, + const ERL_NIF_TERM argv[]) +{ + return element_to(env, argc, argv, 0); +} + +static ErlNifFunc nif_funcs[] = + { + /* Stupid Erlang bug with enif_make_string() is fixed + in R14A only (OTP-8685), so we can't use + element_to_string yet.*/ + /* {"element_to_string", 1, element_to_string}, */ + {"element_to_binary", 1, element_to_binary} + }; + +ERL_NIF_INIT(xml, nif_funcs, NULL, NULL, NULL, NULL) diff --git a/src/xml.erl b/src/xml.erl index 9a9a7f833..22746b10a 100644 --- a/src/xml.erl +++ b/src/xml.erl @@ -37,8 +37,11 @@ get_subtag/2, get_subtag_cdata/2, append_subtags/2, get_path_s/2, + start/0, replace_tag_attr/3]). +-include("ejabberd.hrl"). + %% Select at compile time how to escape characters in binary text %% nodes. %% Can be choosen with ./configure --enable-full-xml @@ -48,6 +51,22 @@ -define(ESCAPE_BINARY(CData), crypt(CData)). -endif. +%% Replace element_to_binary/1 with NIF +%% Can be choosen with ./configure --enable-nif +-ifdef(NIF). +start() -> + SOPath = filename:join(ejabberd:get_so_path(), "xml"), + case catch erlang:load_nif(SOPath, 0) of + ok -> + ok; + Err -> + ?WARNING_MSG("unable to load xml NIF: ~p", [Err]) + end. +-else. +start() -> + ok. +-endif. + element_to_binary(El) -> iolist_to_binary(element_to_string(El)).