From 8b0c007ad990aa27d9868da49215fd1076ac77cc Mon Sep 17 00:00:00 2001 From: kpfleming Date: Mon, 21 Aug 2006 02:11:39 +0000 Subject: merge new_loader_completion branch, including (at least): - restructured build tree and makefiles to eliminate recursion problems - support for embedded modules - support for static builds - simpler cross-compilation support - simpler module/loader interface (no exported symbols) git-svn-id: http://svn.digium.com/svn/asterisk/trunk@40722 f38db490-d61c-443f-a65b-d21fe96a405b --- main/Makefile | 138 + main/abstract_jb.c | 784 +++++ main/acl.c | 425 +++ main/aescrypt.c | 317 ++ main/aeskey.c | 469 +++ main/aesopt.h | 1029 ++++++ main/aestab.c | 232 ++ main/alaw.c | 101 + main/app.c | 1328 ++++++++ main/ast_expr2.c | 2871 +++++++++++++++++ main/ast_expr2.fl | 405 +++ main/ast_expr2.h | 120 + main/ast_expr2.y | 1045 ++++++ main/ast_expr2f.c | 3299 +++++++++++++++++++ main/asterisk.c | 2727 ++++++++++++++++ main/astmm.c | 443 +++ main/autoservice.c | 154 + main/buildinfo.c | 33 + main/callerid.c | 1096 +++++++ main/cdr.c | 1175 +++++++ main/channel.c | 4516 ++++++++++++++++++++++++++ main/chanvars.c | 86 + main/cli.c | 1390 ++++++++ main/coef_in.h | 13 + main/coef_out.h | 4 + main/config.c | 1235 +++++++ main/cryptostub.c | 95 + main/db.c | 593 ++++ main/db1-ast/Makefile | 73 + main/db1-ast/btree/bt_close.c | 182 ++ main/db1-ast/btree/bt_conv.c | 221 ++ main/db1-ast/btree/bt_debug.c | 329 ++ main/db1-ast/btree/bt_delete.c | 657 ++++ main/db1-ast/btree/bt_get.c | 105 + main/db1-ast/btree/bt_open.c | 458 +++ main/db1-ast/btree/bt_overflow.c | 228 ++ main/db1-ast/btree/bt_page.c | 100 + main/db1-ast/btree/bt_put.c | 321 ++ main/db1-ast/btree/bt_search.c | 213 ++ main/db1-ast/btree/bt_seq.c | 460 +++ main/db1-ast/btree/bt_split.c | 829 +++++ main/db1-ast/btree/bt_utils.c | 260 ++ main/db1-ast/btree/btree.h | 391 +++ main/db1-ast/btree/extern.h | 70 + main/db1-ast/db/db.c | 103 + main/db1-ast/hash/README | 72 + main/db1-ast/hash/extern.h | 65 + main/db1-ast/hash/hash.c | 999 ++++++ main/db1-ast/hash/hash.h | 293 ++ main/db1-ast/hash/hash_bigkey.c | 668 ++++ main/db1-ast/hash/hash_buf.c | 355 ++ main/db1-ast/hash/hash_func.c | 225 ++ main/db1-ast/hash/hash_log2.c | 56 + main/db1-ast/hash/hash_page.c | 944 ++++++ main/db1-ast/hash/hsearch.c | 107 + main/db1-ast/hash/ndbm.c | 235 ++ main/db1-ast/hash/page.h | 92 + main/db1-ast/hash/search.h | 51 + main/db1-ast/include/circ-queue.h | 131 + main/db1-ast/include/compat.h | 49 + main/db1-ast/include/db.h | 250 ++ main/db1-ast/include/mpool.h | 115 + main/db1-ast/include/ndbm.h | 79 + main/db1-ast/libdb.map | 11 + main/db1-ast/mpool/README | 7 + main/db1-ast/mpool/mpool.c | 498 +++ main/db1-ast/recno/extern.h | 54 + main/db1-ast/recno/rec_close.c | 183 ++ main/db1-ast/recno/rec_delete.c | 197 ++ main/db1-ast/recno/rec_get.c | 311 ++ main/db1-ast/recno/rec_open.c | 241 ++ main/db1-ast/recno/rec_put.c | 280 ++ main/db1-ast/recno/rec_search.c | 126 + main/db1-ast/recno/rec_seq.c | 131 + main/db1-ast/recno/rec_utils.c | 122 + main/db1-ast/recno/recno.h | 39 + main/devicestate.c | 383 +++ main/dlfcn.c | 1314 ++++++++ main/dns.c | 228 ++ main/dnsmgr.c | 426 +++ main/dsp.c | 1761 ++++++++++ main/ecdisa.h | 15 + main/editline/CHANGES | 42 + main/editline/INSTALL | 64 + main/editline/Makefile.in | 233 ++ main/editline/PLATFORMS | 13 + main/editline/README | 11 + main/editline/TEST/test.c | 268 ++ main/editline/chared.c | 695 ++++ main/editline/chared.h | 159 + main/editline/common.c | 951 ++++++ main/editline/config.guess | 1449 +++++++++ main/editline/config.h.in | 21 + main/editline/config.sub | 1412 ++++++++ main/editline/configure | 2421 ++++++++++++++ main/editline/configure.in | 276 ++ main/editline/editline.3 | 646 ++++ main/editline/editrc.5 | 491 +++ main/editline/el.c | 509 +++ main/editline/el.h | 145 + main/editline/emacs.c | 488 +++ main/editline/hist.c | 197 ++ main/editline/hist.h | 80 + main/editline/histedit.h | 197 ++ main/editline/history.c | 875 +++++ main/editline/install-sh | 250 ++ main/editline/key.c | 687 ++++ main/editline/key.h | 79 + main/editline/makelist | 254 ++ main/editline/map.c | 1418 ++++++++ main/editline/map.h | 79 + main/editline/np/fgetln.c | 88 + main/editline/np/strlcat.c | 75 + main/editline/np/strlcpy.c | 75 + main/editline/np/unvis.c | 322 ++ main/editline/np/vis.c | 348 ++ main/editline/np/vis.h | 96 + main/editline/parse.c | 259 ++ main/editline/parse.h | 52 + main/editline/prompt.c | 174 + main/editline/prompt.h | 62 + main/editline/read.c | 555 ++++ main/editline/read.h | 55 + main/editline/readline.c | 1669 ++++++++++ main/editline/readline/readline.h | 118 + main/editline/refresh.c | 1104 +++++++ main/editline/refresh.h | 63 + main/editline/search.c | 649 ++++ main/editline/search.h | 70 + main/editline/sig.c | 198 ++ main/editline/sig.h | 72 + main/editline/sys.h | 133 + main/editline/term.c | 1587 +++++++++ main/editline/term.h | 124 + main/editline/tokenizer.c | 397 +++ main/editline/tokenizer.h | 54 + main/editline/tty.c | 1182 +++++++ main/editline/tty.h | 484 +++ main/editline/vi.c | 941 ++++++ main/enum.c | 663 ++++ main/file.c | 1161 +++++++ main/fixedjitterbuf.c | 351 ++ main/fixedjitterbuf.h | 93 + main/frame.c | 1369 ++++++++ main/fskmodem.c | 305 ++ main/http.c | 698 ++++ main/image.c | 210 ++ main/indications.c | 600 ++++ main/io.c | 371 +++ main/jitterbuf.c | 825 +++++ main/jitterbuf.h | 162 + main/loader.c | 883 +++++ main/logger.c | 914 ++++++ main/manager.c | 2630 +++++++++++++++ main/md5.c | 267 ++ main/netsock.c | 211 ++ main/pbx.c | 5963 ++++++++++++++++++++++++++++++++++ main/plc.c | 251 ++ main/poll.c | 306 ++ main/privacy.c | 119 + main/rtp.c | 3012 +++++++++++++++++ main/say.c | 6420 +++++++++++++++++++++++++++++++++++++ main/sched.c | 399 +++ main/sha1.c | 385 +++ main/slinfactory.c | 157 + main/srv.c | 140 + main/stdtime/Makefile | 29 + main/stdtime/localtime.c | 1509 +++++++++ main/stdtime/private.h | 226 ++ main/stdtime/test.c | 21 + main/stdtime/tzfile.h | 189 ++ main/strcompat.c | 77 + main/tdd.c | 320 ++ main/term.c | 280 ++ main/translate.c | 703 ++++ main/udptl.c | 1255 ++++++++ main/ulaw.c | 106 + main/utils.c | 1231 +++++++ 178 files changed, 102463 insertions(+) create mode 100644 main/Makefile create mode 100644 main/abstract_jb.c create mode 100644 main/acl.c create mode 100644 main/aescrypt.c create mode 100644 main/aeskey.c create mode 100644 main/aesopt.h create mode 100644 main/aestab.c create mode 100644 main/alaw.c create mode 100644 main/app.c create mode 100644 main/ast_expr2.c create mode 100644 main/ast_expr2.fl create mode 100644 main/ast_expr2.h create mode 100644 main/ast_expr2.y create mode 100644 main/ast_expr2f.c create mode 100644 main/asterisk.c create mode 100644 main/astmm.c create mode 100644 main/autoservice.c create mode 100644 main/buildinfo.c create mode 100644 main/callerid.c create mode 100644 main/cdr.c create mode 100644 main/channel.c create mode 100644 main/chanvars.c create mode 100644 main/cli.c create mode 100644 main/coef_in.h create mode 100644 main/coef_out.h create mode 100644 main/config.c create mode 100644 main/cryptostub.c create mode 100644 main/db.c create mode 100644 main/db1-ast/Makefile create mode 100644 main/db1-ast/btree/bt_close.c create mode 100644 main/db1-ast/btree/bt_conv.c create mode 100644 main/db1-ast/btree/bt_debug.c create mode 100644 main/db1-ast/btree/bt_delete.c create mode 100644 main/db1-ast/btree/bt_get.c create mode 100644 main/db1-ast/btree/bt_open.c create mode 100644 main/db1-ast/btree/bt_overflow.c create mode 100644 main/db1-ast/btree/bt_page.c create mode 100644 main/db1-ast/btree/bt_put.c create mode 100644 main/db1-ast/btree/bt_search.c create mode 100644 main/db1-ast/btree/bt_seq.c create mode 100644 main/db1-ast/btree/bt_split.c create mode 100644 main/db1-ast/btree/bt_utils.c create mode 100644 main/db1-ast/btree/btree.h create mode 100644 main/db1-ast/btree/extern.h create mode 100644 main/db1-ast/db/db.c create mode 100644 main/db1-ast/hash/README create mode 100644 main/db1-ast/hash/extern.h create mode 100644 main/db1-ast/hash/hash.c create mode 100644 main/db1-ast/hash/hash.h create mode 100644 main/db1-ast/hash/hash_bigkey.c create mode 100644 main/db1-ast/hash/hash_buf.c create mode 100644 main/db1-ast/hash/hash_func.c create mode 100644 main/db1-ast/hash/hash_log2.c create mode 100644 main/db1-ast/hash/hash_page.c create mode 100644 main/db1-ast/hash/hsearch.c create mode 100644 main/db1-ast/hash/ndbm.c create mode 100644 main/db1-ast/hash/page.h create mode 100644 main/db1-ast/hash/search.h create mode 100644 main/db1-ast/include/circ-queue.h create mode 100644 main/db1-ast/include/compat.h create mode 100644 main/db1-ast/include/db.h create mode 100644 main/db1-ast/include/mpool.h create mode 100644 main/db1-ast/include/ndbm.h create mode 100644 main/db1-ast/libdb.map create mode 100644 main/db1-ast/mpool/README create mode 100644 main/db1-ast/mpool/mpool.c create mode 100644 main/db1-ast/recno/extern.h create mode 100644 main/db1-ast/recno/rec_close.c create mode 100644 main/db1-ast/recno/rec_delete.c create mode 100644 main/db1-ast/recno/rec_get.c create mode 100644 main/db1-ast/recno/rec_open.c create mode 100644 main/db1-ast/recno/rec_put.c create mode 100644 main/db1-ast/recno/rec_search.c create mode 100644 main/db1-ast/recno/rec_seq.c create mode 100644 main/db1-ast/recno/rec_utils.c create mode 100644 main/db1-ast/recno/recno.h create mode 100644 main/devicestate.c create mode 100644 main/dlfcn.c create mode 100644 main/dns.c create mode 100644 main/dnsmgr.c create mode 100644 main/dsp.c create mode 100644 main/ecdisa.h create mode 100644 main/editline/CHANGES create mode 100644 main/editline/INSTALL create mode 100644 main/editline/Makefile.in create mode 100644 main/editline/PLATFORMS create mode 100644 main/editline/README create mode 100644 main/editline/TEST/test.c create mode 100644 main/editline/chared.c create mode 100644 main/editline/chared.h create mode 100644 main/editline/common.c create mode 100755 main/editline/config.guess create mode 100644 main/editline/config.h.in create mode 100755 main/editline/config.sub create mode 100755 main/editline/configure create mode 100644 main/editline/configure.in create mode 100644 main/editline/editline.3 create mode 100644 main/editline/editrc.5 create mode 100644 main/editline/el.c create mode 100644 main/editline/el.h create mode 100644 main/editline/emacs.c create mode 100644 main/editline/hist.c create mode 100644 main/editline/hist.h create mode 100644 main/editline/histedit.h create mode 100644 main/editline/history.c create mode 100755 main/editline/install-sh create mode 100644 main/editline/key.c create mode 100644 main/editline/key.h create mode 100644 main/editline/makelist create mode 100644 main/editline/map.c create mode 100644 main/editline/map.h create mode 100644 main/editline/np/fgetln.c create mode 100644 main/editline/np/strlcat.c create mode 100644 main/editline/np/strlcpy.c create mode 100644 main/editline/np/unvis.c create mode 100644 main/editline/np/vis.c create mode 100644 main/editline/np/vis.h create mode 100644 main/editline/parse.c create mode 100644 main/editline/parse.h create mode 100644 main/editline/prompt.c create mode 100644 main/editline/prompt.h create mode 100644 main/editline/read.c create mode 100644 main/editline/read.h create mode 100644 main/editline/readline.c create mode 100644 main/editline/readline/readline.h create mode 100644 main/editline/refresh.c create mode 100644 main/editline/refresh.h create mode 100644 main/editline/search.c create mode 100644 main/editline/search.h create mode 100644 main/editline/sig.c create mode 100644 main/editline/sig.h create mode 100644 main/editline/sys.h create mode 100644 main/editline/term.c create mode 100644 main/editline/term.h create mode 100644 main/editline/tokenizer.c create mode 100644 main/editline/tokenizer.h create mode 100644 main/editline/tty.c create mode 100644 main/editline/tty.h create mode 100644 main/editline/vi.c create mode 100644 main/enum.c create mode 100644 main/file.c create mode 100644 main/fixedjitterbuf.c create mode 100644 main/fixedjitterbuf.h create mode 100644 main/frame.c create mode 100644 main/fskmodem.c create mode 100644 main/http.c create mode 100644 main/image.c create mode 100644 main/indications.c create mode 100644 main/io.c create mode 100644 main/jitterbuf.c create mode 100644 main/jitterbuf.h create mode 100644 main/loader.c create mode 100644 main/logger.c create mode 100644 main/manager.c create mode 100644 main/md5.c create mode 100644 main/netsock.c create mode 100644 main/pbx.c create mode 100644 main/plc.c create mode 100644 main/poll.c create mode 100644 main/privacy.c create mode 100644 main/rtp.c create mode 100644 main/say.c create mode 100644 main/sched.c create mode 100644 main/sha1.c create mode 100644 main/slinfactory.c create mode 100644 main/srv.c create mode 100644 main/stdtime/Makefile create mode 100644 main/stdtime/localtime.c create mode 100644 main/stdtime/private.h create mode 100644 main/stdtime/test.c create mode 100644 main/stdtime/tzfile.h create mode 100644 main/strcompat.c create mode 100644 main/tdd.c create mode 100644 main/term.c create mode 100644 main/translate.c create mode 100644 main/udptl.c create mode 100644 main/ulaw.c create mode 100644 main/utils.c (limited to 'main') diff --git a/main/Makefile b/main/Makefile new file mode 100644 index 000000000..850af5f4d --- /dev/null +++ b/main/Makefile @@ -0,0 +1,138 @@ +# +# Asterisk -- A telephony toolkit for Linux. +# +# Makefile to build main Asterisk binary +# +# Copyright (C) 1999-2006, Digium, Inc. +# +# Mark Spencer +# +# This program is free software, distributed under the terms of +# the GNU General Public License +# + +ifneq ($(wildcard $(ASTTOPDIR)/menuselect.makeopts),) + include $(ASTTOPDIR)/menuselect.makeopts + include $(ASTTOPDIR)/menuselect.makedeps +endif + +ifneq ($(wildcard $(ASTTOPDIR)/makeopts.embed_rules),) + include $(ASTTOPDIR)/makeopts.embed_rules +endif + +all: asterisk + +include $(ASTTOPDIR)/Makefile.moddir_rules + +OBJS= io.o sched.o logger.o frame.o loader.o config.o channel.o \ + translate.o file.o pbx.o cli.o md5.o term.o \ + ulaw.o alaw.o callerid.o fskmodem.o image.o app.o \ + cdr.o tdd.o acl.o rtp.o udptl.o manager.o asterisk.o \ + dsp.o chanvars.o indications.o autoservice.o db.o privacy.o \ + astmm.o enum.o srv.o dns.o aescrypt.o aestab.o aeskey.o \ + utils.o plc.o jitterbuf.o dnsmgr.o devicestate.o \ + netsock.o slinfactory.o ast_expr2.o ast_expr2f.o \ + cryptostub.o sha1.o http.o fixedjitterbuf.o abstract_jb.o + +# we need to link in the objects statically, not as a library, because +# otherwise modules will not have them available if none of the static +# objects use it. +OBJS+=stdtime/localtime.o + +# At the moment say.o is an optional component which can be overridden +# by a module. +OBJS+=say.o + +ifeq ($(wildcard /usr/include/sys/poll.h),) + OBJS+=poll.o +endif + +ifeq ($(wildcard /usr/include/dlfcn.h),) + OBJS+=dlfcn.o +endif + +ifneq ($(findstring $(OSARCH), linux-gnu uclinux ),) + ifneq ($(findstring LOADABLE_MODULES,$(MENUSELECT_CFLAGS)),) + AST_LIBS+=-ldl + endif + AST_LIBS+=-lpthread $(EDITLINE_LIB) -lm -lresolv +else + AST_LIBS+=$(EDITLINE_LIB) -lm +endif + +ifneq ($(findstring darwin,$(OSARCH)),) + AST_LIBS+=-lresolv + ASTLINK=-Wl,-dynamic + # Mac on Intel CoreDuo does not need poll compatibility layer + ifneq ($(PROC),i386) + OBJS+=poll.o + ASTCFLAGS+=-DPOLLCOMPAT + endif +else +# These are used for all but Darwin + ASTLINK=-Wl,-E + ifneq ($(findstring BSD,$(OSARCH)),) + LDFLAGS+=-L/usr/local/lib + endif +endif + +ifeq ($(OSARCH),FreeBSD) + AST_LIBS+=-lcrypto +endif + +ifeq ($(OSARCH),NetBSD) + AST_LIBS+=-lpthread -lcrypto -lm -L/usr/pkg/lib $(EDITLINE_LIB) +endif + +ifeq ($(OSARCH),OpenBSD) + AST_LIBS+=-lcrypto -lpthread -lm $(EDITLINE_LIB) +endif + +ifeq ($(OSARCH),SunOS) + AST_LIBS+=-lpthread -ldl -lnsl -lsocket -lresolv -L/opt/ssl/lib -L/usr/local/ssl/lib + OBJS+=strcompat.o + ASTLINK= +endif + +editline/libedit.a: + cd editline && unset CFLAGS AST_LIBS && test -f config.h || CFLAGS="$(OPTIMIZE)" ./configure --build=$(BUILD_PLATFORM) --host=$(HOST_PLATFORM) + $(MAKE) -C editline libedit.a + +db1-ast/libdb1.a: + CFLAGS="$(ASTCFLAGS)" $(MAKE) -C db1-ast libdb1.a + +ast_expr2.c ast_expr2.h: + bison -o $@ -d --name-prefix=ast_yy ast_expr2.y + +ast_expr2f.c: + flex -o $@ --full ast_expr2.fl + +testexpr2: ast_expr2f.c ast_expr2.c ast_expr2.h + $(CC) -g -c -Iinclude -DSTANDALONE ast_expr2f.c + $(CC) -g -c -Iinclude -DSTANDALONE ast_expr2.c + $(CC) -g -o testexpr2 ast_expr2f.o ast_expr2.o + rm ast_expr2.o ast_expr2f.o + +channel.o: CFLAGS+=$(ZAPTEL_INCLUDE) + +AST_EMBED_LDSCRIPTS:=$(sort $(EMBED_LDSCRIPTS)) +AST_EMBED_LDFLAGS:=$(foreach dep,$(EMBED_LDFLAGS),$(value $(dep))) +AST_EMBED_LIBS:=$(foreach dep,$(EMBED_LIBS),$(value $(dep))) +OBJS:=$(sort $(OBJS)) + +asterisk: $(OBJS) editline/libedit.a db1-ast/libdb1.a $(AST_EMBED_LDSCRIPTS) + @$(ASTTOPDIR)/build_tools/make_build_h > $(ASTTOPDIR)/include/asterisk/build.h.tmp + @if cmp -s $(ASTTOPDIR)/include/asterisk/build.h.tmp $(ASTTOPDIR)/include/asterisk/build.h ; then echo ; else \ + mv $(ASTTOPDIR)/include/asterisk/build.h.tmp $(ASTTOPDIR)/include/asterisk/build.h ; \ + fi + @rm -f $(ASTTOPDIR)/include/asterisk/build.h.tmp + @$(CC) -c -o buildinfo.o $(CFLAGS) buildinfo.c + $(ECHO_PREFIX) echo " [LD] $^ -> $@" + $(CMD_PREFIX) $(CXX) $(LDFLAGS) $(STATIC_BUILD) -o asterisk $(ASTLINK) $(AST_EMBED_LDFLAGS) $^ buildinfo.o $(AST_LIBS) $(AST_EMBED_LIBS) + +clean:: + rm -f asterisk + rm -f .depend + @if [ -f editline/Makefile ]; then $(MAKE) -C editline distclean ; fi + @$(MAKE) -C db1-ast clean + @$(MAKE) -C stdtime clean diff --git a/main/abstract_jb.c b/main/abstract_jb.c new file mode 100644 index 000000000..1525d81b0 --- /dev/null +++ b/main/abstract_jb.c @@ -0,0 +1,784 @@ +/* + * abstract_jb: common implementation-independent jitterbuffer stuff + * + * Copyright (C) 2005, Attractel OOD + * + * Contributors: + * Slav Klenov + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + * + * A license has been granted to Digium (via disclaimer) for the use of + * this code. + */ + +/*! \file + * + * \brief Common implementation-independent jitterbuffer stuff. + * + * \author Slav Klenov + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include + +#include "asterisk/frame.h" +#include "asterisk/channel.h" +#include "asterisk/logger.h" +#include "asterisk/term.h" +#include "asterisk/options.h" +#include "asterisk/utils.h" + +#include "asterisk/abstract_jb.h" +#include "fixedjitterbuf.h" +#include "jitterbuf.h" + +/*! Internal jb flags */ +enum { + JB_USE = (1 << 0), + JB_TIMEBASE_INITIALIZED = (1 << 1), + JB_CREATED = (1 << 2) +}; + +/* Hooks for the abstract jb implementation */ + +/*! \brief Create */ +typedef void * (*jb_create_impl)(struct ast_jb_conf *general_config, long resynch_threshold); +/*! \brief Destroy */ +typedef void (*jb_destroy_impl)(void *jb); +/*! \brief Put first frame */ +typedef int (*jb_put_first_impl)(void *jb, struct ast_frame *fin, long now); +/*! \brief Put frame */ +typedef int (*jb_put_impl)(void *jb, struct ast_frame *fin, long now); +/*! \brief Get frame for now */ +typedef int (*jb_get_impl)(void *jb, struct ast_frame **fout, long now, long interpl); +/*! \brief Get next */ +typedef long (*jb_next_impl)(void *jb); +/*! \brief Remove first frame */ +typedef int (*jb_remove_impl)(void *jb, struct ast_frame **fout); +/*! \brief Force resynch */ +typedef void (*jb_force_resynch_impl)(void *jb); + + +/*! + * \brief Jitterbuffer implementation private struct. + */ +struct ast_jb_impl +{ + char name[AST_JB_IMPL_NAME_SIZE]; + jb_create_impl create; + jb_destroy_impl destroy; + jb_put_first_impl put_first; + jb_put_impl put; + jb_get_impl get; + jb_next_impl next; + jb_remove_impl remove; + jb_force_resynch_impl force_resync; +}; + +/* Implementation functions */ +/* fixed */ +static void * jb_create_fixed(struct ast_jb_conf *general_config, long resynch_threshold); +static void jb_destroy_fixed(void *jb); +static int jb_put_first_fixed(void *jb, struct ast_frame *fin, long now); +static int jb_put_fixed(void *jb, struct ast_frame *fin, long now); +static int jb_get_fixed(void *jb, struct ast_frame **fout, long now, long interpl); +static long jb_next_fixed(void *jb); +static int jb_remove_fixed(void *jb, struct ast_frame **fout); +static void jb_force_resynch_fixed(void *jb); +/* adaptive */ +static void * jb_create_adaptive(struct ast_jb_conf *general_config, long resynch_threshold); +static void jb_destroy_adaptive(void *jb); +static int jb_put_first_adaptive(void *jb, struct ast_frame *fin, long now); +static int jb_put_adaptive(void *jb, struct ast_frame *fin, long now); +static int jb_get_adaptive(void *jb, struct ast_frame **fout, long now, long interpl); +static long jb_next_adaptive(void *jb); +static int jb_remove_adaptive(void *jb, struct ast_frame **fout); +static void jb_force_resynch_adaptive(void *jb); + +/* Available jb implementations */ +static struct ast_jb_impl avail_impl[] = +{ + { + .name = "fixed", + .create = jb_create_fixed, + .destroy = jb_destroy_fixed, + .put_first = jb_put_first_fixed, + .put = jb_put_fixed, + .get = jb_get_fixed, + .next = jb_next_fixed, + .remove = jb_remove_fixed, + .force_resync = jb_force_resynch_fixed + }, + { + .name = "adaptive", + .create = jb_create_adaptive, + .destroy = jb_destroy_adaptive, + .put_first = jb_put_first_adaptive, + .put = jb_put_adaptive, + .get = jb_get_adaptive, + .next = jb_next_adaptive, + .remove = jb_remove_adaptive, + .force_resync = jb_force_resynch_adaptive + } +}; + +static int default_impl = 0; + + +/*! Abstract return codes */ +enum { + JB_IMPL_OK, + JB_IMPL_DROP, + JB_IMPL_INTERP, + JB_IMPL_NOFRAME +}; + +/* Translations between impl and abstract return codes */ +static int fixed_to_abstract_code[] = + {JB_IMPL_OK, JB_IMPL_DROP, JB_IMPL_INTERP, JB_IMPL_NOFRAME}; +static int adaptive_to_abstract_code[] = + {JB_IMPL_OK, JB_IMPL_NOFRAME, JB_IMPL_NOFRAME, JB_IMPL_INTERP, JB_IMPL_DROP, JB_IMPL_OK}; + +/* JB_GET actions (used only for the frames log) */ +static char *jb_get_actions[] = {"Delivered", "Dropped", "Interpolated", "No"}; + +/*! \brief Macros for the frame log files */ +#define jb_framelog(...) do { \ + if (jb->logfile) { \ + fprintf(jb->logfile, __VA_ARGS__); \ + fflush(jb->logfile); \ + } \ +} while (0) + + +/* Internal utility functions */ +static void jb_choose_impl(struct ast_channel *chan); +static void jb_get_and_deliver(struct ast_channel *chan); +static int create_jb(struct ast_channel *chan, struct ast_frame *first_frame); +static long get_now(struct ast_jb *jb, struct timeval *tv); + + +/* Interface ast jb functions impl */ + + +static void jb_choose_impl(struct ast_channel *chan) +{ + struct ast_jb *jb = &chan->jb; + struct ast_jb_conf *jbconf = &jb->conf; + struct ast_jb_impl *test_impl; + int i, avail_impl_count = sizeof(avail_impl) / sizeof(avail_impl[0]); + + jb->impl = &avail_impl[default_impl]; + + if (ast_strlen_zero(jbconf->impl)) + return; + + for (i = 0; i < avail_impl_count; i++) { + test_impl = &avail_impl[i]; + if (!strcasecmp(jbconf->impl, test_impl->name)) { + jb->impl = test_impl; + return; + } + } +} + +int ast_jb_do_usecheck(struct ast_channel *c0, struct ast_channel *c1) +{ + struct ast_jb *jb0 = &c0->jb; + struct ast_jb *jb1 = &c1->jb; + struct ast_jb_conf *conf0 = &jb0->conf; + struct ast_jb_conf *conf1 = &jb1->conf; + int c0_wants_jitter = c0->tech->properties & AST_CHAN_TP_WANTSJITTER; + int c0_creates_jitter = c0->tech->properties & AST_CHAN_TP_CREATESJITTER; + int c0_jb_enabled = ast_test_flag(conf0, AST_JB_ENABLED); + int c0_force_jb = ast_test_flag(conf0, AST_JB_FORCED); + int c0_jb_timebase_initialized = ast_test_flag(jb0, JB_TIMEBASE_INITIALIZED); + int c0_jb_created = ast_test_flag(jb0, JB_CREATED); + int c1_wants_jitter = c1->tech->properties & AST_CHAN_TP_WANTSJITTER; + int c1_creates_jitter = c1->tech->properties & AST_CHAN_TP_CREATESJITTER; + int c1_jb_enabled = ast_test_flag(conf1, AST_JB_ENABLED); + int c1_force_jb = ast_test_flag(conf1, AST_JB_FORCED); + int c1_jb_timebase_initialized = ast_test_flag(jb1, JB_TIMEBASE_INITIALIZED); + int c1_jb_created = ast_test_flag(jb1, JB_CREATED); + int inuse = 0; + + /* Determine whether audio going to c0 needs a jitter buffer */ + if (((!c0_wants_jitter && c1_creates_jitter) || (c0_force_jb && c1_creates_jitter)) && c0_jb_enabled) { + ast_set_flag(jb0, JB_USE); + if (!c0_jb_timebase_initialized) { + if (c1_jb_timebase_initialized) { + memcpy(&jb0->timebase, &jb1->timebase, sizeof(struct timeval)); + } else { + gettimeofday(&jb0->timebase, NULL); + } + ast_set_flag(jb0, JB_TIMEBASE_INITIALIZED); + } + + if (!c0_jb_created) { + jb_choose_impl(c0); + } + + inuse = 1; + } + + /* Determine whether audio going to c1 needs a jitter buffer */ + if (((!c1_wants_jitter && c0_creates_jitter) || (c1_force_jb && c0_creates_jitter)) && c1_jb_enabled) { + ast_set_flag(jb1, JB_USE); + if (!c1_jb_timebase_initialized) { + if (c0_jb_timebase_initialized) { + memcpy(&jb1->timebase, &jb0->timebase, sizeof(struct timeval)); + } else { + gettimeofday(&jb1->timebase, NULL); + } + ast_set_flag(jb1, JB_TIMEBASE_INITIALIZED); + } + + if (!c1_jb_created) { + jb_choose_impl(c1); + } + + inuse = 1; + } + + return inuse; +} + +int ast_jb_get_when_to_wakeup(struct ast_channel *c0, struct ast_channel *c1, int time_left) +{ + struct ast_jb *jb0 = &c0->jb; + struct ast_jb *jb1 = &c1->jb; + int c0_use_jb = ast_test_flag(jb0, JB_USE); + int c0_jb_is_created = ast_test_flag(jb0, JB_CREATED); + int c1_use_jb = ast_test_flag(jb1, JB_USE); + int c1_jb_is_created = ast_test_flag(jb1, JB_CREATED); + int wait, wait0, wait1; + struct timeval tv_now; + + if (time_left == 0) { + /* No time left - the bridge will be retried */ + /* TODO: Test disable this */ + /*return 0;*/ + } + + if (time_left < 0) { + time_left = INT_MAX; + } + + gettimeofday(&tv_now, NULL); + + wait0 = (c0_use_jb && c0_jb_is_created) ? jb0->next - get_now(jb0, &tv_now) : time_left; + wait1 = (c1_use_jb && c1_jb_is_created) ? jb1->next - get_now(jb1, &tv_now) : time_left; + + wait = wait0 < wait1 ? wait0 : wait1; + wait = wait < time_left ? wait : time_left; + + if (wait == INT_MAX) { + wait = -1; + } else if (wait < 1) { + /* don't let wait=0, because this can cause the pbx thread to loop without any sleeping at all */ + wait = 1; + } + + return wait; +} + + +int ast_jb_put(struct ast_channel *chan, struct ast_frame *f) +{ + struct ast_jb *jb = &chan->jb; + struct ast_jb_impl *jbimpl = jb->impl; + void *jbobj = jb->jbobj; + struct ast_frame *frr; + long now = 0; + + if (!ast_test_flag(jb, JB_USE)) + return -1; + + if (f->frametype != AST_FRAME_VOICE) { + if (f->frametype == AST_FRAME_DTMF && ast_test_flag(jb, JB_CREATED)) { + jb_framelog("JB_PUT {now=%ld}: Received DTMF frame. Force resynching jb...\n", now); + jbimpl->force_resync(jbobj); + } + + return -1; + } + + /* We consider an enabled jitterbuffer should receive frames with valid timing info. */ + if (!f->has_timing_info || f->len < 2 || f->ts < 0) { + ast_log(LOG_WARNING, "%s recieved frame with invalid timing info: " + "has_timing_info=%d, len=%ld, ts=%ld, src=%s\n", + chan->name, f->has_timing_info, f->len, f->ts, f->src); + return -1; + } + + if (f->mallocd & AST_MALLOCD_HDR) + frr = ast_frdup(f); + else + frr = ast_frisolate(f); + + if (!frr) { + ast_log(LOG_ERROR, "Failed to isolate frame for the jitterbuffer on channel '%s'\n", chan->name); + return -1; + } + + if (!ast_test_flag(jb, JB_CREATED)) { + if (create_jb(chan, frr)) { + ast_frfree(frr); + /* Disable the jitterbuffer */ + ast_clear_flag(jb, JB_USE); + return -1; + } + + ast_set_flag(jb, JB_CREATED); + return 0; + } else { + now = get_now(jb, NULL); + if (jbimpl->put(jbobj, frr, now) != JB_IMPL_OK) { + jb_framelog("JB_PUT {now=%ld}: Dropped frame with ts=%ld and len=%ld\n", now, frr->ts, frr->len); + ast_frfree(frr); + /*return -1;*/ + /* TODO: Check this fix - should return 0 here, because the dropped frame shouldn't + be delivered at all */ + return 0; + } + + jb->next = jbimpl->next(jbobj); + + jb_framelog("JB_PUT {now=%ld}: Queued frame with ts=%ld and len=%ld\n", now, frr->ts, frr->len); + + return 0; + } +} + + +void ast_jb_get_and_deliver(struct ast_channel *c0, struct ast_channel *c1) +{ + struct ast_jb *jb0 = &c0->jb; + struct ast_jb *jb1 = &c1->jb; + int c0_use_jb = ast_test_flag(jb0, JB_USE); + int c0_jb_is_created = ast_test_flag(jb0, JB_CREATED); + int c1_use_jb = ast_test_flag(jb1, JB_USE); + int c1_jb_is_created = ast_test_flag(jb1, JB_CREATED); + + if (c0_use_jb && c0_jb_is_created) + jb_get_and_deliver(c0); + + if (c1_use_jb && c1_jb_is_created) + jb_get_and_deliver(c1); +} + + +static void jb_get_and_deliver(struct ast_channel *chan) +{ + struct ast_jb *jb = &chan->jb; + struct ast_jb_impl *jbimpl = jb->impl; + void *jbobj = jb->jbobj; + struct ast_frame *f, finterp; + long now; + int interpolation_len, res; + + now = get_now(jb, NULL); + jb->next = jbimpl->next(jbobj); + if (now < jb->next) { + jb_framelog("\tJB_GET {now=%ld}: now < next=%ld\n", now, jb->next); + return; + } + + while (now >= jb->next) { + interpolation_len = ast_codec_interp_len(jb->last_format); + + res = jbimpl->get(jbobj, &f, now, interpolation_len); + + switch(res) { + case JB_IMPL_OK: + /* deliver the frame */ + ast_write(chan, f); + case JB_IMPL_DROP: + jb_framelog("\tJB_GET {now=%ld}: %s frame with ts=%ld and len=%ld\n", + now, jb_get_actions[res], f->ts, f->len); + jb->last_format = f->subclass; + ast_frfree(f); + break; + case JB_IMPL_INTERP: + /* interpolate a frame */ + f = &finterp; + f->frametype = AST_FRAME_VOICE; + f->subclass = jb->last_format; + f->datalen = 0; + f->samples = interpolation_len * 8; + f->mallocd = 0; + f->src = "JB interpolation"; + f->data = NULL; + f->delivery = ast_tvadd(jb->timebase, ast_samp2tv(jb->next, 1000)); + f->offset = AST_FRIENDLY_OFFSET; + /* deliver the interpolated frame */ + ast_write(chan, f); + jb_framelog("\tJB_GET {now=%ld}: Interpolated frame with len=%d\n", now, interpolation_len); + break; + case JB_IMPL_NOFRAME: + ast_log(LOG_WARNING, + "JB_IMPL_NOFRAME is retuned from the %s jb when now=%ld >= next=%ld, jbnext=%ld!\n", + jbimpl->name, now, jb->next, jbimpl->next(jbobj)); + jb_framelog("\tJB_GET {now=%ld}: No frame for now!?\n", now); + return; + default: + ast_log(LOG_ERROR, "This should never happen!\n"); + CRASH; + break; + } + + jb->next = jbimpl->next(jbobj); + } +} + + +static int create_jb(struct ast_channel *chan, struct ast_frame *frr) +{ + struct ast_jb *jb = &chan->jb; + struct ast_jb_conf *jbconf = &jb->conf; + struct ast_jb_impl *jbimpl = jb->impl; + void *jbobj; + struct ast_channel *bridged; + long now; + char logfile_pathname[20 + AST_JB_IMPL_NAME_SIZE + 2*AST_CHANNEL_NAME + 1]; + char name1[AST_CHANNEL_NAME], name2[AST_CHANNEL_NAME], *tmp; + int res; + + jbobj = jb->jbobj = jbimpl->create(jbconf, jbconf->resync_threshold); + if (!jbobj) { + ast_log(LOG_WARNING, "Failed to create jitterbuffer on channel '%s'\n", chan->name); + return -1; + } + + now = get_now(jb, NULL); + res = jbimpl->put_first(jbobj, frr, now); + + /* The result of putting the first frame should not differ from OK. However, its possible + some implementations (i.e. adaptive's when resynch_threshold is specified) to drop it. */ + if (res != JB_IMPL_OK) { + ast_log(LOG_WARNING, "Failed to put first frame in the jitterbuffer on channel '%s'\n", chan->name); + /* + jbimpl->destroy(jbobj); + return -1; + */ + } + + /* Init next */ + jb->next = jbimpl->next(jbobj); + + /* Init last format for a first time. */ + jb->last_format = frr->subclass; + + /* Create a frame log file */ + if (ast_test_flag(jbconf, AST_JB_LOG)) { + snprintf(name2, sizeof(name2), "%s", chan->name); + tmp = strchr(name2, '/'); + if (tmp) + *tmp = '#'; + + bridged = ast_bridged_channel(chan); + if (!bridged) { + /* We should always have bridged chan if a jitterbuffer is in use */ + CRASH; + } + snprintf(name1, sizeof(name1), "%s", bridged->name); + tmp = strchr(name1, '/'); + if (tmp) + *tmp = '#'; + + snprintf(logfile_pathname, sizeof(logfile_pathname), + "/tmp/ast_%s_jb_%s--%s.log", jbimpl->name, name1, name2); + jb->logfile = fopen(logfile_pathname, "w+b"); + + if (!jb->logfile) + ast_log(LOG_WARNING, "Failed to create frame log file with pathname '%s'\n", logfile_pathname); + + if (res == JB_IMPL_OK) + jb_framelog("JB_PUT_FIRST {now=%ld}: Queued frame with ts=%ld and len=%ld\n", + now, frr->ts, frr->len); + else + jb_framelog("JB_PUT_FIRST {now=%ld}: Dropped frame with ts=%ld and len=%ld\n", + now, frr->ts, frr->len); + } + + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "%s jitterbuffer created on channel %s\n", jbimpl->name, chan->name); + + /* Free the frame if it has not been queued in the jb */ + if (res != JB_IMPL_OK) + ast_frfree(frr); + + return 0; +} + + +void ast_jb_destroy(struct ast_channel *chan) +{ + struct ast_jb *jb = &chan->jb; + struct ast_jb_impl *jbimpl = jb->impl; + void *jbobj = jb->jbobj; + struct ast_frame *f; + + if (jb->logfile) { + fclose(jb->logfile); + jb->logfile = NULL; + } + + if (ast_test_flag(jb, JB_CREATED)) { + /* Remove and free all frames still queued in jb */ + while (jbimpl->remove(jbobj, &f) == JB_IMPL_OK) { + ast_frfree(f); + } + + jbimpl->destroy(jbobj); + jb->jbobj = NULL; + + ast_clear_flag(jb, JB_CREATED); + + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "%s jitterbuffer destroyed on channel %s\n", jbimpl->name, chan->name); + } +} + + +static long get_now(struct ast_jb *jb, struct timeval *tv) +{ + struct timeval now; + + if (!tv) { + tv = &now; + gettimeofday(tv, NULL); + } + + return ast_tvdiff_ms(*tv, jb->timebase); +} + + +int ast_jb_read_conf(struct ast_jb_conf *conf, char *varname, char *value) +{ + int prefixlen = sizeof(AST_JB_CONF_PREFIX) - 1; + char *name; + int tmp; + + if (strncasecmp(AST_JB_CONF_PREFIX, varname, prefixlen)) + return -1; + + name = varname + prefixlen; + + if (!strcasecmp(name, AST_JB_CONF_ENABLE)) { + ast_set2_flag(conf, ast_true(value), AST_JB_ENABLED); + } else if (!strcasecmp(name, AST_JB_CONF_FORCE)) { + ast_set2_flag(conf, ast_true(value), AST_JB_FORCED); + } else if (!strcasecmp(name, AST_JB_CONF_MAX_SIZE)) { + if ((tmp = atoi(value)) > 0) + conf->max_size = tmp; + } else if (!strcasecmp(name, AST_JB_CONF_RESYNCH_THRESHOLD)) { + if ((tmp = atoi(value)) > 0) + conf->resync_threshold = tmp; + } else if (!strcasecmp(name, AST_JB_CONF_IMPL)) { + if (!ast_strlen_zero(value)) + snprintf(conf->impl, sizeof(conf->impl), "%s", value); + } else if (!strcasecmp(name, AST_JB_CONF_LOG)) { + ast_set2_flag(conf, ast_true(value), AST_JB_LOG); + } else { + return -1; + } + + return 0; +} + + +void ast_jb_configure(struct ast_channel *chan, const struct ast_jb_conf *conf) +{ + memcpy(&chan->jb.conf, conf, sizeof(*conf)); +} + + +void ast_jb_get_config(const struct ast_channel *chan, struct ast_jb_conf *conf) +{ + memcpy(conf, &chan->jb.conf, sizeof(*conf)); +} + + +/* Implementation functions */ + +/* fixed */ + +static void * jb_create_fixed(struct ast_jb_conf *general_config, long resynch_threshold) +{ + struct fixed_jb_conf conf; + + conf.jbsize = general_config->max_size; + conf.resync_threshold = resynch_threshold; + + return fixed_jb_new(&conf); +} + + +static void jb_destroy_fixed(void *jb) +{ + struct fixed_jb *fixedjb = (struct fixed_jb *) jb; + + /* destroy the jb */ + fixed_jb_destroy(fixedjb); +} + + +static int jb_put_first_fixed(void *jb, struct ast_frame *fin, long now) +{ + struct fixed_jb *fixedjb = (struct fixed_jb *) jb; + int res; + + res = fixed_jb_put_first(fixedjb, fin, fin->len, fin->ts, now); + + return fixed_to_abstract_code[res]; +} + + +static int jb_put_fixed(void *jb, struct ast_frame *fin, long now) +{ + struct fixed_jb *fixedjb = (struct fixed_jb *) jb; + int res; + + res = fixed_jb_put(fixedjb, fin, fin->len, fin->ts, now); + + return fixed_to_abstract_code[res]; +} + + +static int jb_get_fixed(void *jb, struct ast_frame **fout, long now, long interpl) +{ + struct fixed_jb *fixedjb = (struct fixed_jb *) jb; + struct fixed_jb_frame frame; + int res; + + res = fixed_jb_get(fixedjb, &frame, now, interpl); + *fout = frame.data; + + return fixed_to_abstract_code[res]; +} + + +static long jb_next_fixed(void *jb) +{ + struct fixed_jb *fixedjb = (struct fixed_jb *) jb; + + return fixed_jb_next(fixedjb); +} + + +static int jb_remove_fixed(void *jb, struct ast_frame **fout) +{ + struct fixed_jb *fixedjb = (struct fixed_jb *) jb; + struct fixed_jb_frame frame; + int res; + + res = fixed_jb_remove(fixedjb, &frame); + *fout = frame.data; + + return fixed_to_abstract_code[res]; +} + + +static void jb_force_resynch_fixed(void *jb) +{ + struct fixed_jb *fixedjb = (struct fixed_jb *) jb; + + fixed_jb_set_force_resynch(fixedjb); +} + + +/* adaptive */ + +static void *jb_create_adaptive(struct ast_jb_conf *general_config, long resynch_threshold) +{ + jb_conf jbconf; + jitterbuf *adaptivejb; + + adaptivejb = jb_new(); + if (adaptivejb) { + jbconf.max_jitterbuf = general_config->max_size; + jbconf.resync_threshold = general_config->resync_threshold; + jbconf.max_contig_interp = 10; + jb_setconf(adaptivejb, &jbconf); + } + + return adaptivejb; +} + + +static void jb_destroy_adaptive(void *jb) +{ + jitterbuf *adaptivejb = (jitterbuf *) jb; + + jb_destroy(adaptivejb); +} + + +static int jb_put_first_adaptive(void *jb, struct ast_frame *fin, long now) +{ + return jb_put_adaptive(jb, fin, now); +} + + +static int jb_put_adaptive(void *jb, struct ast_frame *fin, long now) +{ + jitterbuf *adaptivejb = (jitterbuf *) jb; + int res; + + res = jb_put(adaptivejb, fin, JB_TYPE_VOICE, fin->len, fin->ts, now); + + return adaptive_to_abstract_code[res]; +} + + +static int jb_get_adaptive(void *jb, struct ast_frame **fout, long now, long interpl) +{ + jitterbuf *adaptivejb = (jitterbuf *) jb; + jb_frame frame; + int res; + + res = jb_get(adaptivejb, &frame, now, interpl); + *fout = frame.data; + + return adaptive_to_abstract_code[res]; +} + + +static long jb_next_adaptive(void *jb) +{ + jitterbuf *adaptivejb = (jitterbuf *) jb; + + return jb_next(adaptivejb); +} + + +static int jb_remove_adaptive(void *jb, struct ast_frame **fout) +{ + jitterbuf *adaptivejb = (jitterbuf *) jb; + jb_frame frame; + int res; + + res = jb_getall(adaptivejb, &frame); + *fout = frame.data; + + return adaptive_to_abstract_code[res]; +} + + +static void jb_force_resynch_adaptive(void *jb) +{ +} diff --git a/main/acl.c b/main/acl.c new file mode 100644 index 000000000..f3fd5097a --- /dev/null +++ b/main/acl.c @@ -0,0 +1,425 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Various sorts of access control + * + * \author Mark Spencer + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) +#include +#include +#endif + +#if defined(SOLARIS) +#include +#endif + +/* netinet/ip.h may not define the following (See RFCs 791 and 1349) */ +#if !defined(IPTOS_LOWCOST) +#define IPTOS_LOWCOST 0x02 +#endif + +#if !defined(IPTOS_MINCOST) +#define IPTOS_MINCOST IPTOS_LOWCOST +#endif + +#include "asterisk/acl.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/options.h" +#include "asterisk/utils.h" +#include "asterisk/lock.h" +#include "asterisk/srv.h" + +struct ast_ha { + /* Host access rule */ + struct in_addr netaddr; + struct in_addr netmask; + int sense; + struct ast_ha *next; +}; + +/* Default IP - if not otherwise set, don't breathe garbage */ +static struct in_addr __ourip = { 0x00000000 }; + +struct my_ifreq { + char ifrn_name[IFNAMSIZ]; /* Interface name, e.g. "eth0", "ppp0", etc. */ + struct sockaddr_in ifru_addr; +}; + +/* Free HA structure */ +void ast_free_ha(struct ast_ha *ha) +{ + struct ast_ha *hal; + while (ha) { + hal = ha; + ha = ha->next; + free(hal); + } +} + +/* Copy HA structure */ +static void ast_copy_ha(struct ast_ha *from, struct ast_ha *to) +{ + memcpy(&to->netaddr, &from->netaddr, sizeof(from->netaddr)); + memcpy(&to->netmask, &from->netmask, sizeof(from->netmask)); + to->sense = from->sense; +} + +/* Create duplicate of ha structure */ +static struct ast_ha *ast_duplicate_ha(struct ast_ha *original) +{ + struct ast_ha *new_ha; + + if ((new_ha = ast_malloc(sizeof(*new_ha)))) { + /* Copy from original to new object */ + ast_copy_ha(original, new_ha); + } + + return new_ha; +} + +/* Create duplicate HA link list */ +/* Used in chan_sip2 templates */ +struct ast_ha *ast_duplicate_ha_list(struct ast_ha *original) +{ + struct ast_ha *start = original; + struct ast_ha *ret = NULL; + struct ast_ha *link, *prev = NULL; + + while (start) { + link = ast_duplicate_ha(start); /* Create copy of this object */ + if (prev) + prev->next = link; /* Link previous to this object */ + + if (!ret) + ret = link; /* Save starting point */ + + start = start->next; /* Go to next object */ + prev = link; /* Save pointer to this object */ + } + return ret; /* Return start of list */ +} + +struct ast_ha *ast_append_ha(char *sense, char *stuff, struct ast_ha *path) +{ + struct ast_ha *ha; + char *nm = "255.255.255.255"; + char tmp[256]; + struct ast_ha *prev = NULL; + struct ast_ha *ret; + int x, z; + unsigned int y; + + ret = path; + while (path) { + prev = path; + path = path->next; + } + if ((ha = ast_malloc(sizeof(*ha)))) { + ast_copy_string(tmp, stuff, sizeof(tmp)); + nm = strchr(tmp, '/'); + if (!nm) { + nm = "255.255.255.255"; + } else { + *nm = '\0'; + nm++; + } + if (!strchr(nm, '.')) { + if ((sscanf(nm, "%d", &x) == 1) && (x >= 0) && (x <= 32)) { + y = 0; + for (z = 0; z < x; z++) { + y >>= 1; + y |= 0x80000000; + } + ha->netmask.s_addr = htonl(y); + } + } else if (!inet_aton(nm, &ha->netmask)) { + ast_log(LOG_WARNING, "%s is not a valid netmask\n", nm); + free(ha); + return path; + } + if (!inet_aton(tmp, &ha->netaddr)) { + ast_log(LOG_WARNING, "%s is not a valid IP\n", tmp); + free(ha); + return path; + } + ha->netaddr.s_addr &= ha->netmask.s_addr; + if (!strncasecmp(sense, "p", 1)) { + ha->sense = AST_SENSE_ALLOW; + } else { + ha->sense = AST_SENSE_DENY; + } + ha->next = NULL; + if (prev) { + prev->next = ha; + } else { + ret = ha; + } + } + ast_log(LOG_DEBUG, "%s/%s appended to acl for peer\n", stuff, nm); + return ret; +} + +int ast_apply_ha(struct ast_ha *ha, struct sockaddr_in *sin) +{ + /* Start optimistic */ + int res = AST_SENSE_ALLOW; + while (ha) { + char iabuf[INET_ADDRSTRLEN]; + char iabuf2[INET_ADDRSTRLEN]; + /* DEBUG */ + ast_copy_string(iabuf, ast_inet_ntoa(sin->sin_addr), sizeof(iabuf)); + ast_copy_string(iabuf2, ast_inet_ntoa(ha->netaddr), sizeof(iabuf2)); + ast_log(LOG_DEBUG, "##### Testing %s with %s\n", iabuf, iabuf2); + /* For each rule, if this address and the netmask = the net address + apply the current rule */ + if ((sin->sin_addr.s_addr & ha->netmask.s_addr) == ha->netaddr.s_addr) + res = ha->sense; + ha = ha->next; + } + return res; +} + +int ast_get_ip_or_srv(struct sockaddr_in *sin, const char *value, const char *service) +{ + struct hostent *hp; + struct ast_hostent ahp; + char srv[256]; + char host[256]; + int tportno = ntohs(sin->sin_port); + if (inet_aton(value, &sin->sin_addr)) + return 0; + if (service) { + snprintf(srv, sizeof(srv), "%s.%s", service, value); + if (ast_get_srv(NULL, host, sizeof(host), &tportno, srv) > 0) { + sin->sin_port = htons(tportno); + value = host; + } + } + hp = ast_gethostbyname(value, &ahp); + if (hp) { + memcpy(&sin->sin_addr, hp->h_addr, sizeof(sin->sin_addr)); + } else { + ast_log(LOG_WARNING, "Unable to lookup '%s'\n", value); + return -1; + } + return 0; +} + +struct dscp_codepoint { + char *name; + unsigned int space; +}; + +/* IANA registered DSCP codepoints */ + +static const struct dscp_codepoint dscp_pool1[] = { + { "CS0", 0x00 }, + { "CS1", 0x08 }, + { "CS2", 0x10 }, + { "CS3", 0x18 }, + { "CS4", 0x20 }, + { "CS5", 0x28 }, + { "CS6", 0x30 }, + { "CS7", 0x38 }, + { "AF11", 0x0A }, + { "AF12", 0x0C }, + { "AF13", 0x0E }, + { "AF21", 0x12 }, + { "AF22", 0x14 }, + { "AF23", 0x16 }, + { "AF31", 0x1A }, + { "AF32", 0x1C }, + { "AF33", 0x1E }, + { "AF41", 0x22 }, + { "AF42", 0x24 }, + { "AF43", 0x26 }, + { "EF", 0x2E }, +}; + +int ast_str2tos(const char *value, unsigned int *tos) +{ + int fval; + unsigned int x; + + if (sscanf(value, "%i", &fval) == 1) { + *tos = fval & 0xFF; + return 0; + } + + for (x = 0; x < sizeof(dscp_pool1) / sizeof(dscp_pool1[0]); x++) { + if (!strcasecmp(value, dscp_pool1[x].name)) { + *tos = dscp_pool1[x].space << 2; + return 0; + } + } + + if (!strcasecmp(value, "lowdelay")) + *tos = IPTOS_LOWDELAY; + else if (!strcasecmp(value, "throughput")) + *tos = IPTOS_THROUGHPUT; + else if (!strcasecmp(value, "reliability")) + *tos = IPTOS_RELIABILITY; + else if (!strcasecmp(value, "mincost")) + *tos = IPTOS_MINCOST; + else if (!strcasecmp(value, "none")) + *tos = 0; + else + return -1; + + ast_log(LOG_WARNING, "TOS value %s is deprecated. Please see doc/ip-tos.txt for more information.\n", value); + + return 0; +} + +const char *ast_tos2str(unsigned int tos) +{ + unsigned int x; + + switch (tos) { + case 0: + return "none"; + case IPTOS_LOWDELAY: + return "lowdelay"; + case IPTOS_THROUGHPUT: + return "throughput"; + case IPTOS_RELIABILITY: + return "reliability"; + case IPTOS_MINCOST: + return "mincost"; + default: + for (x = 0; x < sizeof(dscp_pool1) / sizeof(dscp_pool1[0]); x++) { + if (dscp_pool1[x].space == (tos >> 2)) + return dscp_pool1[x].name; + } + } + + return "unknown"; +} + +int ast_get_ip(struct sockaddr_in *sin, const char *value) +{ + return ast_get_ip_or_srv(sin, value, NULL); +} + +/* iface is the interface (e.g. eth0); address is the return value */ +int ast_lookup_iface(char *iface, struct in_addr *address) +{ + int mysock, res = 0; + struct my_ifreq ifreq; + + memset(&ifreq, 0, sizeof(ifreq)); + ast_copy_string(ifreq.ifrn_name, iface, sizeof(ifreq.ifrn_name)); + + mysock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + res = ioctl(mysock, SIOCGIFADDR, &ifreq); + + close(mysock); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to get IP of %s: %s\n", iface, strerror(errno)); + memcpy((char *)address, (char *)&__ourip, sizeof(__ourip)); + return -1; + } else { + memcpy((char *)address, (char *)&ifreq.ifru_addr.sin_addr, sizeof(ifreq.ifru_addr.sin_addr)); + return 0; + } +} + +int ast_ouraddrfor(struct in_addr *them, struct in_addr *us) +{ + int s; + struct sockaddr_in sin; + socklen_t slen; + + s = socket(PF_INET, SOCK_DGRAM, 0); + if (s < 0) { + ast_log(LOG_WARNING, "Cannot create socket\n"); + return -1; + } + sin.sin_family = AF_INET; + sin.sin_port = 5060; + sin.sin_addr = *them; + if (connect(s, (struct sockaddr *)&sin, sizeof(sin))) { + ast_log(LOG_WARNING, "Cannot connect\n"); + close(s); + return -1; + } + slen = sizeof(sin); + if (getsockname(s, (struct sockaddr *)&sin, &slen)) { + ast_log(LOG_WARNING, "Cannot get socket name\n"); + close(s); + return -1; + } + close(s); + *us = sin.sin_addr; + return 0; +} + +int ast_find_ourip(struct in_addr *ourip, struct sockaddr_in bindaddr) +{ + char ourhost[MAXHOSTNAMELEN] = ""; + struct ast_hostent ahp; + struct hostent *hp; + struct in_addr saddr; + + /* just use the bind address if it is nonzero */ + if (ntohl(bindaddr.sin_addr.s_addr)) { + memcpy(ourip, &bindaddr.sin_addr, sizeof(*ourip)); + return 0; + } + /* try to use our hostname */ + if (gethostname(ourhost, sizeof(ourhost) - 1)) { + ast_log(LOG_WARNING, "Unable to get hostname\n"); + } else { + hp = ast_gethostbyname(ourhost, &ahp); + if (hp) { + memcpy(ourip, hp->h_addr, sizeof(*ourip)); + return 0; + } + } + /* A.ROOT-SERVERS.NET. */ + if (inet_aton("198.41.0.4", &saddr) && !ast_ouraddrfor(&saddr, ourip)) + return 0; + return -1; +} + diff --git a/main/aescrypt.c b/main/aescrypt.c new file mode 100644 index 000000000..1ccddf3f5 --- /dev/null +++ b/main/aescrypt.c @@ -0,0 +1,317 @@ +/* + --------------------------------------------------------------------------- + Copyright (c) 2003, Dr Brian Gladman , Worcester, UK. + All rights reserved. + + LICENSE TERMS + + The free distribution and use of this software in both source and binary + form is allowed (with or without changes) provided that: + + 1. distributions of this source code include the above copyright + notice, this list of conditions and the following disclaimer; + + 2. distributions in binary form include the above copyright + notice, this list of conditions and the following disclaimer + in the documentation and/or other associated materials; + + 3. the copyright holder's name is not used to endorse products + built using this software without specific written permission. + + ALTERNATIVELY, provided that this notice is retained in full, this product + may be distributed under the terms of the GNU General Public License (GPL), + in which case the provisions of the GPL apply INSTEAD OF those given above. + + DISCLAIMER + + This software is provided 'as is' with no explicit or implied warranties + in respect of its properties, including, but not limited to, correctness + and/or fitness for purpose. + --------------------------------------------------------------------------- + Issue Date: 26/08/2003 + +*/ + +/*! \file + * + * \brief This file contains the code for implementing encryption and decryption + * for AES (Rijndael) for block and key sizes of 16, 24 and 32 bytes. It + * can optionally be replaced by code written in assembler using NASM. For + * further details see the file aesopt.h + * + * \author Dr Brian Gladman + */ + +#include "aesopt.h" + +#if defined(__cplusplus) +extern "C" +{ +#endif + +#define si(y,x,k,c) (s(y,c) = word_in(x, c) ^ (k)[c]) +#define so(y,x,c) word_out(y, c, s(x,c)) + +#if defined(ARRAYS) +#define locals(y,x) x[4],y[4] +#else +#define locals(y,x) x##0,x##1,x##2,x##3,y##0,y##1,y##2,y##3 +#endif + +#define l_copy(y, x) s(y,0) = s(x,0); s(y,1) = s(x,1); \ + s(y,2) = s(x,2); s(y,3) = s(x,3); +#define state_in(y,x,k) si(y,x,k,0); si(y,x,k,1); si(y,x,k,2); si(y,x,k,3) +#define state_out(y,x) so(y,x,0); so(y,x,1); so(y,x,2); so(y,x,3) +#define round(rm,y,x,k) rm(y,x,k,0); rm(y,x,k,1); rm(y,x,k,2); rm(y,x,k,3) + +#if defined(ENCRYPTION) && !defined(AES_ASM) + +/* Visual C++ .Net v7.1 provides the fastest encryption code when using + Pentium optimiation with small code but this is poor for decryption + so we need to control this with the following VC++ pragmas +*/ + +#if defined(_MSC_VER) +#pragma optimize( "s", on ) +#endif + +/* Given the column (c) of the output state variable, the following + macros give the input state variables which are needed in its + computation for each row (r) of the state. All the alternative + macros give the same end values but expand into different ways + of calculating these values. In particular the complex macro + used for dynamically variable block sizes is designed to expand + to a compile time constant whenever possible but will expand to + conditional clauses on some branches (I am grateful to Frank + Yellin for this construction) +*/ + +#define fwd_var(x,r,c)\ + ( r == 0 ? ( c == 0 ? s(x,0) : c == 1 ? s(x,1) : c == 2 ? s(x,2) : s(x,3))\ + : r == 1 ? ( c == 0 ? s(x,1) : c == 1 ? s(x,2) : c == 2 ? s(x,3) : s(x,0))\ + : r == 2 ? ( c == 0 ? s(x,2) : c == 1 ? s(x,3) : c == 2 ? s(x,0) : s(x,1))\ + : ( c == 0 ? s(x,3) : c == 1 ? s(x,0) : c == 2 ? s(x,1) : s(x,2))) + +#if defined(FT4_SET) +#undef dec_fmvars +#define fwd_rnd(y,x,k,c) (s(y,c) = (k)[c] ^ four_tables(x,t_use(f,n),fwd_var,rf1,c)) +#elif defined(FT1_SET) +#undef dec_fmvars +#define fwd_rnd(y,x,k,c) (s(y,c) = (k)[c] ^ one_table(x,upr,t_use(f,n),fwd_var,rf1,c)) +#else +#define fwd_rnd(y,x,k,c) (s(y,c) = (k)[c] ^ fwd_mcol(no_table(x,t_use(s,box),fwd_var,rf1,c))) +#endif + +#if defined(FL4_SET) +#define fwd_lrnd(y,x,k,c) (s(y,c) = (k)[c] ^ four_tables(x,t_use(f,l),fwd_var,rf1,c)) +#elif defined(FL1_SET) +#define fwd_lrnd(y,x,k,c) (s(y,c) = (k)[c] ^ one_table(x,ups,t_use(f,l),fwd_var,rf1,c)) +#else +#define fwd_lrnd(y,x,k,c) (s(y,c) = (k)[c] ^ no_table(x,t_use(s,box),fwd_var,rf1,c)) +#endif + +aes_rval aes_encrypt(const void *in_blk, void *out_blk, const aes_encrypt_ctx cx[1]) +{ aes_32t locals(b0, b1); + const aes_32t *kp = cx->ks; +#ifdef dec_fmvars + dec_fmvars; /* declare variables for fwd_mcol() if needed */ +#endif + + aes_32t nr = (kp[45] ^ kp[52] ^ kp[53] ? kp[52] : 14); + +#ifdef AES_ERR_CHK + if( (nr != 10 || !(kp[0] | kp[3] | kp[4])) + && (nr != 12 || !(kp[0] | kp[5] | kp[6])) + && (nr != 14 || !(kp[0] | kp[7] | kp[8])) ) + return aes_error; +#endif + + state_in(b0, in_blk, kp); + +#if (ENC_UNROLL == FULL) + + switch(nr) + { + case 14: + round(fwd_rnd, b1, b0, kp + 1 * N_COLS); + round(fwd_rnd, b0, b1, kp + 2 * N_COLS); + kp += 2 * N_COLS; + case 12: + round(fwd_rnd, b1, b0, kp + 1 * N_COLS); + round(fwd_rnd, b0, b1, kp + 2 * N_COLS); + kp += 2 * N_COLS; + case 10: + round(fwd_rnd, b1, b0, kp + 1 * N_COLS); + round(fwd_rnd, b0, b1, kp + 2 * N_COLS); + round(fwd_rnd, b1, b0, kp + 3 * N_COLS); + round(fwd_rnd, b0, b1, kp + 4 * N_COLS); + round(fwd_rnd, b1, b0, kp + 5 * N_COLS); + round(fwd_rnd, b0, b1, kp + 6 * N_COLS); + round(fwd_rnd, b1, b0, kp + 7 * N_COLS); + round(fwd_rnd, b0, b1, kp + 8 * N_COLS); + round(fwd_rnd, b1, b0, kp + 9 * N_COLS); + round(fwd_lrnd, b0, b1, kp +10 * N_COLS); + } + +#else + +#if (ENC_UNROLL == PARTIAL) + { aes_32t rnd; + for(rnd = 0; rnd < (nr >> 1) - 1; ++rnd) + { + kp += N_COLS; + round(fwd_rnd, b1, b0, kp); + kp += N_COLS; + round(fwd_rnd, b0, b1, kp); + } + kp += N_COLS; + round(fwd_rnd, b1, b0, kp); +#else + { aes_32t rnd; + for(rnd = 0; rnd < nr - 1; ++rnd) + { + kp += N_COLS; + round(fwd_rnd, b1, b0, kp); + l_copy(b0, b1); + } +#endif + kp += N_COLS; + round(fwd_lrnd, b0, b1, kp); + } +#endif + + state_out(out_blk, b0); +#ifdef AES_ERR_CHK + return aes_good; +#endif +} + +#endif + +#if defined(DECRYPTION) && !defined(AES_ASM) + +/* Visual C++ .Net v7.1 provides the fastest encryption code when using + Pentium optimiation with small code but this is poor for decryption + so we need to control this with the following VC++ pragmas +*/ + +#if defined(_MSC_VER) +#pragma optimize( "t", on ) +#endif + +/* Given the column (c) of the output state variable, the following + macros give the input state variables which are needed in its + computation for each row (r) of the state. All the alternative + macros give the same end values but expand into different ways + of calculating these values. In particular the complex macro + used for dynamically variable block sizes is designed to expand + to a compile time constant whenever possible but will expand to + conditional clauses on some branches (I am grateful to Frank + Yellin for this construction) +*/ + +#define inv_var(x,r,c)\ + ( r == 0 ? ( c == 0 ? s(x,0) : c == 1 ? s(x,1) : c == 2 ? s(x,2) : s(x,3))\ + : r == 1 ? ( c == 0 ? s(x,3) : c == 1 ? s(x,0) : c == 2 ? s(x,1) : s(x,2))\ + : r == 2 ? ( c == 0 ? s(x,2) : c == 1 ? s(x,3) : c == 2 ? s(x,0) : s(x,1))\ + : ( c == 0 ? s(x,1) : c == 1 ? s(x,2) : c == 2 ? s(x,3) : s(x,0))) + +#if defined(IT4_SET) +#undef dec_imvars +#define inv_rnd(y,x,k,c) (s(y,c) = (k)[c] ^ four_tables(x,t_use(i,n),inv_var,rf1,c)) +#elif defined(IT1_SET) +#undef dec_imvars +#define inv_rnd(y,x,k,c) (s(y,c) = (k)[c] ^ one_table(x,upr,t_use(i,n),inv_var,rf1,c)) +#else +#define inv_rnd(y,x,k,c) (s(y,c) = inv_mcol((k)[c] ^ no_table(x,t_use(i,box),inv_var,rf1,c))) +#endif + +#if defined(IL4_SET) +#define inv_lrnd(y,x,k,c) (s(y,c) = (k)[c] ^ four_tables(x,t_use(i,l),inv_var,rf1,c)) +#elif defined(IL1_SET) +#define inv_lrnd(y,x,k,c) (s(y,c) = (k)[c] ^ one_table(x,ups,t_use(i,l),inv_var,rf1,c)) +#else +#define inv_lrnd(y,x,k,c) (s(y,c) = (k)[c] ^ no_table(x,t_use(i,box),inv_var,rf1,c)) +#endif + +aes_rval aes_decrypt(const void *in_blk, void *out_blk, const aes_decrypt_ctx cx[1]) +{ aes_32t locals(b0, b1); +#ifdef dec_imvars + dec_imvars; /* declare variables for inv_mcol() if needed */ +#endif + + aes_32t nr = (cx->ks[45] ^ cx->ks[52] ^ cx->ks[53] ? cx->ks[52] : 14); + const aes_32t *kp = cx->ks + nr * N_COLS; + +#ifdef AES_ERR_CHK + if( (nr != 10 || !(cx->ks[0] | cx->ks[3] | cx->ks[4])) + && (nr != 12 || !(cx->ks[0] | cx->ks[5] | cx->ks[6])) + && (nr != 14 || !(cx->ks[0] | cx->ks[7] | cx->ks[8])) ) + return aes_error; +#endif + + state_in(b0, in_blk, kp); + +#if (DEC_UNROLL == FULL) + + switch(nr) + { + case 14: + round(inv_rnd, b1, b0, kp - 1 * N_COLS); + round(inv_rnd, b0, b1, kp - 2 * N_COLS); + kp -= 2 * N_COLS; + case 12: + round(inv_rnd, b1, b0, kp - 1 * N_COLS); + round(inv_rnd, b0, b1, kp - 2 * N_COLS); + kp -= 2 * N_COLS; + case 10: + round(inv_rnd, b1, b0, kp - 1 * N_COLS); + round(inv_rnd, b0, b1, kp - 2 * N_COLS); + round(inv_rnd, b1, b0, kp - 3 * N_COLS); + round(inv_rnd, b0, b1, kp - 4 * N_COLS); + round(inv_rnd, b1, b0, kp - 5 * N_COLS); + round(inv_rnd, b0, b1, kp - 6 * N_COLS); + round(inv_rnd, b1, b0, kp - 7 * N_COLS); + round(inv_rnd, b0, b1, kp - 8 * N_COLS); + round(inv_rnd, b1, b0, kp - 9 * N_COLS); + round(inv_lrnd, b0, b1, kp - 10 * N_COLS); + } + +#else + +#if (DEC_UNROLL == PARTIAL) + { aes_32t rnd; + for(rnd = 0; rnd < (nr >> 1) - 1; ++rnd) + { + kp -= N_COLS; + round(inv_rnd, b1, b0, kp); + kp -= N_COLS; + round(inv_rnd, b0, b1, kp); + } + kp -= N_COLS; + round(inv_rnd, b1, b0, kp); +#else + { aes_32t rnd; + for(rnd = 0; rnd < nr - 1; ++rnd) + { + kp -= N_COLS; + round(inv_rnd, b1, b0, kp); + l_copy(b0, b1); + } +#endif + kp -= N_COLS; + round(inv_lrnd, b0, b1, kp); + } +#endif + + state_out(out_blk, b0); +#ifdef AES_ERR_CHK + return aes_good; +#endif +} + +#endif + +#if defined(__cplusplus) +} +#endif diff --git a/main/aeskey.c b/main/aeskey.c new file mode 100644 index 000000000..d34badc6b --- /dev/null +++ b/main/aeskey.c @@ -0,0 +1,469 @@ +/* + --------------------------------------------------------------------------- + Copyright (c) 2003, Dr Brian Gladman , Worcester, UK. + All rights reserved. + + LICENSE TERMS + + The free distribution and use of this software in both source and binary + form is allowed (with or without changes) provided that: + + 1. distributions of this source code include the above copyright + notice, this list of conditions and the following disclaimer; + + 2. distributions in binary form include the above copyright + notice, this list of conditions and the following disclaimer + in the documentation and/or other associated materials; + + 3. the copyright holder's name is not used to endorse products + built using this software without specific written permission. + + ALTERNATIVELY, provided that this notice is retained in full, this product + may be distributed under the terms of the GNU General Public License (GPL), + in which case the provisions of the GPL apply INSTEAD OF those given above. + + DISCLAIMER + + This software is provided 'as is' with no explicit or implied warranties + in respect of its properties, including, but not limited to, correctness + and/or fitness for purpose. + --------------------------------------------------------------------------- + Issue Date: 26/08/2003 + +*/ + +/*! \file + * + * \brief This file contains the code for implementing the key schedule for AES + * (Rijndael) for block and key sizes of 16, 24, and 32 bytes. See aesopt.h + * for further details including optimisation. + * + * \author Dr Brian Gladman + */ + +#include "aesopt.h" + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/* Initialise the key schedule from the user supplied key. The key + length can be specified in bytes, with legal values of 16, 24 + and 32, or in bits, with legal values of 128, 192 and 256. These + values correspond with Nk values of 4, 6 and 8 respectively. + + The following macros implement a single cycle in the key + schedule generation process. The number of cycles needed + for each cx->n_col and nk value is: + + nk = 4 5 6 7 8 + ------------------------------ + cx->n_col = 4 10 9 8 7 7 + cx->n_col = 5 14 11 10 9 9 + cx->n_col = 6 19 15 12 11 11 + cx->n_col = 7 21 19 16 13 14 + cx->n_col = 8 29 23 19 17 14 +*/ + +#define ke4(k,i) \ +{ k[4*(i)+4] = ss[0] ^= ls_box(ss[3],3) ^ t_use(r,c)[i]; k[4*(i)+5] = ss[1] ^= ss[0]; \ + k[4*(i)+6] = ss[2] ^= ss[1]; k[4*(i)+7] = ss[3] ^= ss[2]; \ +} +#define kel4(k,i) \ +{ k[4*(i)+4] = ss[0] ^= ls_box(ss[3],3) ^ t_use(r,c)[i]; k[4*(i)+5] = ss[1] ^= ss[0]; \ + k[4*(i)+6] = ss[2] ^= ss[1]; k[4*(i)+7] = ss[3] ^= ss[2]; \ +} + +#define ke6(k,i) \ +{ k[6*(i)+ 6] = ss[0] ^= ls_box(ss[5],3) ^ t_use(r,c)[i]; k[6*(i)+ 7] = ss[1] ^= ss[0]; \ + k[6*(i)+ 8] = ss[2] ^= ss[1]; k[6*(i)+ 9] = ss[3] ^= ss[2]; \ + k[6*(i)+10] = ss[4] ^= ss[3]; k[6*(i)+11] = ss[5] ^= ss[4]; \ +} +#define kel6(k,i) \ +{ k[6*(i)+ 6] = ss[0] ^= ls_box(ss[5],3) ^ t_use(r,c)[i]; k[6*(i)+ 7] = ss[1] ^= ss[0]; \ + k[6*(i)+ 8] = ss[2] ^= ss[1]; k[6*(i)+ 9] = ss[3] ^= ss[2]; \ +} + +#define ke8(k,i) \ +{ k[8*(i)+ 8] = ss[0] ^= ls_box(ss[7],3) ^ t_use(r,c)[i]; k[8*(i)+ 9] = ss[1] ^= ss[0]; \ + k[8*(i)+10] = ss[2] ^= ss[1]; k[8*(i)+11] = ss[3] ^= ss[2]; \ + k[8*(i)+12] = ss[4] ^= ls_box(ss[3],0); k[8*(i)+13] = ss[5] ^= ss[4]; \ + k[8*(i)+14] = ss[6] ^= ss[5]; k[8*(i)+15] = ss[7] ^= ss[6]; \ +} +#define kel8(k,i) \ +{ k[8*(i)+ 8] = ss[0] ^= ls_box(ss[7],3) ^ t_use(r,c)[i]; k[8*(i)+ 9] = ss[1] ^= ss[0]; \ + k[8*(i)+10] = ss[2] ^= ss[1]; k[8*(i)+11] = ss[3] ^= ss[2]; \ +} + +#if defined(ENCRYPTION_KEY_SCHEDULE) + +#if defined(AES_128) || defined(AES_VAR) + +aes_rval aes_encrypt_key128(const void *in_key, aes_encrypt_ctx cx[1]) +{ aes_32t ss[4]; + + cx->ks[0] = ss[0] = word_in(in_key, 0); + cx->ks[1] = ss[1] = word_in(in_key, 1); + cx->ks[2] = ss[2] = word_in(in_key, 2); + cx->ks[3] = ss[3] = word_in(in_key, 3); + +#if ENC_UNROLL == NONE + { aes_32t i; + + for(i = 0; i < ((11 * N_COLS - 1) / 4); ++i) + ke4(cx->ks, i); + } +#else + ke4(cx->ks, 0); ke4(cx->ks, 1); + ke4(cx->ks, 2); ke4(cx->ks, 3); + ke4(cx->ks, 4); ke4(cx->ks, 5); + ke4(cx->ks, 6); ke4(cx->ks, 7); + ke4(cx->ks, 8); kel4(cx->ks, 9); +#endif + + /* cx->ks[45] ^ cx->ks[52] ^ cx->ks[53] is zero for a 256 bit */ + /* key and must be non-zero for 128 and 192 bits keys */ + cx->ks[53] = cx->ks[45] = 0; + cx->ks[52] = 10; +#ifdef AES_ERR_CHK + return aes_good; +#endif +} + +#endif + +#if defined(AES_192) || defined(AES_VAR) + +aes_rval aes_encrypt_key192(const void *in_key, aes_encrypt_ctx cx[1]) +{ aes_32t ss[6]; + + cx->ks[0] = ss[0] = word_in(in_key, 0); + cx->ks[1] = ss[1] = word_in(in_key, 1); + cx->ks[2] = ss[2] = word_in(in_key, 2); + cx->ks[3] = ss[3] = word_in(in_key, 3); + cx->ks[4] = ss[4] = word_in(in_key, 4); + cx->ks[5] = ss[5] = word_in(in_key, 5); + +#if ENC_UNROLL == NONE + { aes_32t i; + + for(i = 0; i < (13 * N_COLS - 1) / 6; ++i) + ke6(cx->ks, i); + } +#else + ke6(cx->ks, 0); ke6(cx->ks, 1); + ke6(cx->ks, 2); ke6(cx->ks, 3); + ke6(cx->ks, 4); ke6(cx->ks, 5); + ke6(cx->ks, 6); kel6(cx->ks, 7); +#endif + + /* cx->ks[45] ^ cx->ks[52] ^ cx->ks[53] is zero for a 256 bit */ + /* key and must be non-zero for 128 and 192 bits keys */ + cx->ks[53] = cx->ks[45]; + cx->ks[52] = 12; +#ifdef AES_ERR_CHK + return aes_good; +#endif +} + +#endif + +#if defined(AES_256) || defined(AES_VAR) + +aes_rval aes_encrypt_key256(const void *in_key, aes_encrypt_ctx cx[1]) +{ aes_32t ss[8]; + + cx->ks[0] = ss[0] = word_in(in_key, 0); + cx->ks[1] = ss[1] = word_in(in_key, 1); + cx->ks[2] = ss[2] = word_in(in_key, 2); + cx->ks[3] = ss[3] = word_in(in_key, 3); + cx->ks[4] = ss[4] = word_in(in_key, 4); + cx->ks[5] = ss[5] = word_in(in_key, 5); + cx->ks[6] = ss[6] = word_in(in_key, 6); + cx->ks[7] = ss[7] = word_in(in_key, 7); + +#if ENC_UNROLL == NONE + { aes_32t i; + + for(i = 0; i < (15 * N_COLS - 1) / 8; ++i) + ke8(cx->ks, i); + } +#else + ke8(cx->ks, 0); ke8(cx->ks, 1); + ke8(cx->ks, 2); ke8(cx->ks, 3); + ke8(cx->ks, 4); ke8(cx->ks, 5); + kel8(cx->ks, 6); +#endif +#ifdef AES_ERR_CHK + return aes_good; +#endif +} + +#endif + +#if defined(AES_VAR) + +aes_rval aes_encrypt_key(const void *in_key, int key_len, aes_encrypt_ctx cx[1]) +{ + switch(key_len) + { +#ifdef AES_ERR_CHK + case 16: case 128: return aes_encrypt_key128(in_key, cx); + case 24: case 192: return aes_encrypt_key192(in_key, cx); + case 32: case 256: return aes_encrypt_key256(in_key, cx); + default: return aes_error; +#else + case 16: case 128: aes_encrypt_key128(in_key, cx); return; + case 24: case 192: aes_encrypt_key192(in_key, cx); return; + case 32: case 256: aes_encrypt_key256(in_key, cx); return; +#endif + } +} + +#endif + +#endif + +#if defined(DECRYPTION_KEY_SCHEDULE) + +#if DEC_ROUND == NO_TABLES +#define ff(x) (x) +#else +#define ff(x) inv_mcol(x) +#ifdef dec_imvars +#define d_vars dec_imvars +#endif +#endif + +#if 1 +#define kdf4(k,i) \ +{ ss[0] = ss[0] ^ ss[2] ^ ss[1] ^ ss[3]; ss[1] = ss[1] ^ ss[3]; ss[2] = ss[2] ^ ss[3]; ss[3] = ss[3]; \ + ss[4] = ls_box(ss[(i+3) % 4], 3) ^ t_use(r,c)[i]; ss[i % 4] ^= ss[4]; \ + ss[4] ^= k[4*(i)]; k[4*(i)+4] = ff(ss[4]); ss[4] ^= k[4*(i)+1]; k[4*(i)+5] = ff(ss[4]); \ + ss[4] ^= k[4*(i)+2]; k[4*(i)+6] = ff(ss[4]); ss[4] ^= k[4*(i)+3]; k[4*(i)+7] = ff(ss[4]); \ +} +#define kd4(k,i) \ +{ ss[4] = ls_box(ss[(i+3) % 4], 3) ^ t_use(r,c)[i]; ss[i % 4] ^= ss[4]; ss[4] = ff(ss[4]); \ + k[4*(i)+4] = ss[4] ^= k[4*(i)]; k[4*(i)+5] = ss[4] ^= k[4*(i)+1]; \ + k[4*(i)+6] = ss[4] ^= k[4*(i)+2]; k[4*(i)+7] = ss[4] ^= k[4*(i)+3]; \ +} +#define kdl4(k,i) \ +{ ss[4] = ls_box(ss[(i+3) % 4], 3) ^ t_use(r,c)[i]; ss[i % 4] ^= ss[4]; \ + k[4*(i)+4] = (ss[0] ^= ss[1]) ^ ss[2] ^ ss[3]; k[4*(i)+5] = ss[1] ^ ss[3]; \ + k[4*(i)+6] = ss[0]; k[4*(i)+7] = ss[1]; \ +} +#else +#define kdf4(k,i) \ +{ ss[0] ^= ls_box(ss[3],3) ^ t_use(r,c)[i]; k[4*(i)+ 4] = ff(ss[0]); ss[1] ^= ss[0]; k[4*(i)+ 5] = ff(ss[1]); \ + ss[2] ^= ss[1]; k[4*(i)+ 6] = ff(ss[2]); ss[3] ^= ss[2]; k[4*(i)+ 7] = ff(ss[3]); \ +} +#define kd4(k,i) \ +{ ss[4] = ls_box(ss[3],3) ^ t_use(r,c)[i]; \ + ss[0] ^= ss[4]; ss[4] = ff(ss[4]); k[4*(i)+ 4] = ss[4] ^= k[4*(i)]; \ + ss[1] ^= ss[0]; k[4*(i)+ 5] = ss[4] ^= k[4*(i)+ 1]; \ + ss[2] ^= ss[1]; k[4*(i)+ 6] = ss[4] ^= k[4*(i)+ 2]; \ + ss[3] ^= ss[2]; k[4*(i)+ 7] = ss[4] ^= k[4*(i)+ 3]; \ +} +#define kdl4(k,i) \ +{ ss[0] ^= ls_box(ss[3],3) ^ t_use(r,c)[i]; k[4*(i)+ 4] = ss[0]; ss[1] ^= ss[0]; k[4*(i)+ 5] = ss[1]; \ + ss[2] ^= ss[1]; k[4*(i)+ 6] = ss[2]; ss[3] ^= ss[2]; k[4*(i)+ 7] = ss[3]; \ +} +#endif + +#define kdf6(k,i) \ +{ ss[0] ^= ls_box(ss[5],3) ^ t_use(r,c)[i]; k[6*(i)+ 6] = ff(ss[0]); ss[1] ^= ss[0]; k[6*(i)+ 7] = ff(ss[1]); \ + ss[2] ^= ss[1]; k[6*(i)+ 8] = ff(ss[2]); ss[3] ^= ss[2]; k[6*(i)+ 9] = ff(ss[3]); \ + ss[4] ^= ss[3]; k[6*(i)+10] = ff(ss[4]); ss[5] ^= ss[4]; k[6*(i)+11] = ff(ss[5]); \ +} +#define kd6(k,i) \ +{ ss[6] = ls_box(ss[5],3) ^ t_use(r,c)[i]; \ + ss[0] ^= ss[6]; ss[6] = ff(ss[6]); k[6*(i)+ 6] = ss[6] ^= k[6*(i)]; \ + ss[1] ^= ss[0]; k[6*(i)+ 7] = ss[6] ^= k[6*(i)+ 1]; \ + ss[2] ^= ss[1]; k[6*(i)+ 8] = ss[6] ^= k[6*(i)+ 2]; \ + ss[3] ^= ss[2]; k[6*(i)+ 9] = ss[6] ^= k[6*(i)+ 3]; \ + ss[4] ^= ss[3]; k[6*(i)+10] = ss[6] ^= k[6*(i)+ 4]; \ + ss[5] ^= ss[4]; k[6*(i)+11] = ss[6] ^= k[6*(i)+ 5]; \ +} +#define kdl6(k,i) \ +{ ss[0] ^= ls_box(ss[5],3) ^ t_use(r,c)[i]; k[6*(i)+ 6] = ss[0]; ss[1] ^= ss[0]; k[6*(i)+ 7] = ss[1]; \ + ss[2] ^= ss[1]; k[6*(i)+ 8] = ss[2]; ss[3] ^= ss[2]; k[6*(i)+ 9] = ss[3]; \ +} + +#define kdf8(k,i) \ +{ ss[0] ^= ls_box(ss[7],3) ^ t_use(r,c)[i]; k[8*(i)+ 8] = ff(ss[0]); ss[1] ^= ss[0]; k[8*(i)+ 9] = ff(ss[1]); \ + ss[2] ^= ss[1]; k[8*(i)+10] = ff(ss[2]); ss[3] ^= ss[2]; k[8*(i)+11] = ff(ss[3]); \ + ss[4] ^= ls_box(ss[3],0); k[8*(i)+12] = ff(ss[4]); ss[5] ^= ss[4]; k[8*(i)+13] = ff(ss[5]); \ + ss[6] ^= ss[5]; k[8*(i)+14] = ff(ss[6]); ss[7] ^= ss[6]; k[8*(i)+15] = ff(ss[7]); \ +} +#define kd8(k,i) \ +{ aes_32t g = ls_box(ss[7],3) ^ t_use(r,c)[i]; \ + ss[0] ^= g; g = ff(g); k[8*(i)+ 8] = g ^= k[8*(i)]; \ + ss[1] ^= ss[0]; k[8*(i)+ 9] = g ^= k[8*(i)+ 1]; \ + ss[2] ^= ss[1]; k[8*(i)+10] = g ^= k[8*(i)+ 2]; \ + ss[3] ^= ss[2]; k[8*(i)+11] = g ^= k[8*(i)+ 3]; \ + g = ls_box(ss[3],0); \ + ss[4] ^= g; g = ff(g); k[8*(i)+12] = g ^= k[8*(i)+ 4]; \ + ss[5] ^= ss[4]; k[8*(i)+13] = g ^= k[8*(i)+ 5]; \ + ss[6] ^= ss[5]; k[8*(i)+14] = g ^= k[8*(i)+ 6]; \ + ss[7] ^= ss[6]; k[8*(i)+15] = g ^= k[8*(i)+ 7]; \ +} +#define kdl8(k,i) \ +{ ss[0] ^= ls_box(ss[7],3) ^ t_use(r,c)[i]; k[8*(i)+ 8] = ss[0]; ss[1] ^= ss[0]; k[8*(i)+ 9] = ss[1]; \ + ss[2] ^= ss[1]; k[8*(i)+10] = ss[2]; ss[3] ^= ss[2]; k[8*(i)+11] = ss[3]; \ +} + +#if defined(AES_128) || defined(AES_VAR) + +aes_rval aes_decrypt_key128(const void *in_key, aes_decrypt_ctx cx[1]) +{ aes_32t ss[5]; +#ifdef d_vars + d_vars; +#endif + cx->ks[0] = ss[0] = word_in(in_key, 0); + cx->ks[1] = ss[1] = word_in(in_key, 1); + cx->ks[2] = ss[2] = word_in(in_key, 2); + cx->ks[3] = ss[3] = word_in(in_key, 3); + +#if DEC_UNROLL == NONE + { aes_32t i; + + for(i = 0; i < (11 * N_COLS - 1) / 4; ++i) + ke4(cx->ks, i); +#if !(DEC_ROUND == NO_TABLES) + for(i = N_COLS; i < 10 * N_COLS; ++i) + cx->ks[i] = inv_mcol(cx->ks[i]); +#endif + } +#else + kdf4(cx->ks, 0); kd4(cx->ks, 1); + kd4(cx->ks, 2); kd4(cx->ks, 3); + kd4(cx->ks, 4); kd4(cx->ks, 5); + kd4(cx->ks, 6); kd4(cx->ks, 7); + kd4(cx->ks, 8); kdl4(cx->ks, 9); +#endif + + /* cx->ks[45] ^ cx->ks[52] ^ cx->ks[53] is zero for a 256 bit */ + /* key and must be non-zero for 128 and 192 bits keys */ + cx->ks[53] = cx->ks[45] = 0; + cx->ks[52] = 10; +#ifdef AES_ERR_CHK + return aes_good; +#endif +} + +#endif + +#if defined(AES_192) || defined(AES_VAR) + +aes_rval aes_decrypt_key192(const void *in_key, aes_decrypt_ctx cx[1]) +{ aes_32t ss[7]; +#ifdef d_vars + d_vars; +#endif + cx->ks[0] = ss[0] = word_in(in_key, 0); + cx->ks[1] = ss[1] = word_in(in_key, 1); + cx->ks[2] = ss[2] = word_in(in_key, 2); + cx->ks[3] = ss[3] = word_in(in_key, 3); + +#if DEC_UNROLL == NONE + cx->ks[4] = ss[4] = word_in(in_key, 4); + cx->ks[5] = ss[5] = word_in(in_key, 5); + { aes_32t i; + + for(i = 0; i < (13 * N_COLS - 1) / 6; ++i) + ke6(cx->ks, i); +#if !(DEC_ROUND == NO_TABLES) + for(i = N_COLS; i < 12 * N_COLS; ++i) + cx->ks[i] = inv_mcol(cx->ks[i]); +#endif + } +#else + cx->ks[4] = ff(ss[4] = word_in(in_key, 4)); + cx->ks[5] = ff(ss[5] = word_in(in_key, 5)); + kdf6(cx->ks, 0); kd6(cx->ks, 1); + kd6(cx->ks, 2); kd6(cx->ks, 3); + kd6(cx->ks, 4); kd6(cx->ks, 5); + kd6(cx->ks, 6); kdl6(cx->ks, 7); +#endif + + /* cx->ks[45] ^ cx->ks[52] ^ cx->ks[53] is zero for a 256 bit */ + /* key and must be non-zero for 128 and 192 bits keys */ + cx->ks[53] = cx->ks[45]; + cx->ks[52] = 12; +#ifdef AES_ERR_CHK + return aes_good; +#endif +} + +#endif + +#if defined(AES_256) || defined(AES_VAR) + +aes_rval aes_decrypt_key256(const void *in_key, aes_decrypt_ctx cx[1]) +{ aes_32t ss[8]; +#ifdef d_vars + d_vars; +#endif + cx->ks[0] = ss[0] = word_in(in_key, 0); + cx->ks[1] = ss[1] = word_in(in_key, 1); + cx->ks[2] = ss[2] = word_in(in_key, 2); + cx->ks[3] = ss[3] = word_in(in_key, 3); + +#if DEC_UNROLL == NONE + cx->ks[4] = ss[4] = word_in(in_key, 4); + cx->ks[5] = ss[5] = word_in(in_key, 5); + cx->ks[6] = ss[6] = word_in(in_key, 6); + cx->ks[7] = ss[7] = word_in(in_key, 7); + { aes_32t i; + + for(i = 0; i < (15 * N_COLS - 1) / 8; ++i) + ke8(cx->ks, i); +#if !(DEC_ROUND == NO_TABLES) + for(i = N_COLS; i < 14 * N_COLS; ++i) + cx->ks[i] = inv_mcol(cx->ks[i]); +#endif + } +#else + cx->ks[4] = ff(ss[4] = word_in(in_key, 4)); + cx->ks[5] = ff(ss[5] = word_in(in_key, 5)); + cx->ks[6] = ff(ss[6] = word_in(in_key, 6)); + cx->ks[7] = ff(ss[7] = word_in(in_key, 7)); + kdf8(cx->ks, 0); kd8(cx->ks, 1); + kd8(cx->ks, 2); kd8(cx->ks, 3); + kd8(cx->ks, 4); kd8(cx->ks, 5); + kdl8(cx->ks, 6); +#endif +#ifdef AES_ERR_CHK + return aes_good; +#endif +} + +#endif + +#if defined(AES_VAR) + +aes_rval aes_decrypt_key(const void *in_key, int key_len, aes_decrypt_ctx cx[1]) +{ + switch(key_len) + { +#ifdef AES_ERR_CHK + case 16: case 128: return aes_decrypt_key128(in_key, cx); + case 24: case 192: return aes_decrypt_key192(in_key, cx); + case 32: case 256: return aes_decrypt_key256(in_key, cx); + default: return aes_error; +#else + case 16: case 128: aes_decrypt_key128(in_key, cx); return; + case 24: case 192: aes_decrypt_key192(in_key, cx); return; + case 32: case 256: aes_decrypt_key256(in_key, cx); return; +#endif + } +} + +#endif + +#endif + +#if defined(__cplusplus) +} +#endif diff --git a/main/aesopt.h b/main/aesopt.h new file mode 100644 index 000000000..bb4f05a0b --- /dev/null +++ b/main/aesopt.h @@ -0,0 +1,1029 @@ +/* + --------------------------------------------------------------------------- + Copyright (c) 2003, Dr Brian Gladman , Worcester, UK. + All rights reserved. + + LICENSE TERMS + + The free distribution and use of this software in both source and binary + form is allowed (with or without changes) provided that: + + 1. distributions of this source code include the above copyright + notice, this list of conditions and the following disclaimer; + + 2. distributions in binary form include the above copyright + notice, this list of conditions and the following disclaimer + in the documentation and/or other associated materials; + + 3. the copyright holder's name is not used to endorse products + built using this software without specific written permission. + + ALTERNATIVELY, provided that this notice is retained in full, this product + may be distributed under the terms of the GNU General Public License (GPL), + in which case the provisions of the GPL apply INSTEAD OF those given above. + + DISCLAIMER + + This software is provided 'as is' with no explicit or implied warranties + in respect of its properties, including, but not limited to, correctness + and/or fitness for purpose. + --------------------------------------------------------------------------- + Issue Date: 26/08/2003 + + My thanks go to Dag Arne Osvik for devising the schemes used here for key + length derivation from the form of the key schedule + + This file contains the compilation options for AES (Rijndael) and code + that is common across encryption, key scheduling and table generation. + + OPERATION + + These source code files implement the AES algorithm Rijndael designed by + Joan Daemen and Vincent Rijmen. This version is designed for the standard + block size of 16 bytes and for key sizes of 128, 192 and 256 bits (16, 24 + and 32 bytes). + + This version is designed for flexibility and speed using operations on + 32-bit words rather than operations on bytes. It can be compiled with + either big or little endian internal byte order but is faster when the + native byte order for the processor is used. + + THE CIPHER INTERFACE + + The cipher interface is implemented as an array of bytes in which lower + AES bit sequence indexes map to higher numeric significance within bytes. + + aes_08t (an unsigned 8-bit type) + aes_32t (an unsigned 32-bit type) + struct aes_encrypt_ctx (structure for the cipher encryption context) + struct aes_decrypt_ctx (structure for the cipher decryption context) + aes_rval the function return type + + C subroutine calls: + + aes_rval aes_encrypt_key128(const void *in_key, aes_encrypt_ctx cx[1]); + aes_rval aes_encrypt_key192(const void *in_key, aes_encrypt_ctx cx[1]); + aes_rval aes_encrypt_key256(const void *in_key, aes_encrypt_ctx cx[1]); + aes_rval aes_encrypt(const void *in_blk, + void *out_blk, const aes_encrypt_ctx cx[1]); + + aes_rval aes_decrypt_key128(const void *in_key, aes_decrypt_ctx cx[1]); + aes_rval aes_decrypt_key192(const void *in_key, aes_decrypt_ctx cx[1]); + aes_rval aes_decrypt_key256(const void *in_key, aes_decrypt_ctx cx[1]); + aes_rval aes_decrypt(const void *in_blk, + void *out_blk, const aes_decrypt_ctx cx[1]); + + IMPORTANT NOTE: If you are using this C interface with dynamic tables make sure that + you call genTabs() before AES is used so that the tables are initialised. + + C++ aes class subroutines: + + Class AESencrypt for encryption + + Construtors: + AESencrypt(void) + AESencrypt(const void *in_key) - 128 bit key + Members: + void key128(const void *in_key) + void key192(const void *in_key) + void key256(const void *in_key) + void encrypt(const void *in_blk, void *out_blk) const + + Class AESdecrypt for encryption + Construtors: + AESdecrypt(void) + AESdecrypt(const void *in_key) - 128 bit key + Members: + void key128(const void *in_key) + void key192(const void *in_key) + void key256(const void *in_key) + void decrypt(const void *in_blk, void *out_blk) const + + COMPILATION + + The files used to provide AES (Rijndael) are + + a. aes.h for the definitions needed for use in C. + b. aescpp.h for the definitions needed for use in C++. + c. aesopt.h for setting compilation options (also includes common code). + d. aescrypt.c for encryption and decrytpion, or + e. aeskey.c for key scheduling. + f. aestab.c for table loading or generation. + g. aescrypt.asm for encryption and decryption using assembler code. + h. aescrypt.mmx.asm for encryption and decryption using MMX assembler. + + To compile AES (Rijndael) for use in C code use aes.h and set the + defines here for the facilities you need (key lengths, encryption + and/or decryption). Do not define AES_DLL or AES_CPP. Set the options + for optimisations and table sizes here. + + To compile AES (Rijndael) for use in in C++ code use aescpp.h but do + not define AES_DLL + + To compile AES (Rijndael) in C as a Dynamic Link Library DLL) use + aes.h and include the AES_DLL define. + + CONFIGURATION OPTIONS (here and in aes.h) + + a. set AES_DLL in aes.h if AES (Rijndael) is to be compiled as a DLL + b. You may need to set PLATFORM_BYTE_ORDER to define the byte order. + c. If you want the code to run in a specific internal byte order, then + ALGORITHM_BYTE_ORDER must be set accordingly. + d. set other configuration options decribed below. +*/ + +#ifndef _AESOPT_H +#define _AESOPT_H + +#include "asterisk/aes.h" +#include "asterisk/endian.h" + +/* CONFIGURATION - USE OF DEFINES + + Later in this section there are a number of defines that control the + operation of the code. In each section, the purpose of each define is + explained so that the relevant form can be included or excluded by + setting either 1's or 0's respectively on the branches of the related + #if clauses. +*/ + +/* BYTE ORDER IN 32-BIT WORDS + + To obtain the highest speed on processors with 32-bit words, this code + needs to determine the byte order of the target machine. The following + block of code is an attempt to capture the most obvious ways in which + various environemnts define byte order. It may well fail, in which case + the definitions will need to be set by editing at the points marked + **** EDIT HERE IF NECESSARY **** below. My thanks to Peter Gutmann for + some of these defines (from cryptlib). +*/ + +#define BRG_LITTLE_ENDIAN 1234 /* byte 0 is least significant (i386) */ +#define BRG_BIG_ENDIAN 4321 /* byte 0 is most significant (mc68k) */ + +#if defined( __alpha__ ) || defined( __alpha ) || defined( i386 ) || \ + defined( __i386__ ) || defined( _M_I86 ) || defined( _M_IX86 ) || \ + defined( __OS2__ ) || defined( sun386 ) || defined( __TURBOC__ ) || \ + defined( vax ) || defined( vms ) || defined( VMS ) || \ + defined( __VMS ) + +#define PLATFORM_BYTE_ORDER BRG_LITTLE_ENDIAN + +#endif + +#if defined( AMIGA ) || defined( applec ) || defined( __AS400__ ) || \ + defined( _CRAY ) || defined( __hppa ) || defined( __hp9000 ) || \ + defined( ibm370 ) || defined( mc68000 ) || defined( m68k ) || \ + defined( __MRC__ ) || defined( __MVS__ ) || defined( __MWERKS__ ) || \ + defined( sparc ) || defined( __sparc) || defined( SYMANTEC_C ) || \ + defined( __TANDEM ) || defined( THINK_C ) || defined( __VMCMS__ ) + +#define PLATFORM_BYTE_ORDER BRG_BIG_ENDIAN + +#endif + +/* if the platform is still not known, try to find its byte order */ +/* from commonly used definitions in the headers included earlier */ + +#if !defined(PLATFORM_BYTE_ORDER) + +#if defined(LITTLE_ENDIAN) || defined(BIG_ENDIAN) +# if defined(LITTLE_ENDIAN) && !defined(BIG_ENDIAN) +# define PLATFORM_BYTE_ORDER BRG_LITTLE_ENDIAN +# elif !defined(LITTLE_ENDIAN) && defined(BIG_ENDIAN) +# define PLATFORM_BYTE_ORDER BRG_BIG_ENDIAN +# elif defined(BYTE_ORDER) && (BYTE_ORDER == LITTLE_ENDIAN) +# define PLATFORM_BYTE_ORDER BRG_LITTLE_ENDIAN +# elif defined(BYTE_ORDER) && (BYTE_ORDER == BIG_ENDIAN) +# define PLATFORM_BYTE_ORDER BRG_BIG_ENDIAN +# endif + +#elif defined(_LITTLE_ENDIAN) || defined(_BIG_ENDIAN) +# if defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN) +# define PLATFORM_BYTE_ORDER BRG_LITTLE_ENDIAN +# elif !defined(_LITTLE_ENDIAN) && defined(_BIG_ENDIAN) +# define PLATFORM_BYTE_ORDER BRG_BIG_ENDIAN +# elif defined(_BYTE_ORDER) && (_BYTE_ORDER == _LITTLE_ENDIAN) +# define PLATFORM_BYTE_ORDER BRG_LITTLE_ENDIAN +# elif defined(_BYTE_ORDER) && (_BYTE_ORDER == _BIG_ENDIAN) +# define PLATFORM_BYTE_ORDER BRG_BIG_ENDIAN +# endif + +#elif defined(__LITTLE_ENDIAN__) || defined(__BIG_ENDIAN__) +# if defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__) +# define PLATFORM_BYTE_ORDER BRG_LITTLE_ENDIAN +# elif !defined(__LITTLE_ENDIAN__) && defined(__BIG_ENDIAN__) +# define PLATFORM_BYTE_ORDER BRG_BIG_ENDIAN +# elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __LITTLE_ENDIAN__) +# define PLATFORM_BYTE_ORDER BRG_LITTLE_ENDIAN +# elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __BIG_ENDIAN__) +# define PLATFORM_BYTE_ORDER BRG_BIG_ENDIAN +# endif + +#elif 0 /* **** EDIT HERE IF NECESSARY **** */ +#define PLATFORM_BYTE_ORDER BRG_LITTLE_ENDIAN + +#elif 0 /* **** EDIT HERE IF NECESSARY **** */ +#define PLATFORM_BYTE_ORDER BRG_BIG_ENDIAN + +#else +#error Please edit aesopt.h (line 235 or 238) to set the platform byte order +#endif + +#endif + +/* SOME LOCAL DEFINITIONS */ + +#define NO_TABLES 0 +#define ONE_TABLE 1 +#define FOUR_TABLES 4 +#define NONE 0 +#define PARTIAL 1 +#define FULL 2 + +#if defined(bswap32) +#define aes_sw32 bswap32 +#elif defined(bswap_32) +#define aes_sw32 bswap_32 +#else +#define brot(x,n) (((aes_32t)(x) << n) | ((aes_32t)(x) >> (32 - n))) +#define aes_sw32(x) ((brot((x),8) & 0x00ff00ff) | (brot((x),24) & 0xff00ff00)) +#endif + +/* 1. FUNCTIONS REQUIRED + + This implementation provides subroutines for encryption, decryption + and for setting the three key lengths (separately) for encryption + and decryption. When the assembler code is not being used the following + definition blocks allow the selection of the routines that are to be + included in the compilation. +*/ +#ifdef AES_ENCRYPT +#define ENCRYPTION +#define ENCRYPTION_KEY_SCHEDULE +#endif + +#ifdef AES_DECRYPT +#define DECRYPTION +#define DECRYPTION_KEY_SCHEDULE +#endif + +/* 2. ASSEMBLER SUPPORT + + This define (which can be on the command line) enables the use of the + assembler code routines for encryption and decryption with the C code + only providing key scheduling +*/ +#if 0 +#define AES_ASM +#endif + +/* 3. BYTE ORDER WITHIN 32 BIT WORDS + + The fundamental data processing units in Rijndael are 8-bit bytes. The + input, output and key input are all enumerated arrays of bytes in which + bytes are numbered starting at zero and increasing to one less than the + number of bytes in the array in question. This enumeration is only used + for naming bytes and does not imply any adjacency or order relationship + from one byte to another. When these inputs and outputs are considered + as bit sequences, bits 8*n to 8*n+7 of the bit sequence are mapped to + byte[n] with bit 8n+i in the sequence mapped to bit 7-i within the byte. + In this implementation bits are numbered from 0 to 7 starting at the + numerically least significant end of each byte (bit n represents 2^n). + + However, Rijndael can be implemented more efficiently using 32-bit + words by packing bytes into words so that bytes 4*n to 4*n+3 are placed + into word[n]. While in principle these bytes can be assembled into words + in any positions, this implementation only supports the two formats in + which bytes in adjacent positions within words also have adjacent byte + numbers. This order is called big-endian if the lowest numbered bytes + in words have the highest numeric significance and little-endian if the + opposite applies. + + This code can work in either order irrespective of the order used by the + machine on which it runs. Normally the internal byte order will be set + to the order of the processor on which the code is to be run but this + define can be used to reverse this in special situations + + NOTE: Assembler code versions rely on PLATFORM_BYTE_ORDER being set +*/ +#if 1 || defined(AES_ASM) +#define ALGORITHM_BYTE_ORDER PLATFORM_BYTE_ORDER +#elif 0 +#define ALGORITHM_BYTE_ORDER BRG_LITTLE_ENDIAN +#elif 0 +#define ALGORITHM_BYTE_ORDER BRG_BIG_ENDIAN +#else +#error The algorithm byte order is not defined +#endif + +/* 4. FAST INPUT/OUTPUT OPERATIONS. + + On some machines it is possible to improve speed by transferring the + bytes in the input and output arrays to and from the internal 32-bit + variables by addressing these arrays as if they are arrays of 32-bit + words. On some machines this will always be possible but there may + be a large performance penalty if the byte arrays are not aligned on + the normal word boundaries. On other machines this technique will + lead to memory access errors when such 32-bit word accesses are not + properly aligned. The option SAFE_IO avoids such problems but will + often be slower on those machines that support misaligned access + (especially so if care is taken to align the input and output byte + arrays on 32-bit word boundaries). If SAFE_IO is not defined it is + assumed that access to byte arrays as if they are arrays of 32-bit + words will not cause problems when such accesses are misaligned. +*/ +#if 1 && !defined(_MSC_VER) +#define SAFE_IO +#endif + +/* 5. LOOP UNROLLING + + The code for encryption and decrytpion cycles through a number of rounds + that can be implemented either in a loop or by expanding the code into a + long sequence of instructions, the latter producing a larger program but + one that will often be much faster. The latter is called loop unrolling. + There are also potential speed advantages in expanding two iterations in + a loop with half the number of iterations, which is called partial loop + unrolling. The following options allow partial or full loop unrolling + to be set independently for encryption and decryption +*/ +#if 1 +#define ENC_UNROLL FULL +#elif 0 +#define ENC_UNROLL PARTIAL +#else +#define ENC_UNROLL NONE +#endif + +#if 1 +#define DEC_UNROLL FULL +#elif 0 +#define DEC_UNROLL PARTIAL +#else +#define DEC_UNROLL NONE +#endif + +/* 6. FAST FINITE FIELD OPERATIONS + + If this section is included, tables are used to provide faster finite + field arithmetic (this has no effect if FIXED_TABLES is defined). +*/ +#if 1 +#define FF_TABLES +#endif + +/* 7. INTERNAL STATE VARIABLE FORMAT + + The internal state of Rijndael is stored in a number of local 32-bit + word varaibles which can be defined either as an array or as individual + names variables. Include this section if you want to store these local + varaibles in arrays. Otherwise individual local variables will be used. +*/ +#if 1 +#define ARRAYS +#endif + +/* In this implementation the columns of the state array are each held in + 32-bit words. The state array can be held in various ways: in an array + of words, in a number of individual word variables or in a number of + processor registers. The following define maps a variable name x and + a column number c to the way the state array variable is to be held. + The first define below maps the state into an array x[c] whereas the + second form maps the state into a number of individual variables x0, + x1, etc. Another form could map individual state colums to machine + register names. +*/ + +#if defined(ARRAYS) +#define s(x,c) x[c] +#else +#define s(x,c) x##c +#endif + +/* 8. FIXED OR DYNAMIC TABLES + + When this section is included the tables used by the code are compiled + statically into the binary file. Otherwise the subroutine gen_tabs() + must be called to compute them before the code is first used. +*/ +#if 1 +#define FIXED_TABLES +#endif + +/* 9. TABLE ALIGNMENT + + On some sytsems speed will be improved by aligning the AES large lookup + tables on particular boundaries. This define should be set to a power of + two giving the desired alignment. It can be left undefined if alignment + is not needed. This option is specific to the Microsft VC++ compiler - + it seems to sometimes cause trouble for the VC++ version 6 compiler. +*/ + +#if 0 && defined(_MSC_VER) && (_MSC_VER >= 1300) +#define TABLE_ALIGN 64 +#endif + +/* 10. INTERNAL TABLE CONFIGURATION + + This cipher proceeds by repeating in a number of cycles known as 'rounds' + which are implemented by a round function which can optionally be speeded + up using tables. The basic tables are each 256 32-bit words, with either + one or four tables being required for each round function depending on + how much speed is required. The encryption and decryption round functions + are different and the last encryption and decrytpion round functions are + different again making four different round functions in all. + + This means that: + 1. Normal encryption and decryption rounds can each use either 0, 1 + or 4 tables and table spaces of 0, 1024 or 4096 bytes each. + 2. The last encryption and decryption rounds can also use either 0, 1 + or 4 tables and table spaces of 0, 1024 or 4096 bytes each. + + Include or exclude the appropriate definitions below to set the number + of tables used by this implementation. +*/ + +#if 1 /* set tables for the normal encryption round */ +#define ENC_ROUND FOUR_TABLES +#elif 0 +#define ENC_ROUND ONE_TABLE +#else +#define ENC_ROUND NO_TABLES +#endif + +#if 1 /* set tables for the last encryption round */ +#define LAST_ENC_ROUND FOUR_TABLES +#elif 0 +#define LAST_ENC_ROUND ONE_TABLE +#else +#define LAST_ENC_ROUND NO_TABLES +#endif + +#if 1 /* set tables for the normal decryption round */ +#define DEC_ROUND FOUR_TABLES +#elif 0 +#define DEC_ROUND ONE_TABLE +#else +#define DEC_ROUND NO_TABLES +#endif + +#if 1 /* set tables for the last decryption round */ +#define LAST_DEC_ROUND FOUR_TABLES +#elif 0 +#define LAST_DEC_ROUND ONE_TABLE +#else +#define LAST_DEC_ROUND NO_TABLES +#endif + +/* The decryption key schedule can be speeded up with tables in the same + way that the round functions can. Include or exclude the following + defines to set this requirement. +*/ +#if 1 +#define KEY_SCHED FOUR_TABLES +#elif 0 +#define KEY_SCHED ONE_TABLE +#else +#define KEY_SCHED NO_TABLES +#endif + +/* END OF CONFIGURATION OPTIONS */ + +#define RC_LENGTH (5 * (AES_BLOCK_SIZE / 4 - 2)) + +/* Disable or report errors on some combinations of options */ + +#if ENC_ROUND == NO_TABLES && LAST_ENC_ROUND != NO_TABLES +#undef LAST_ENC_ROUND +#define LAST_ENC_ROUND NO_TABLES +#elif ENC_ROUND == ONE_TABLE && LAST_ENC_ROUND == FOUR_TABLES +#undef LAST_ENC_ROUND +#define LAST_ENC_ROUND ONE_TABLE +#endif + +#if ENC_ROUND == NO_TABLES && ENC_UNROLL != NONE +#undef ENC_UNROLL +#define ENC_UNROLL NONE +#endif + +#if DEC_ROUND == NO_TABLES && LAST_DEC_ROUND != NO_TABLES +#undef LAST_DEC_ROUND +#define LAST_DEC_ROUND NO_TABLES +#elif DEC_ROUND == ONE_TABLE && LAST_DEC_ROUND == FOUR_TABLES +#undef LAST_DEC_ROUND +#define LAST_DEC_ROUND ONE_TABLE +#endif + +#if DEC_ROUND == NO_TABLES && DEC_UNROLL != NONE +#undef DEC_UNROLL +#define DEC_UNROLL NONE +#endif + +/* upr(x,n): rotates bytes within words by n positions, moving bytes to + higher index positions with wrap around into low positions + ups(x,n): moves bytes by n positions to higher index positions in + words but without wrap around + bval(x,n): extracts a byte from a word + + NOTE: The definitions given here are intended only for use with + unsigned variables and with shift counts that are compile + time constants +*/ + +#if (ALGORITHM_BYTE_ORDER == BRG_LITTLE_ENDIAN) +#define upr(x,n) (((aes_32t)(x) << (8 * (n))) | ((aes_32t)(x) >> (32 - 8 * (n)))) +#define ups(x,n) ((aes_32t) (x) << (8 * (n))) +#define bval(x,n) ((aes_08t)((x) >> (8 * (n)))) +#define bytes2word(b0, b1, b2, b3) \ + (((aes_32t)(b3) << 24) | ((aes_32t)(b2) << 16) | ((aes_32t)(b1) << 8) | (b0)) +#endif + +#if (ALGORITHM_BYTE_ORDER == BRG_BIG_ENDIAN) +#define upr(x,n) (((aes_32t)(x) >> (8 * (n))) | ((aes_32t)(x) << (32 - 8 * (n)))) +#define ups(x,n) ((aes_32t) (x) >> (8 * (n)))) +#define bval(x,n) ((aes_08t)((x) >> (24 - 8 * (n)))) +#define bytes2word(b0, b1, b2, b3) \ + (((aes_32t)(b0) << 24) | ((aes_32t)(b1) << 16) | ((aes_32t)(b2) << 8) | (b3)) +#endif + +#if defined(SAFE_IO) + +#define word_in(x,c) bytes2word(((aes_08t*)(x)+4*c)[0], ((aes_08t*)(x)+4*c)[1], \ + ((aes_08t*)(x)+4*c)[2], ((aes_08t*)(x)+4*c)[3]) +#define word_out(x,c,v) { ((aes_08t*)(x)+4*c)[0] = bval(v,0); ((aes_08t*)(x)+4*c)[1] = bval(v,1); \ + ((aes_08t*)(x)+4*c)[2] = bval(v,2); ((aes_08t*)(x)+4*c)[3] = bval(v,3); } + +#elif (ALGORITHM_BYTE_ORDER == PLATFORM_BYTE_ORDER) + +#define word_in(x,c) (*((aes_32t*)(x)+(c))) +#define word_out(x,c,v) (*((aes_32t*)(x)+(c)) = (v)) + +#else + +#define word_in(x,c) aes_sw32(*((aes_32t*)(x)+(c))) +#define word_out(x,c,v) (*((aes_32t*)(x)+(c)) = aes_sw32(v)) + +#endif + +/* the finite field modular polynomial and elements */ + +#define WPOLY 0x011b +#define BPOLY 0x1b + +/* multiply four bytes in GF(2^8) by 'x' {02} in parallel */ + +#define m1 0x80808080 +#define m2 0x7f7f7f7f +#define gf_mulx(x) ((((x) & m2) << 1) ^ ((((x) & m1) >> 7) * BPOLY)) + +/* The following defines provide alternative definitions of gf_mulx that might + give improved performance if a fast 32-bit multiply is not available. Note + that a temporary variable u needs to be defined where gf_mulx is used. + +#define gf_mulx(x) (u = (x) & m1, u |= (u >> 1), ((x) & m2) << 1) ^ ((u >> 3) | (u >> 6)) +#define m4 (0x01010101 * BPOLY) +#define gf_mulx(x) (u = (x) & m1, ((x) & m2) << 1) ^ ((u - (u >> 7)) & m4) +*/ + +/* Work out which tables are needed for the different options */ + +#ifdef AES_ASM +#ifdef ENC_ROUND +#undef ENC_ROUND +#endif +#define ENC_ROUND FOUR_TABLES +#ifdef LAST_ENC_ROUND +#undef LAST_ENC_ROUND +#endif +#define LAST_ENC_ROUND FOUR_TABLES +#ifdef DEC_ROUND +#undef DEC_ROUND +#endif +#define DEC_ROUND FOUR_TABLES +#ifdef LAST_DEC_ROUND +#undef LAST_DEC_ROUND +#endif +#define LAST_DEC_ROUND FOUR_TABLES +#ifdef KEY_SCHED +#undef KEY_SCHED +#define KEY_SCHED FOUR_TABLES +#endif +#endif + +#if defined(ENCRYPTION) || defined(AES_ASM) +#if ENC_ROUND == ONE_TABLE +#define FT1_SET +#elif ENC_ROUND == FOUR_TABLES +#define FT4_SET +#else +#define SBX_SET +#endif +#if LAST_ENC_ROUND == ONE_TABLE +#define FL1_SET +#elif LAST_ENC_ROUND == FOUR_TABLES +#define FL4_SET +#elif !defined(SBX_SET) +#define SBX_SET +#endif +#endif + +#if defined(DECRYPTION) || defined(AES_ASM) +#if DEC_ROUND == ONE_TABLE +#define IT1_SET +#elif DEC_ROUND == FOUR_TABLES +#define IT4_SET +#else +#define ISB_SET +#endif +#if LAST_DEC_ROUND == ONE_TABLE +#define IL1_SET +#elif LAST_DEC_ROUND == FOUR_TABLES +#define IL4_SET +#elif !defined(ISB_SET) +#define ISB_SET +#endif +#endif + +#if defined(ENCRYPTION_KEY_SCHEDULE) || defined(DECRYPTION_KEY_SCHEDULE) +#if KEY_SCHED == ONE_TABLE +#define LS1_SET +#define IM1_SET +#elif KEY_SCHED == FOUR_TABLES +#define LS4_SET +#define IM4_SET +#elif !defined(SBX_SET) +#define SBX_SET +#endif +#endif + +/* generic definitions of Rijndael macros that use tables */ + +#define no_table(x,box,vf,rf,c) bytes2word( \ + box[bval(vf(x,0,c),rf(0,c))], \ + box[bval(vf(x,1,c),rf(1,c))], \ + box[bval(vf(x,2,c),rf(2,c))], \ + box[bval(vf(x,3,c),rf(3,c))]) + +#define one_table(x,op,tab,vf,rf,c) \ + ( tab[bval(vf(x,0,c),rf(0,c))] \ + ^ op(tab[bval(vf(x,1,c),rf(1,c))],1) \ + ^ op(tab[bval(vf(x,2,c),rf(2,c))],2) \ + ^ op(tab[bval(vf(x,3,c),rf(3,c))],3)) + +#define four_tables(x,tab,vf,rf,c) \ + ( tab[0][bval(vf(x,0,c),rf(0,c))] \ + ^ tab[1][bval(vf(x,1,c),rf(1,c))] \ + ^ tab[2][bval(vf(x,2,c),rf(2,c))] \ + ^ tab[3][bval(vf(x,3,c),rf(3,c))]) + +#define vf1(x,r,c) (x) +#define rf1(r,c) (r) +#define rf2(r,c) ((8+r-c)&3) + +/* perform forward and inverse column mix operation on four bytes in long word x in */ +/* parallel. NOTE: x must be a simple variable, NOT an expression in these macros. */ + +#if defined(FM4_SET) /* not currently used */ +#define fwd_mcol(x) four_tables(x,t_use(f,m),vf1,rf1,0) +#elif defined(FM1_SET) /* not currently used */ +#define fwd_mcol(x) one_table(x,upr,t_use(f,m),vf1,rf1,0) +#else +#define dec_fmvars aes_32t g2 +#define fwd_mcol(x) (g2 = gf_mulx(x), g2 ^ upr((x) ^ g2, 3) ^ upr((x), 2) ^ upr((x), 1)) +#endif + +#if defined(IM4_SET) +#define inv_mcol(x) four_tables(x,t_use(i,m),vf1,rf1,0) +#elif defined(IM1_SET) +#define inv_mcol(x) one_table(x,upr,t_use(i,m),vf1,rf1,0) +#else +#define dec_imvars aes_32t g2, g4, g9 +#define inv_mcol(x) (g2 = gf_mulx(x), g4 = gf_mulx(g2), g9 = (x) ^ gf_mulx(g4), g4 ^= g9, \ + (x) ^ g2 ^ g4 ^ upr(g2 ^ g9, 3) ^ upr(g4, 2) ^ upr(g9, 1)) +#endif + +#if defined(FL4_SET) +#define ls_box(x,c) four_tables(x,t_use(f,l),vf1,rf2,c) +#elif defined(LS4_SET) +#define ls_box(x,c) four_tables(x,t_use(l,s),vf1,rf2,c) +#elif defined(FL1_SET) +#define ls_box(x,c) one_table(x,upr,t_use(f,l),vf1,rf2,c) +#elif defined(LS1_SET) +#define ls_box(x,c) one_table(x,upr,t_use(l,s),vf1,rf2,c) +#else +#define ls_box(x,c) no_table(x,t_use(s,box),vf1,rf2,c) +#endif + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/* If there are no global variables, the definitions here can be + used to put the AES tables in a structure so that a pointer + can then be added to the AES context to pass them to the AES + routines that need them. If this facility is used, the calling + program has to ensure that this pointer is managed appropriately. + In particular, the value of the t_dec(in,it) item in the table + structure must be set to zero in order to ensure that the tables + are initialised. In practice the three code sequences in aeskey.c + that control the calls to gen_tabs() and the gen_tabs() routine + itself will have to be changed for a specific implementation. If + global variables are available it will generally be preferable to + use them with the precomputed FIXED_TABLES option that uses static + global tables. + + The following defines can be used to control the way the tables + are defined, initialised and used in embedded environments that + require special features for these purposes + + the 't_dec' construction is used to declare fixed table arrays + the 't_set' construction is used to set fixed table values + the 't_use' construction is used to access fixed table values + + 256 byte tables: + + t_xxx(s,box) => forward S box + t_xxx(i,box) => inverse S box + + 256 32-bit word OR 4 x 256 32-bit word tables: + + t_xxx(f,n) => forward normal round + t_xxx(f,l) => forward last round + t_xxx(i,n) => inverse normal round + t_xxx(i,l) => inverse last round + t_xxx(l,s) => key schedule table + t_xxx(i,m) => key schedule table + + Other variables and tables: + + t_xxx(r,c) => the rcon table +*/ + +#define t_dec(m,n) t_##m##n +#define t_set(m,n) t_##m##n +#define t_use(m,n) t_##m##n + +#if defined(DO_TABLES) /* declare and instantiate tables */ + +/* finite field arithmetic operations for table generation */ + +#if defined(FIXED_TABLES) || !defined(FF_TABLES) + +#define f2(x) ((x<<1) ^ (((x>>7) & 1) * WPOLY)) +#define f4(x) ((x<<2) ^ (((x>>6) & 1) * WPOLY) ^ (((x>>6) & 2) * WPOLY)) +#define f8(x) ((x<<3) ^ (((x>>5) & 1) * WPOLY) ^ (((x>>5) & 2) * WPOLY) \ + ^ (((x>>5) & 4) * WPOLY)) +#define f3(x) (f2(x) ^ x) +#define f9(x) (f8(x) ^ x) +#define fb(x) (f8(x) ^ f2(x) ^ x) +#define fd(x) (f8(x) ^ f4(x) ^ x) +#define fe(x) (f8(x) ^ f4(x) ^ f2(x)) + +#else + +#define f2(x) ((x) ? pow[log[x] + 0x19] : 0) +#define f3(x) ((x) ? pow[log[x] + 0x01] : 0) +#define f9(x) ((x) ? pow[log[x] + 0xc7] : 0) +#define fb(x) ((x) ? pow[log[x] + 0x68] : 0) +#define fd(x) ((x) ? pow[log[x] + 0xee] : 0) +#define fe(x) ((x) ? pow[log[x] + 0xdf] : 0) +#define fi(x) ((x) ? pow[ 255 - log[x]] : 0) + +#endif + +#if defined(FIXED_TABLES) /* declare and set values for static tables */ + +#define sb_data(w) \ + w(0x63), w(0x7c), w(0x77), w(0x7b), w(0xf2), w(0x6b), w(0x6f), w(0xc5),\ + w(0x30), w(0x01), w(0x67), w(0x2b), w(0xfe), w(0xd7), w(0xab), w(0x76),\ + w(0xca), w(0x82), w(0xc9), w(0x7d), w(0xfa), w(0x59), w(0x47), w(0xf0),\ + w(0xad), w(0xd4), w(0xa2), w(0xaf), w(0x9c), w(0xa4), w(0x72), w(0xc0),\ + w(0xb7), w(0xfd), w(0x93), w(0x26), w(0x36), w(0x3f), w(0xf7), w(0xcc),\ + w(0x34), w(0xa5), w(0xe5), w(0xf1), w(0x71), w(0xd8), w(0x31), w(0x15),\ + w(0x04), w(0xc7), w(0x23), w(0xc3), w(0x18), w(0x96), w(0x05), w(0x9a),\ + w(0x07), w(0x12), w(0x80), w(0xe2), w(0xeb), w(0x27), w(0xb2), w(0x75),\ + w(0x09), w(0x83), w(0x2c), w(0x1a), w(0x1b), w(0x6e), w(0x5a), w(0xa0),\ + w(0x52), w(0x3b), w(0xd6), w(0xb3), w(0x29), w(0xe3), w(0x2f), w(0x84),\ + w(0x53), w(0xd1), w(0x00), w(0xed), w(0x20), w(0xfc), w(0xb1), w(0x5b),\ + w(0x6a), w(0xcb), w(0xbe), w(0x39), w(0x4a), w(0x4c), w(0x58), w(0xcf),\ + w(0xd0), w(0xef), w(0xaa), w(0xfb), w(0x43), w(0x4d), w(0x33), w(0x85),\ + w(0x45), w(0xf9), w(0x02), w(0x7f), w(0x50), w(0x3c), w(0x9f), w(0xa8),\ + w(0x51), w(0xa3), w(0x40), w(0x8f), w(0x92), w(0x9d), w(0x38), w(0xf5),\ + w(0xbc), w(0xb6), w(0xda), w(0x21), w(0x10), w(0xff), w(0xf3), w(0xd2),\ + w(0xcd), w(0x0c), w(0x13), w(0xec), w(0x5f), w(0x97), w(0x44), w(0x17),\ + w(0xc4), w(0xa7), w(0x7e), w(0x3d), w(0x64), w(0x5d), w(0x19), w(0x73),\ + w(0x60), w(0x81), w(0x4f), w(0xdc), w(0x22), w(0x2a), w(0x90), w(0x88),\ + w(0x46), w(0xee), w(0xb8), w(0x14), w(0xde), w(0x5e), w(0x0b), w(0xdb),\ + w(0xe0), w(0x32), w(0x3a), w(0x0a), w(0x49), w(0x06), w(0x24), w(0x5c),\ + w(0xc2), w(0xd3), w(0xac), w(0x62), w(0x91), w(0x95), w(0xe4), w(0x79),\ + w(0xe7), w(0xc8), w(0x37), w(0x6d), w(0x8d), w(0xd5), w(0x4e), w(0xa9),\ + w(0x6c), w(0x56), w(0xf4), w(0xea), w(0x65), w(0x7a), w(0xae), w(0x08),\ + w(0xba), w(0x78), w(0x25), w(0x2e), w(0x1c), w(0xa6), w(0xb4), w(0xc6),\ + w(0xe8), w(0xdd), w(0x74), w(0x1f), w(0x4b), w(0xbd), w(0x8b), w(0x8a),\ + w(0x70), w(0x3e), w(0xb5), w(0x66), w(0x48), w(0x03), w(0xf6), w(0x0e),\ + w(0x61), w(0x35), w(0x57), w(0xb9), w(0x86), w(0xc1), w(0x1d), w(0x9e),\ + w(0xe1), w(0xf8), w(0x98), w(0x11), w(0x69), w(0xd9), w(0x8e), w(0x94),\ + w(0x9b), w(0x1e), w(0x87), w(0xe9), w(0xce), w(0x55), w(0x28), w(0xdf),\ + w(0x8c), w(0xa1), w(0x89), w(0x0d), w(0xbf), w(0xe6), w(0x42), w(0x68),\ + w(0x41), w(0x99), w(0x2d), w(0x0f), w(0xb0), w(0x54), w(0xbb), w(0x16) + +#define isb_data(w) \ + w(0x52), w(0x09), w(0x6a), w(0xd5), w(0x30), w(0x36), w(0xa5), w(0x38),\ + w(0xbf), w(0x40), w(0xa3), w(0x9e), w(0x81), w(0xf3), w(0xd7), w(0xfb),\ + w(0x7c), w(0xe3), w(0x39), w(0x82), w(0x9b), w(0x2f), w(0xff), w(0x87),\ + w(0x34), w(0x8e), w(0x43), w(0x44), w(0xc4), w(0xde), w(0xe9), w(0xcb),\ + w(0x54), w(0x7b), w(0x94), w(0x32), w(0xa6), w(0xc2), w(0x23), w(0x3d),\ + w(0xee), w(0x4c), w(0x95), w(0x0b), w(0x42), w(0xfa), w(0xc3), w(0x4e),\ + w(0x08), w(0x2e), w(0xa1), w(0x66), w(0x28), w(0xd9), w(0x24), w(0xb2),\ + w(0x76), w(0x5b), w(0xa2), w(0x49), w(0x6d), w(0x8b), w(0xd1), w(0x25),\ + w(0x72), w(0xf8), w(0xf6), w(0x64), w(0x86), w(0x68), w(0x98), w(0x16),\ + w(0xd4), w(0xa4), w(0x5c), w(0xcc), w(0x5d), w(0x65), w(0xb6), w(0x92),\ + w(0x6c), w(0x70), w(0x48), w(0x50), w(0xfd), w(0xed), w(0xb9), w(0xda),\ + w(0x5e), w(0x15), w(0x46), w(0x57), w(0xa7), w(0x8d), w(0x9d), w(0x84),\ + w(0x90), w(0xd8), w(0xab), w(0x00), w(0x8c), w(0xbc), w(0xd3), w(0x0a),\ + w(0xf7), w(0xe4), w(0x58), w(0x05), w(0xb8), w(0xb3), w(0x45), w(0x06),\ + w(0xd0), w(0x2c), w(0x1e), w(0x8f), w(0xca), w(0x3f), w(0x0f), w(0x02),\ + w(0xc1), w(0xaf), w(0xbd), w(0x03), w(0x01), w(0x13), w(0x8a), w(0x6b),\ + w(0x3a), w(0x91), w(0x11), w(0x41), w(0x4f), w(0x67), w(0xdc), w(0xea),\ + w(0x97), w(0xf2), w(0xcf), w(0xce), w(0xf0), w(0xb4), w(0xe6), w(0x73),\ + w(0x96), w(0xac), w(0x74), w(0x22), w(0xe7), w(0xad), w(0x35), w(0x85),\ + w(0xe2), w(0xf9), w(0x37), w(0xe8), w(0x1c), w(0x75), w(0xdf), w(0x6e),\ + w(0x47), w(0xf1), w(0x1a), w(0x71), w(0x1d), w(0x29), w(0xc5), w(0x89),\ + w(0x6f), w(0xb7), w(0x62), w(0x0e), w(0xaa), w(0x18), w(0xbe), w(0x1b),\ + w(0xfc), w(0x56), w(0x3e), w(0x4b), w(0xc6), w(0xd2), w(0x79), w(0x20),\ + w(0x9a), w(0xdb), w(0xc0), w(0xfe), w(0x78), w(0xcd), w(0x5a), w(0xf4),\ + w(0x1f), w(0xdd), w(0xa8), w(0x33), w(0x88), w(0x07), w(0xc7), w(0x31),\ + w(0xb1), w(0x12), w(0x10), w(0x59), w(0x27), w(0x80), w(0xec), w(0x5f),\ + w(0x60), w(0x51), w(0x7f), w(0xa9), w(0x19), w(0xb5), w(0x4a), w(0x0d),\ + w(0x2d), w(0xe5), w(0x7a), w(0x9f), w(0x93), w(0xc9), w(0x9c), w(0xef),\ + w(0xa0), w(0xe0), w(0x3b), w(0x4d), w(0xae), w(0x2a), w(0xf5), w(0xb0),\ + w(0xc8), w(0xeb), w(0xbb), w(0x3c), w(0x83), w(0x53), w(0x99), w(0x61),\ + w(0x17), w(0x2b), w(0x04), w(0x7e), w(0xba), w(0x77), w(0xd6), w(0x26),\ + w(0xe1), w(0x69), w(0x14), w(0x63), w(0x55), w(0x21), w(0x0c), w(0x7d), + +#define mm_data(w) \ + w(0x00), w(0x01), w(0x02), w(0x03), w(0x04), w(0x05), w(0x06), w(0x07),\ + w(0x08), w(0x09), w(0x0a), w(0x0b), w(0x0c), w(0x0d), w(0x0e), w(0x0f),\ + w(0x10), w(0x11), w(0x12), w(0x13), w(0x14), w(0x15), w(0x16), w(0x17),\ + w(0x18), w(0x19), w(0x1a), w(0x1b), w(0x1c), w(0x1d), w(0x1e), w(0x1f),\ + w(0x20), w(0x21), w(0x22), w(0x23), w(0x24), w(0x25), w(0x26), w(0x27),\ + w(0x28), w(0x29), w(0x2a), w(0x2b), w(0x2c), w(0x2d), w(0x2e), w(0x2f),\ + w(0x30), w(0x31), w(0x32), w(0x33), w(0x34), w(0x35), w(0x36), w(0x37),\ + w(0x38), w(0x39), w(0x3a), w(0x3b), w(0x3c), w(0x3d), w(0x3e), w(0x3f),\ + w(0x40), w(0x41), w(0x42), w(0x43), w(0x44), w(0x45), w(0x46), w(0x47),\ + w(0x48), w(0x49), w(0x4a), w(0x4b), w(0x4c), w(0x4d), w(0x4e), w(0x4f),\ + w(0x50), w(0x51), w(0x52), w(0x53), w(0x54), w(0x55), w(0x56), w(0x57),\ + w(0x58), w(0x59), w(0x5a), w(0x5b), w(0x5c), w(0x5d), w(0x5e), w(0x5f),\ + w(0x60), w(0x61), w(0x62), w(0x63), w(0x64), w(0x65), w(0x66), w(0x67),\ + w(0x68), w(0x69), w(0x6a), w(0x6b), w(0x6c), w(0x6d), w(0x6e), w(0x6f),\ + w(0x70), w(0x71), w(0x72), w(0x73), w(0x74), w(0x75), w(0x76), w(0x77),\ + w(0x78), w(0x79), w(0x7a), w(0x7b), w(0x7c), w(0x7d), w(0x7e), w(0x7f),\ + w(0x80), w(0x81), w(0x82), w(0x83), w(0x84), w(0x85), w(0x86), w(0x87),\ + w(0x88), w(0x89), w(0x8a), w(0x8b), w(0x8c), w(0x8d), w(0x8e), w(0x8f),\ + w(0x90), w(0x91), w(0x92), w(0x93), w(0x94), w(0x95), w(0x96), w(0x97),\ + w(0x98), w(0x99), w(0x9a), w(0x9b), w(0x9c), w(0x9d), w(0x9e), w(0x9f),\ + w(0xa0), w(0xa1), w(0xa2), w(0xa3), w(0xa4), w(0xa5), w(0xa6), w(0xa7),\ + w(0xa8), w(0xa9), w(0xaa), w(0xab), w(0xac), w(0xad), w(0xae), w(0xaf),\ + w(0xb0), w(0xb1), w(0xb2), w(0xb3), w(0xb4), w(0xb5), w(0xb6), w(0xb7),\ + w(0xb8), w(0xb9), w(0xba), w(0xbb), w(0xbc), w(0xbd), w(0xbe), w(0xbf),\ + w(0xc0), w(0xc1), w(0xc2), w(0xc3), w(0xc4), w(0xc5), w(0xc6), w(0xc7),\ + w(0xc8), w(0xc9), w(0xca), w(0xcb), w(0xcc), w(0xcd), w(0xce), w(0xcf),\ + w(0xd0), w(0xd1), w(0xd2), w(0xd3), w(0xd4), w(0xd5), w(0xd6), w(0xd7),\ + w(0xd8), w(0xd9), w(0xda), w(0xdb), w(0xdc), w(0xdd), w(0xde), w(0xdf),\ + w(0xe0), w(0xe1), w(0xe2), w(0xe3), w(0xe4), w(0xe5), w(0xe6), w(0xe7),\ + w(0xe8), w(0xe9), w(0xea), w(0xeb), w(0xec), w(0xed), w(0xee), w(0xef),\ + w(0xf0), w(0xf1), w(0xf2), w(0xf3), w(0xf4), w(0xf5), w(0xf6), w(0xf7),\ + w(0xf8), w(0xf9), w(0xfa), w(0xfb), w(0xfc), w(0xfd), w(0xfe), w(0xff) + +#define h0(x) (x) + +/* These defines are used to ensure tables are generated in the + right format depending on the internal byte order required +*/ + +#define w0(p) bytes2word(p, 0, 0, 0) +#define w1(p) bytes2word(0, p, 0, 0) +#define w2(p) bytes2word(0, 0, p, 0) +#define w3(p) bytes2word(0, 0, 0, p) + +#define u0(p) bytes2word(f2(p), p, p, f3(p)) +#define u1(p) bytes2word(f3(p), f2(p), p, p) +#define u2(p) bytes2word(p, f3(p), f2(p), p) +#define u3(p) bytes2word(p, p, f3(p), f2(p)) + +#define v0(p) bytes2word(fe(p), f9(p), fd(p), fb(p)) +#define v1(p) bytes2word(fb(p), fe(p), f9(p), fd(p)) +#define v2(p) bytes2word(fd(p), fb(p), fe(p), f9(p)) +#define v3(p) bytes2word(f9(p), fd(p), fb(p), fe(p)) + +const aes_32t t_dec(r,c)[RC_LENGTH] = +{ + w0(0x01), w0(0x02), w0(0x04), w0(0x08), w0(0x10), + w0(0x20), w0(0x40), w0(0x80), w0(0x1b), w0(0x36) +}; + +#define d_1(t,n,b,v) const t n[256] = { b(v##0) } +#define d_4(t,n,b,v) const t n[4][256] = { { b(v##0) }, { b(v##1) }, { b(v##2) }, { b(v##3) } } + +#else /* declare and instantiate tables for dynamic value generation in in tab.c */ + +aes_32t t_dec(r,c)[RC_LENGTH]; + +#define d_1(t,n,b,v) t n[256] +#define d_4(t,n,b,v) t n[4][256] + +#endif + +#else /* declare tables without instantiation */ + +#if defined(FIXED_TABLES) + +extern const aes_32t t_dec(r,c)[RC_LENGTH]; + +#if defined(_MSC_VER) && defined(TABLE_ALIGN) +#define d_1(t,n,b,v) extern __declspec(align(TABLE_ALIGN)) const t n[256] +#define d_4(t,n,b,v) extern __declspec(align(TABLE_ALIGN)) const t n[4][256] +#else +#define d_1(t,n,b,v) extern const t n[256] +#define d_4(t,n,b,v) extern const t n[4][256] +#endif +#else + +extern aes_32t t_dec(r,c)[RC_LENGTH]; + +#if defined(_MSC_VER) && defined(TABLE_ALIGN) +#define d_1(t,n,b,v) extern __declspec(align(TABLE_ALIGN)) t n[256] +#define d_4(t,n,b,v) extern __declspec(align(TABLE_ALIGN)) t n[4][256] +#else +#define d_1(t,n,b,v) extern t n[256] +#define d_4(t,n,b,v) extern t n[4][256] +#endif +#endif + +#endif + +#ifdef SBX_SET + d_1(aes_08t, t_dec(s,box), sb_data, h); +#endif +#ifdef ISB_SET + d_1(aes_08t, t_dec(i,box), isb_data, h); +#endif + +#ifdef FT1_SET + d_1(aes_32t, t_dec(f,n), sb_data, u); +#endif +#ifdef FT4_SET + d_4(aes_32t, t_dec(f,n), sb_data, u); +#endif + +#ifdef FL1_SET + d_1(aes_32t, t_dec(f,l), sb_data, w); +#endif +#ifdef FL4_SET + d_4(aes_32t, t_dec(f,l), sb_data, w); +#endif + +#ifdef IT1_SET + d_1(aes_32t, t_dec(i,n), isb_data, v); +#endif +#ifdef IT4_SET + d_4(aes_32t, t_dec(i,n), isb_data, v); +#endif + +#ifdef IL1_SET + d_1(aes_32t, t_dec(i,l), isb_data, w); +#endif +#ifdef IL4_SET + d_4(aes_32t, t_dec(i,l), isb_data, w); +#endif + +#ifdef LS1_SET +#ifdef FL1_SET +#undef LS1_SET +#else + d_1(aes_32t, t_dec(l,s), sb_data, w); +#endif +#endif + +#ifdef LS4_SET +#ifdef FL4_SET +#undef LS4_SET +#else + d_4(aes_32t, t_dec(l,s), sb_data, w); +#endif +#endif + +#ifdef IM1_SET + d_1(aes_32t, t_dec(i,m), mm_data, v); +#endif +#ifdef IM4_SET + d_4(aes_32t, t_dec(i,m), mm_data, v); +#endif + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/main/aestab.c b/main/aestab.c new file mode 100644 index 000000000..c84a480af --- /dev/null +++ b/main/aestab.c @@ -0,0 +1,232 @@ +/* + --------------------------------------------------------------------------- + Copyright (c) 2003, Dr Brian Gladman , Worcester, UK. + All rights reserved. + + LICENSE TERMS + + The free distribution and use of this software in both source and binary + form is allowed (with or without changes) provided that: + + 1. distributions of this source code include the above copyright + notice, this list of conditions and the following disclaimer; + + 2. distributions in binary form include the above copyright + notice, this list of conditions and the following disclaimer + in the documentation and/or other associated materials; + + 3. the copyright holder's name is not used to endorse products + built using this software without specific written permission. + + ALTERNATIVELY, provided that this notice is retained in full, this product + may be distributed under the terms of the GNU General Public License (GPL), + in which case the provisions of the GPL apply INSTEAD OF those given above. + + DISCLAIMER + + This software is provided 'as is' with no explicit or implied warranties + in respect of its properties, including, but not limited to, correctness + and/or fitness for purpose. + --------------------------------------------------------------------------- + Issue Date: 26/08/2003 + +*/ + +#if defined(__cplusplus) +extern "C" +{ +#endif + +#define DO_TABLES + +#include "aesopt.h" + +#if defined(FIXED_TABLES) + +/* implemented in case of wrong call for fixed tables */ + +void gen_tabs(void) +{ +} + +#else /* dynamic table generation */ + +#if !defined(FF_TABLES) + +/* Generate the tables for the dynamic table option + + It will generally be sensible to use tables to compute finite + field multiplies and inverses but where memory is scarse this + code might sometimes be better. But it only has effect during + initialisation so its pretty unimportant in overall terms. +*/ + +/* return 2 ^ (n - 1) where n is the bit number of the highest bit + set in x with x in the range 1 < x < 0x00000200. This form is + used so that locals within fi can be bytes rather than words +*/ + +static aes_08t hibit(const aes_32t x) +{ aes_08t r = (aes_08t)((x >> 1) | (x >> 2)); + + r |= (r >> 2); + r |= (r >> 4); + return (r + 1) >> 1; +} + +/* return the inverse of the finite field element x */ + +static aes_08t fi(const aes_08t x) +{ aes_08t p1 = x, p2 = BPOLY, n1 = hibit(x), n2 = 0x80, v1 = 1, v2 = 0; + + if(x < 2) return x; + + for(;;) + { + if(!n1) return v1; + + while(n2 >= n1) + { + n2 /= n1; p2 ^= p1 * n2; v2 ^= v1 * n2; n2 = hibit(p2); + } + + if(!n2) return v2; + + while(n1 >= n2) + { + n1 /= n2; p1 ^= p2 * n1; v1 ^= v2 * n1; n1 = hibit(p1); + } + } +} + +#endif + +/* The forward and inverse affine transformations used in the S-box */ + +#define fwd_affine(x) \ + (w = (aes_32t)x, w ^= (w<<1)^(w<<2)^(w<<3)^(w<<4), 0x63^(aes_08t)(w^(w>>8))) + +#define inv_affine(x) \ + (w = (aes_32t)x, w = (w<<1)^(w<<3)^(w<<6), 0x05^(aes_08t)(w^(w>>8))) + +static int init = 0; + +void gen_tabs(void) +{ aes_32t i, w; + +#if defined(FF_TABLES) + + aes_08t pow[512], log[256]; + + if(init) return; + /* log and power tables for GF(2^8) finite field with + WPOLY as modular polynomial - the simplest primitive + root is 0x03, used here to generate the tables + */ + + i = 0; w = 1; + do + { + pow[i] = (aes_08t)w; + pow[i + 255] = (aes_08t)w; + log[w] = (aes_08t)i++; + w ^= (w << 1) ^ (w & 0x80 ? WPOLY : 0); + } + while (w != 1); + +#else + if(init) return; +#endif + + for(i = 0, w = 1; i < RC_LENGTH; ++i) + { + t_set(r,c)[i] = bytes2word(w, 0, 0, 0); + w = f2(w); + } + + for(i = 0; i < 256; ++i) + { aes_08t b; + + b = fwd_affine(fi((aes_08t)i)); + w = bytes2word(f2(b), b, b, f3(b)); + +#ifdef SBX_SET + t_set(s,box)[i] = b; +#endif + +#ifdef FT1_SET /* tables for a normal encryption round */ + t_set(f,n)[i] = w; +#endif +#ifdef FT4_SET + t_set(f,n)[0][i] = w; + t_set(f,n)[1][i] = upr(w,1); + t_set(f,n)[2][i] = upr(w,2); + t_set(f,n)[3][i] = upr(w,3); +#endif + w = bytes2word(b, 0, 0, 0); + +#ifdef FL1_SET /* tables for last encryption round (may also */ + t_set(f,l)[i] = w; /* be used in the key schedule) */ +#endif +#ifdef FL4_SET + t_set(f,l)[0][i] = w; + t_set(f,l)[1][i] = upr(w,1); + t_set(f,l)[2][i] = upr(w,2); + t_set(f,l)[3][i] = upr(w,3); +#endif + +#ifdef LS1_SET /* table for key schedule if t_set(f,l) above is */ + t_set(l,s)[i] = w; /* not of the required form */ +#endif +#ifdef LS4_SET + t_set(l,s)[0][i] = w; + t_set(l,s)[1][i] = upr(w,1); + t_set(l,s)[2][i] = upr(w,2); + t_set(l,s)[3][i] = upr(w,3); +#endif + + b = fi(inv_affine((aes_08t)i)); + w = bytes2word(fe(b), f9(b), fd(b), fb(b)); + +#ifdef IM1_SET /* tables for the inverse mix column operation */ + t_set(i,m)[b] = w; +#endif +#ifdef IM4_SET + t_set(i,m)[0][b] = w; + t_set(i,m)[1][b] = upr(w,1); + t_set(i,m)[2][b] = upr(w,2); + t_set(i,m)[3][b] = upr(w,3); +#endif + +#ifdef ISB_SET + t_set(i,box)[i] = b; +#endif +#ifdef IT1_SET /* tables for a normal decryption round */ + t_set(i,n)[i] = w; +#endif +#ifdef IT4_SET + t_set(i,n)[0][i] = w; + t_set(i,n)[1][i] = upr(w,1); + t_set(i,n)[2][i] = upr(w,2); + t_set(i,n)[3][i] = upr(w,3); +#endif + w = bytes2word(b, 0, 0, 0); +#ifdef IL1_SET /* tables for last decryption round */ + t_set(i,l)[i] = w; +#endif +#ifdef IL4_SET + t_set(i,l)[0][i] = w; + t_set(i,l)[1][i] = upr(w,1); + t_set(i,l)[2][i] = upr(w,2); + t_set(i,l)[3][i] = upr(w,3); +#endif + } + init = 1; +} + +#endif + +#if defined(__cplusplus) +} +#endif + diff --git a/main/alaw.c b/main/alaw.c new file mode 100644 index 000000000..782419d9e --- /dev/null +++ b/main/alaw.c @@ -0,0 +1,101 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief u-Law to Signed linear conversion + * + * \author Mark Spencer + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/alaw.h" + +#define AMI_MASK 0x55 + +static inline unsigned char linear2alaw (short int linear) +{ + int mask; + int seg; + int pcm_val; + static int seg_end[8] = + { + 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF + }; + + pcm_val = linear; + if (pcm_val >= 0) + { + /* Sign (7th) bit = 1 */ + mask = AMI_MASK | 0x80; + } + else + { + /* Sign bit = 0 */ + mask = AMI_MASK; + pcm_val = -pcm_val; + } + + /* Convert the scaled magnitude to segment number. */ + for (seg = 0; seg < 8; seg++) + { + if (pcm_val <= seg_end[seg]) + break; + } + /* Combine the sign, segment, and quantization bits. */ + return ((seg << 4) | ((pcm_val >> ((seg) ? (seg + 3) : 4)) & 0x0F)) ^ mask; +} +/*- End of function --------------------------------------------------------*/ + +static inline short int alaw2linear (unsigned char alaw) +{ + int i; + int seg; + + alaw ^= AMI_MASK; + i = ((alaw & 0x0F) << 4); + seg = (((int) alaw & 0x70) >> 4); + if (seg) + i = (i + 0x100) << (seg - 1); + return (short int) ((alaw & 0x80) ? i : -i); +} + +unsigned char __ast_lin2a[8192]; +short __ast_alaw[256]; + +void ast_alaw_init(void) +{ + int i; + /* + * Set up mu-law conversion table + */ + for(i = 0;i < 256;i++) + { + __ast_alaw[i] = alaw2linear(i); + } + /* set up the reverse (mu-law) conversion table */ + for(i = -32768; i < 32768; i++) + { + __ast_lin2a[((unsigned short)i) >> 3] = linear2alaw(i); + } + +} + diff --git a/main/app.c b/main/app.c new file mode 100644 index 000000000..1099feaea --- /dev/null +++ b/main/app.c @@ -0,0 +1,1328 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Convenient Application Routines + * + * \author Mark Spencer + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/file.h" +#include "asterisk/app.h" +#include "asterisk/dsp.h" +#include "asterisk/logger.h" +#include "asterisk/options.h" +#include "asterisk/utils.h" +#include "asterisk/lock.h" +#include "asterisk/indications.h" + +#define MAX_OTHER_FORMATS 10 + + +/* ! +This function presents a dialtone and reads an extension into 'collect' +which must be a pointer to a **pre-initialized** array of char having a +size of 'size' suitable for writing to. It will collect no more than the smaller +of 'maxlen' or 'size' minus the original strlen() of collect digits. +\return 0 if extension does not exist, 1 if extension exists +*/ +int ast_app_dtget(struct ast_channel *chan, const char *context, char *collect, size_t size, int maxlen, int timeout) +{ + struct tone_zone_sound *ts; + int res=0, x=0; + + if (maxlen > size) + maxlen = size; + + if (!timeout && chan->pbx) + timeout = chan->pbx->dtimeout; + else if (!timeout) + timeout = 5; + + ts = ast_get_indication_tone(chan->zone,"dial"); + if (ts && ts->data[0]) + res = ast_playtones_start(chan, 0, ts->data, 0); + else + ast_log(LOG_NOTICE,"Huh....? no dial for indications?\n"); + + for (x = strlen(collect); x < maxlen; ) { + res = ast_waitfordigit(chan, timeout); + if (!ast_ignore_pattern(context, collect)) + ast_playtones_stop(chan); + if (res < 1) + break; + collect[x++] = res; + if (!ast_matchmore_extension(chan, context, collect, 1, chan->cid.cid_num)) { + if (collect[x-1] == '#') { + /* Not a valid extension, ending in #, assume the # was to finish dialing */ + collect[x-1] = '\0'; + } + break; + } + } + if (res >= 0) + res = ast_exists_extension(chan, context, collect, 1, chan->cid.cid_num) ? 1 : 0; + return res; +} + +/*! \param timeout set timeout to 0 for "standard" timeouts. Set timeout to -1 for + "ludicrous time" (essentially never times out) */ +int ast_app_getdata(struct ast_channel *c, char *prompt, char *s, int maxlen, int timeout) +{ + int res,to,fto; + /* XXX Merge with full version? XXX */ + if (maxlen) + s[0] = '\0'; + if (prompt) { + res = ast_streamfile(c, prompt, c->language); + if (res < 0) + return res; + } + fto = c->pbx ? c->pbx->rtimeout * 1000 : 6000; + to = c->pbx ? c->pbx->dtimeout * 1000 : 2000; + + if (timeout > 0) + fto = to = timeout; + if (timeout < 0) + fto = to = 1000000000; + res = ast_readstring(c, s, maxlen, to, fto, "#"); + return res; +} + + +int ast_app_getdata_full(struct ast_channel *c, char *prompt, char *s, int maxlen, int timeout, int audiofd, int ctrlfd) +{ + int res, to, fto; + if (prompt) { + res = ast_streamfile(c, prompt, c->language); + if (res < 0) + return res; + } + fto = 6000; + to = 2000; + if (timeout > 0) + fto = to = timeout; + if (timeout < 0) + fto = to = 1000000000; + res = ast_readstring_full(c, s, maxlen, to, fto, "#", audiofd, ctrlfd); + return res; +} + +static int (*ast_has_voicemail_func)(const char *mailbox, const char *folder) = NULL; +static int (*ast_inboxcount_func)(const char *mailbox, int *newmsgs, int *oldmsgs) = NULL; +static int (*ast_messagecount_func)(const char *context, const char *mailbox, const char *folder) = NULL; + +void ast_install_vm_functions(int (*has_voicemail_func)(const char *mailbox, const char *folder), + int (*inboxcount_func)(const char *mailbox, int *newmsgs, int *oldmsgs), + int (*messagecount_func)(const char *context, const char *mailbox, const char *folder)) +{ + ast_has_voicemail_func = has_voicemail_func; + ast_inboxcount_func = inboxcount_func; + ast_messagecount_func = messagecount_func; +} + +void ast_uninstall_vm_functions(void) +{ + ast_has_voicemail_func = NULL; + ast_inboxcount_func = NULL; + ast_messagecount_func = NULL; +} + +int ast_app_has_voicemail(const char *mailbox, const char *folder) +{ + static int warned = 0; + if (ast_has_voicemail_func) + return ast_has_voicemail_func(mailbox, folder); + + if ((option_verbose > 2) && !warned) { + ast_verbose(VERBOSE_PREFIX_3 "Message check requested for mailbox %s/folder %s but voicemail not loaded.\n", mailbox, folder ? folder : "INBOX"); + warned++; + } + return 0; +} + + +int ast_app_inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs) +{ + static int warned = 0; + if (newmsgs) + *newmsgs = 0; + if (oldmsgs) + *oldmsgs = 0; + if (ast_inboxcount_func) + return ast_inboxcount_func(mailbox, newmsgs, oldmsgs); + + if (!warned && (option_verbose > 2)) { + warned++; + ast_verbose(VERBOSE_PREFIX_3 "Message count requested for mailbox %s but voicemail not loaded.\n", mailbox); + } + + return 0; +} + +int ast_app_messagecount(const char *context, const char *mailbox, const char *folder) +{ + static int warned = 0; + if (ast_messagecount_func) + return ast_messagecount_func(context, mailbox, folder); + + if (!warned && (option_verbose > 2)) { + warned++; + ast_verbose(VERBOSE_PREFIX_3 "Message count requested for mailbox %s@%s/%s but voicemail not loaded.\n", mailbox, context, folder); + } + + return 0; +} + +int ast_dtmf_stream(struct ast_channel *chan, struct ast_channel *peer, const char *digits, int between) +{ + const char *ptr; + int res = 0; + struct ast_frame f = { + .frametype = AST_FRAME_DTMF, + .src = "ast_dtmf_stream" + }; + + if (!between) + between = 100; + + if (peer) + res = ast_autoservice_start(peer); + + if (!res) + res = ast_waitfor(chan, 100); + + /* ast_waitfor will return the number of remaining ms on success */ + if (res < 0) + return res; + + for (ptr = digits; *ptr; ptr++) { + if (*ptr == 'w') { + /* 'w' -- wait half a second */ + if ((res = ast_safe_sleep(chan, 500))) + break; + } else if (strchr("0123456789*#abcdfABCDF", *ptr)) { + /* Character represents valid DTMF */ + if (*ptr == 'f' || *ptr == 'F') { + /* ignore return values if not supported by channel */ + ast_indicate(chan, AST_CONTROL_FLASH); + } else { + f.subclass = *ptr; + if ((res = ast_write(chan, &f))) + break; + } + /* pause between digits */ + if ((res = ast_safe_sleep(chan, between))) + break; + } else + ast_log(LOG_WARNING, "Illegal DTMF character '%c' in string. (0-9*#aAbBcCdD allowed)\n",*ptr); + } + + if (peer) { + /* Stop autoservice on the peer channel, but don't overwrite any error condition + that has occurred previously while acting on the primary channel */ + if (ast_autoservice_stop(peer) && !res) + res = -1; + } + + return res; +} + +struct linear_state { + int fd; + int autoclose; + int allowoverride; + int origwfmt; +}; + +static void linear_release(struct ast_channel *chan, void *params) +{ + struct linear_state *ls = params; + if (ls->origwfmt && ast_set_write_format(chan, ls->origwfmt)) { + ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", chan->name, ls->origwfmt); + } + if (ls->autoclose) + close(ls->fd); + free(params); +} + +static int linear_generator(struct ast_channel *chan, void *data, int len, int samples) +{ + struct ast_frame f; + short buf[2048 + AST_FRIENDLY_OFFSET / 2]; + struct linear_state *ls = data; + int res; + len = samples * 2; + if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) { + ast_log(LOG_WARNING, "Can't generate %d bytes of data!\n" ,len); + len = sizeof(buf) - AST_FRIENDLY_OFFSET; + } + memset(&f, 0, sizeof(f)); + res = read(ls->fd, buf + AST_FRIENDLY_OFFSET/2, len); + if (res > 0) { + f.frametype = AST_FRAME_VOICE; + f.subclass = AST_FORMAT_SLINEAR; + f.data = buf + AST_FRIENDLY_OFFSET/2; + f.datalen = res; + f.samples = res / 2; + f.offset = AST_FRIENDLY_OFFSET; + ast_write(chan, &f); + if (res == len) + return 0; + } + return -1; +} + +static void *linear_alloc(struct ast_channel *chan, void *params) +{ + struct linear_state *ls; + /* In this case, params is already malloc'd */ + if (params) { + ls = params; + if (ls->allowoverride) + ast_set_flag(chan, AST_FLAG_WRITE_INT); + else + ast_clear_flag(chan, AST_FLAG_WRITE_INT); + ls->origwfmt = chan->writeformat; + if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) { + ast_log(LOG_WARNING, "Unable to set '%s' to linear format (write)\n", chan->name); + free(ls); + ls = params = NULL; + } + } + return params; +} + +static struct ast_generator linearstream = +{ + alloc: linear_alloc, + release: linear_release, + generate: linear_generator, +}; + +int ast_linear_stream(struct ast_channel *chan, const char *filename, int fd, int allowoverride) +{ + struct linear_state *lin; + char tmpf[256]; + int res = -1; + int autoclose = 0; + if (fd < 0) { + if (ast_strlen_zero(filename)) + return -1; + autoclose = 1; + if (filename[0] == '/') + ast_copy_string(tmpf, filename, sizeof(tmpf)); + else + snprintf(tmpf, sizeof(tmpf), "%s/%s/%s", (char *)ast_config_AST_DATA_DIR, "sounds", filename); + fd = open(tmpf, O_RDONLY); + if (fd < 0){ + ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", tmpf, strerror(errno)); + return -1; + } + } + if ((lin = ast_calloc(1, sizeof(*lin)))) { + lin->fd = fd; + lin->allowoverride = allowoverride; + lin->autoclose = autoclose; + res = ast_activate_generator(chan, &linearstream, lin); + } + return res; +} + +int ast_control_streamfile(struct ast_channel *chan, const char *file, + const char *fwd, const char *rev, + const char *stop, const char *pause, + const char *restart, int skipms) +{ + char *breaks = NULL; + char *end = NULL; + int blen = 2; + int res; + long pause_restart_point = 0; + + if (stop) + blen += strlen(stop); + if (pause) + blen += strlen(pause); + if (restart) + blen += strlen(restart); + + if (blen > 2) { + breaks = alloca(blen + 1); + breaks[0] = '\0'; + if (stop) + strcat(breaks, stop); + if (pause) + strcat(breaks, pause); + if (restart) + strcat(breaks, restart); + } + if (chan->_state != AST_STATE_UP) + res = ast_answer(chan); + + if (file) { + if ((end = strchr(file,':'))) { + if (!strcasecmp(end, ":end")) { + *end = '\0'; + end++; + } + } + } + + for (;;) { + ast_stopstream(chan); + res = ast_streamfile(chan, file, chan->language); + if (!res) { + if (pause_restart_point) { + ast_seekstream(chan->stream, pause_restart_point, SEEK_SET); + pause_restart_point = 0; + } + else if (end) { + ast_seekstream(chan->stream, 0, SEEK_END); + end = NULL; + }; + res = ast_waitstream_fr(chan, breaks, fwd, rev, skipms); + } + + if (res < 1) + break; + + /* We go at next loop if we got the restart char */ + if (restart && strchr(restart, res)) { + ast_log(LOG_DEBUG, "we'll restart the stream here at next loop\n"); + pause_restart_point = 0; + continue; + } + + if (pause && strchr(pause, res)) { + pause_restart_point = ast_tellstream(chan->stream); + for (;;) { + ast_stopstream(chan); + res = ast_waitfordigit(chan, 1000); + if (!res) + continue; + else if (res == -1 || strchr(pause, res) || (stop && strchr(stop, res))) + break; + } + if (res == *pause) { + res = 0; + continue; + } + } + + if (res == -1) + break; + + /* if we get one of our stop chars, return it to the calling function */ + if (stop && strchr(stop, res)) + break; + } + + ast_stopstream(chan); + + return res; +} + +int ast_play_and_wait(struct ast_channel *chan, const char *fn) +{ + int d; + d = ast_streamfile(chan, fn, chan->language); + if (d) + return d; + d = ast_waitstream(chan, AST_DIGIT_ANY); + ast_stopstream(chan); + return d; +} + +static int global_silence_threshold = 128; +static int global_maxsilence = 0; + +/*! Optionally play a sound file or a beep, then record audio and video from the channel. + * @param chan Channel to playback to/record from. + * @param playfile Filename of sound to play before recording begins. + * @param recordfile Filename to record to. + * @param maxtime Maximum length of recording (in milliseconds). + * @param fmt Format(s) to record message in. Multiple formats may be specified by separating them with a '|'. + * @param duration Where to store actual length of the recorded message (in milliseconds). + * @param beep Whether to play a beep before starting to record. + * @param silencethreshold + * @param maxsilence Length of silence that will end a recording (in milliseconds). + * @param path Optional filesystem path to unlock. + * @param prepend If true, prepend the recorded audio to an existing file. + * @param acceptdtmf DTMF digits that will end the recording. + * @param canceldtmf DTMF digits that will cancel the recording. + */ + +static int __ast_play_and_record(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int beep, int silencethreshold, int maxsilence, const char *path, int prepend, const char *acceptdtmf, const char *canceldtmf) +{ + int d = 0; + char *fmts; + char comment[256]; + int x, fmtcnt = 1, res = -1, outmsg = 0; + struct ast_filestream *others[MAX_OTHER_FORMATS]; + char *sfmt[MAX_OTHER_FORMATS]; + char *stringp = NULL; + time_t start, end; + struct ast_dsp *sildet = NULL; /* silence detector dsp */ + int totalsilence = 0; + int rfmt = 0; + struct ast_silence_generator *silgen = NULL; + char prependfile[80]; + + if (silencethreshold < 0) + silencethreshold = global_silence_threshold; + + if (maxsilence < 0) + maxsilence = global_maxsilence; + + /* barf if no pointer passed to store duration in */ + if (duration == NULL) { + ast_log(LOG_WARNING, "Error play_and_record called without duration pointer\n"); + return -1; + } + + ast_log(LOG_DEBUG,"play_and_record: %s, %s, '%s'\n", playfile ? playfile : "", recordfile, fmt); + snprintf(comment, sizeof(comment), "Playing %s, Recording to: %s on %s\n", playfile ? playfile : "", recordfile, chan->name); + + if (playfile || beep) { + if (!beep) + d = ast_play_and_wait(chan, playfile); + if (d > -1) + d = ast_stream_and_wait(chan, "beep", chan->language, ""); + if (d < 0) + return -1; + } + + if (prepend) { + ast_copy_string(prependfile, recordfile, sizeof(prependfile)); + strncat(prependfile, "-prepend", sizeof(prependfile) - strlen(prependfile) - 1); + } + + fmts = ast_strdupa(fmt); + + stringp = fmts; + strsep(&stringp, "|"); + ast_log(LOG_DEBUG, "Recording Formats: sfmts=%s\n", fmts); + sfmt[0] = ast_strdupa(fmts); + + while ((fmt = strsep(&stringp, "|"))) { + if (fmtcnt > MAX_OTHER_FORMATS - 1) { + ast_log(LOG_WARNING, "Please increase MAX_OTHER_FORMATS in app.c\n"); + break; + } + sfmt[fmtcnt++] = ast_strdupa(fmt); + } + + end = start = time(NULL); /* pre-initialize end to be same as start in case we never get into loop */ + for (x = 0; x < fmtcnt; x++) { + others[x] = ast_writefile(prepend ? prependfile : recordfile, sfmt[x], comment, O_TRUNC, 0, 0700); + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "x=%d, open writing: %s format: %s, %p\n", x, prepend ? prependfile : recordfile, sfmt[x], others[x]); + + if (!others[x]) + break; + } + + if (path) + ast_unlock_path(path); + + if (maxsilence > 0) { + sildet = ast_dsp_new(); /* Create the silence detector */ + if (!sildet) { + ast_log(LOG_WARNING, "Unable to create silence detector :(\n"); + return -1; + } + ast_dsp_set_threshold(sildet, silencethreshold); + rfmt = chan->readformat; + res = ast_set_read_format(chan, AST_FORMAT_SLINEAR); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n"); + ast_dsp_free(sildet); + return -1; + } + } + + if (!prepend) { + /* Request a video update */ + ast_indicate(chan, AST_CONTROL_VIDUPDATE); + + if (ast_opt_transmit_silence) + silgen = ast_channel_start_silence_generator(chan); + } + + if (x == fmtcnt) { + /* Loop forever, writing the packets we read to the writer(s), until + we read a digit or get a hangup */ + struct ast_frame *f; + for (;;) { + res = ast_waitfor(chan, 2000); + if (!res) { + ast_log(LOG_DEBUG, "One waitfor failed, trying another\n"); + /* Try one more time in case of masq */ + res = ast_waitfor(chan, 2000); + if (!res) { + ast_log(LOG_WARNING, "No audio available on %s??\n", chan->name); + res = -1; + } + } + + if (res < 0) { + f = NULL; + break; + } + f = ast_read(chan); + if (!f) + break; + if (f->frametype == AST_FRAME_VOICE) { + /* write each format */ + for (x = 0; x < fmtcnt; x++) { + if (prepend && !others[x]) + break; + res = ast_writestream(others[x], f); + } + + /* Silence Detection */ + if (maxsilence > 0) { + int dspsilence = 0; + ast_dsp_silence(sildet, f, &dspsilence); + if (dspsilence) + totalsilence = dspsilence; + else + totalsilence = 0; + + if (totalsilence > maxsilence) { + /* Ended happily with silence */ + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Recording automatically stopped after a silence of %d seconds\n", totalsilence/1000); + res = 'S'; + outmsg = 2; + break; + } + } + /* Exit on any error */ + if (res) { + ast_log(LOG_WARNING, "Error writing frame\n"); + break; + } + } else if (f->frametype == AST_FRAME_VIDEO) { + /* Write only once */ + ast_writestream(others[0], f); + } else if (f->frametype == AST_FRAME_DTMF) { + if (prepend) { + /* stop recording with any digit */ + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "User ended message by pressing %c\n", f->subclass); + res = 't'; + outmsg = 2; + break; + } + if (strchr(acceptdtmf, f->subclass)) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "User ended message by pressing %c\n", f->subclass); + res = f->subclass; + outmsg = 2; + break; + } + if (strchr(canceldtmf, f->subclass)) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "User cancelled message by pressing %c\n", f->subclass); + res = f->subclass; + outmsg = 0; + break; + } + } + if (maxtime) { + end = time(NULL); + if (maxtime < (end - start)) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Took too long, cutting it short...\n"); + res = 't'; + outmsg = 2; + break; + } + } + ast_frfree(f); + } + if (!f) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "User hung up\n"); + res = -1; + outmsg = 1; + } else { + ast_frfree(f); + } + if (end == start) + end = time(NULL); + } else { + ast_log(LOG_WARNING, "Error creating writestream '%s', format '%s'\n", recordfile, sfmt[x]); + } + + if (!prepend) { + if (silgen) + ast_channel_stop_silence_generator(chan, silgen); + } + *duration = end - start; + + if (!prepend) { + for (x = 0; x < fmtcnt; x++) { + if (!others[x]) + break; + if (res > 0) + ast_stream_rewind(others[x], totalsilence ? totalsilence - 200 : 200); + ast_truncstream(others[x]); + ast_closestream(others[x]); + } + } + + if (prepend && outmsg) { + struct ast_filestream *realfiles[MAX_OTHER_FORMATS]; + struct ast_frame *fr; + + for (x = 0; x < fmtcnt; x++) { + snprintf(comment, sizeof(comment), "Opening the real file %s.%s\n", recordfile, sfmt[x]); + realfiles[x] = ast_readfile(recordfile, sfmt[x], comment, O_RDONLY, 0, 0); + if (!others[x] || !realfiles[x]) + break; + ast_stream_rewind(others[x], totalsilence ? totalsilence - 200 : 200); + ast_truncstream(others[x]); + /* add the original file too */ + while ((fr = ast_readframe(realfiles[x]))) { + ast_writestream(others[x], fr); + ast_frfree(fr); + } + ast_closestream(others[x]); + ast_closestream(realfiles[x]); + ast_filerename(prependfile, recordfile, sfmt[x]); + if (option_verbose > 3) + ast_verbose(VERBOSE_PREFIX_4 "Recording Format: sfmts=%s, prependfile %s, recordfile %s\n", sfmt[x], prependfile, recordfile); + ast_filedelete(prependfile, sfmt[x]); + } + } + if (rfmt && ast_set_read_format(chan, rfmt)) { + ast_log(LOG_WARNING, "Unable to restore format %s to channel '%s'\n", ast_getformatname(rfmt), chan->name); + } + if (outmsg == 2) { + ast_stream_and_wait(chan, "auth-thankyou", chan->language, ""); + } + if (sildet) + ast_dsp_free(sildet); + return res; +} + +static char default_acceptdtmf[] = "#"; +static char default_canceldtmf[] = ""; + +int ast_play_and_record_full(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int silencethreshold, int maxsilence, const char *path, const char *acceptdtmf, const char *canceldtmf) +{ + return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, 0, silencethreshold, maxsilence, path, 0, S_OR(acceptdtmf, default_acceptdtmf), S_OR(canceldtmf, default_canceldtmf)); +} + +int ast_play_and_record(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int silencethreshold, int maxsilence, const char *path) +{ + return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, 0, silencethreshold, maxsilence, path, 0, default_acceptdtmf, default_canceldtmf); +} + +int ast_play_and_prepend(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt, int *duration, int beep, int silencethreshold, int maxsilence) +{ + return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, beep, silencethreshold, maxsilence, NULL, 1, default_acceptdtmf, default_canceldtmf); +} + +/* Channel group core functions */ + +int ast_app_group_split_group(const char *data, char *group, int group_max, char *category, int category_max) +{ + int res=0; + char tmp[256]; + char *grp=NULL, *cat=NULL; + + if (!ast_strlen_zero(data)) { + ast_copy_string(tmp, data, sizeof(tmp)); + grp = tmp; + cat = strchr(tmp, '@'); + if (cat) { + *cat = '\0'; + cat++; + } + } + + if (!ast_strlen_zero(grp)) + ast_copy_string(group, grp, group_max); + else + res = -1; + + if (cat) + snprintf(category, category_max, "%s_%s", GROUP_CATEGORY_PREFIX, cat); + else + ast_copy_string(category, GROUP_CATEGORY_PREFIX, category_max); + + return res; +} + +int ast_app_group_set_channel(struct ast_channel *chan, const char *data) +{ + int res=0; + char group[80] = ""; + char category[80] = ""; + + if (!ast_app_group_split_group(data, group, sizeof(group), category, sizeof(category))) { + pbx_builtin_setvar_helper(chan, category, group); + } else + res = -1; + + return res; +} + +int ast_app_group_get_count(const char *group, const char *category) +{ + struct ast_channel *chan; + int count = 0; + const char *test; + char cat[80]; + const char *s; + + if (ast_strlen_zero(group)) + return 0; + + s = S_OR(category, GROUP_CATEGORY_PREFIX); + ast_copy_string(cat, s, sizeof(cat)); + + chan = NULL; + while ((chan = ast_channel_walk_locked(chan)) != NULL) { + test = pbx_builtin_getvar_helper(chan, cat); + if (test && !strcasecmp(test, group)) + count++; + ast_channel_unlock(chan); + } + + return count; +} + +int ast_app_group_match_get_count(const char *groupmatch, const char *category) +{ + regex_t regexbuf; + struct ast_channel *chan; + int count = 0; + const char *test; + char cat[80]; + const char *s; + + if (ast_strlen_zero(groupmatch)) + return 0; + + /* if regex compilation fails, return zero matches */ + if (regcomp(®exbuf, groupmatch, REG_EXTENDED | REG_NOSUB)) + return 0; + + s = S_OR(category, GROUP_CATEGORY_PREFIX); + ast_copy_string(cat, s, sizeof(cat)); + + chan = NULL; + while ((chan = ast_channel_walk_locked(chan)) != NULL) { + test = pbx_builtin_getvar_helper(chan, cat); + if (test && !regexec(®exbuf, test, 0, NULL, 0)) + count++; + ast_channel_unlock(chan); + } + + regfree(®exbuf); + + return count; +} + +unsigned int ast_app_separate_args(char *buf, char delim, char **array, int arraylen) +{ + int argc; + char *scan; + int paren = 0, quote = 0; + + if (!buf || !array || !arraylen) + return 0; + + memset(array, 0, arraylen * sizeof(*array)); + + scan = buf; + + for (argc = 0; *scan && (argc < arraylen - 1); argc++) { + array[argc] = scan; + for (; *scan; scan++) { + if (*scan == '(') + paren++; + else if (*scan == ')') { + if (paren) + paren--; + } else if (*scan == '"' && delim != '"') { + quote = quote ? 0 : 1; + /* Remove quote character from argument */ + memmove(scan, scan + 1, strlen(scan)); + scan--; + } else if (*scan == '\\') { + /* Literal character, don't parse */ + memmove(scan, scan + 1, strlen(scan)); + } else if ((*scan == delim) && !paren && !quote) { + *scan++ = '\0'; + break; + } + } + } + + if (*scan) + array[argc++] = scan; + + return argc; +} + +enum AST_LOCK_RESULT ast_lock_path(const char *path) +{ + char *s; + char *fs; + int res; + int fd; + int lp = strlen(path); + time_t start; + + if (!(s = alloca(lp + 10)) || !(fs = alloca(lp + 20))) { + ast_log(LOG_WARNING, "Out of memory!\n"); + return AST_LOCK_FAILURE; + } + + snprintf(fs, strlen(path) + 19, "%s/.lock-%08lx", path, ast_random()); + fd = open(fs, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) { + ast_log(LOG_ERROR, "Unable to create lock file '%s': %s\n", path, strerror(errno)); + return AST_LOCK_PATH_NOT_FOUND; + } + close(fd); + + snprintf(s, strlen(path) + 9, "%s/.lock", path); + start = time(NULL); + while (((res = link(fs, s)) < 0) && (errno == EEXIST) && (time(NULL) - start < 5)) + usleep(1); + + unlink(fs); + + if (res) { + ast_log(LOG_WARNING, "Failed to lock path '%s': %s\n", path, strerror(errno)); + return AST_LOCK_TIMEOUT; + } else { + ast_log(LOG_DEBUG, "Locked path '%s'\n", path); + return AST_LOCK_SUCCESS; + } +} + +int ast_unlock_path(const char *path) +{ + char *s; + int res; + + if (!(s = alloca(strlen(path) + 10))) { + ast_log(LOG_WARNING, "Out of memory!\n"); + return -1; + } + + snprintf(s, strlen(path) + 9, "%s/%s", path, ".lock"); + + if ((res = unlink(s))) + ast_log(LOG_ERROR, "Could not unlock path '%s': %s\n", path, strerror(errno)); + else + ast_log(LOG_DEBUG, "Unlocked path '%s'\n", path); + + return res; +} + +int ast_record_review(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, const char *path) +{ + int silencethreshold = 128; + int maxsilence=0; + int res = 0; + int cmd = 0; + int max_attempts = 3; + int attempts = 0; + int recorded = 0; + int message_exists = 0; + /* Note that urgent and private are for flagging messages as such in the future */ + + /* barf if no pointer passed to store duration in */ + if (duration == NULL) { + ast_log(LOG_WARNING, "Error ast_record_review called without duration pointer\n"); + return -1; + } + + cmd = '3'; /* Want to start by recording */ + + while ((cmd >= 0) && (cmd != 't')) { + switch (cmd) { + case '1': + if (!message_exists) { + /* In this case, 1 is to record a message */ + cmd = '3'; + break; + } else { + ast_stream_and_wait(chan, "vm-msgsaved", chan->language, ""); + cmd = 't'; + return res; + } + case '2': + /* Review */ + ast_verbose(VERBOSE_PREFIX_3 "Reviewing the recording\n"); + cmd = ast_stream_and_wait(chan, recordfile, chan->language, AST_DIGIT_ANY); + break; + case '3': + message_exists = 0; + /* Record */ + if (recorded == 1) + ast_verbose(VERBOSE_PREFIX_3 "Re-recording\n"); + else + ast_verbose(VERBOSE_PREFIX_3 "Recording\n"); + recorded = 1; + cmd = ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, silencethreshold, maxsilence, path); + if (cmd == -1) { + /* User has hung up, no options to give */ + return cmd; + } + if (cmd == '0') { + break; + } else if (cmd == '*') { + break; + } + else { + /* If all is well, a message exists */ + message_exists = 1; + cmd = 0; + } + break; + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '*': + case '#': + cmd = ast_play_and_wait(chan, "vm-sorry"); + break; + default: + if (message_exists) { + cmd = ast_play_and_wait(chan, "vm-review"); + } + else { + cmd = ast_play_and_wait(chan, "vm-torerecord"); + if (!cmd) + cmd = ast_waitfordigit(chan, 600); + } + + if (!cmd) + cmd = ast_waitfordigit(chan, 6000); + if (!cmd) { + attempts++; + } + if (attempts > max_attempts) { + cmd = 't'; + } + } + } + if (cmd == 't') + cmd = 0; + return cmd; +} + +#define RES_UPONE (1 << 16) +#define RES_EXIT (1 << 17) +#define RES_REPEAT (1 << 18) +#define RES_RESTART ((1 << 19) | RES_REPEAT) + +static int ast_ivr_menu_run_internal(struct ast_channel *chan, struct ast_ivr_menu *menu, void *cbdata); + +static int ivr_dispatch(struct ast_channel *chan, struct ast_ivr_option *option, char *exten, void *cbdata) +{ + int res; + int (*ivr_func)(struct ast_channel *, void *); + char *c; + char *n; + + switch(option->action) { + case AST_ACTION_UPONE: + return RES_UPONE; + case AST_ACTION_EXIT: + return RES_EXIT | (((unsigned long)(option->adata)) & 0xffff); + case AST_ACTION_REPEAT: + return RES_REPEAT | (((unsigned long)(option->adata)) & 0xffff); + case AST_ACTION_RESTART: + return RES_RESTART ; + case AST_ACTION_NOOP: + return 0; + case AST_ACTION_BACKGROUND: + res = ast_stream_and_wait(chan, (char *)option->adata, chan->language, AST_DIGIT_ANY); + if (res < 0) { + ast_log(LOG_NOTICE, "Unable to find file '%s'!\n", (char *)option->adata); + res = 0; + } + return res; + case AST_ACTION_PLAYBACK: + res = ast_stream_and_wait(chan, (char *)option->adata, chan->language, ""); + if (res < 0) { + ast_log(LOG_NOTICE, "Unable to find file '%s'!\n", (char *)option->adata); + res = 0; + } + return res; + case AST_ACTION_MENU: + res = ast_ivr_menu_run_internal(chan, (struct ast_ivr_menu *)option->adata, cbdata); + /* Do not pass entry errors back up, treaat ast though ti was an "UPONE" */ + if (res == -2) + res = 0; + return res; + case AST_ACTION_WAITOPTION: + res = ast_waitfordigit(chan, 1000 * (chan->pbx ? chan->pbx->rtimeout : 10)); + if (!res) + return 't'; + return res; + case AST_ACTION_CALLBACK: + ivr_func = option->adata; + res = ivr_func(chan, cbdata); + return res; + case AST_ACTION_TRANSFER: + res = ast_parseable_goto(chan, option->adata); + return 0; + case AST_ACTION_PLAYLIST: + case AST_ACTION_BACKLIST: + res = 0; + c = ast_strdupa(option->adata); + while ((n = strsep(&c, ";"))) { + if ((res = ast_stream_and_wait(chan, n, chan->language, + (option->action == AST_ACTION_BACKLIST) ? AST_DIGIT_ANY : ""))) + break; + } + ast_stopstream(chan); + return res; + default: + ast_log(LOG_NOTICE, "Unknown dispatch function %d, ignoring!\n", option->action); + return 0; + }; + return -1; +} + +static int option_exists(struct ast_ivr_menu *menu, char *option) +{ + int x; + for (x = 0; menu->options[x].option; x++) + if (!strcasecmp(menu->options[x].option, option)) + return x; + return -1; +} + +static int option_matchmore(struct ast_ivr_menu *menu, char *option) +{ + int x; + for (x = 0; menu->options[x].option; x++) + if ((!strncasecmp(menu->options[x].option, option, strlen(option))) && + (menu->options[x].option[strlen(option)])) + return x; + return -1; +} + +static int read_newoption(struct ast_channel *chan, struct ast_ivr_menu *menu, char *exten, int maxexten) +{ + int res=0; + int ms; + while (option_matchmore(menu, exten)) { + ms = chan->pbx ? chan->pbx->dtimeout : 5000; + if (strlen(exten) >= maxexten - 1) + break; + res = ast_waitfordigit(chan, ms); + if (res < 1) + break; + exten[strlen(exten) + 1] = '\0'; + exten[strlen(exten)] = res; + } + return res > 0 ? 0 : res; +} + +static int ast_ivr_menu_run_internal(struct ast_channel *chan, struct ast_ivr_menu *menu, void *cbdata) +{ + /* Execute an IVR menu structure */ + int res=0; + int pos = 0; + int retries = 0; + char exten[AST_MAX_EXTENSION] = "s"; + if (option_exists(menu, "s") < 0) { + strcpy(exten, "g"); + if (option_exists(menu, "g") < 0) { + ast_log(LOG_WARNING, "No 's' nor 'g' extension in menu '%s'!\n", menu->title); + return -1; + } + } + while(!res) { + while(menu->options[pos].option) { + if (!strcasecmp(menu->options[pos].option, exten)) { + res = ivr_dispatch(chan, menu->options + pos, exten, cbdata); + ast_log(LOG_DEBUG, "IVR Dispatch of '%s' (pos %d) yields %d\n", exten, pos, res); + if (res < 0) + break; + else if (res & RES_UPONE) + return 0; + else if (res & RES_EXIT) + return res; + else if (res & RES_REPEAT) { + int maxretries = res & 0xffff; + if ((res & RES_RESTART) == RES_RESTART) { + retries = 0; + } else + retries++; + if (!maxretries) + maxretries = 3; + if ((maxretries > 0) && (retries >= maxretries)) { + ast_log(LOG_DEBUG, "Max retries %d exceeded\n", maxretries); + return -2; + } else { + if (option_exists(menu, "g") > -1) + strcpy(exten, "g"); + else if (option_exists(menu, "s") > -1) + strcpy(exten, "s"); + } + pos = 0; + continue; + } else if (res && strchr(AST_DIGIT_ANY, res)) { + ast_log(LOG_DEBUG, "Got start of extension, %c\n", res); + exten[1] = '\0'; + exten[0] = res; + if ((res = read_newoption(chan, menu, exten, sizeof(exten)))) + break; + if (option_exists(menu, exten) < 0) { + if (option_exists(menu, "i")) { + ast_log(LOG_DEBUG, "Invalid extension entered, going to 'i'!\n"); + strcpy(exten, "i"); + pos = 0; + continue; + } else { + ast_log(LOG_DEBUG, "Aborting on invalid entry, with no 'i' option!\n"); + res = -2; + break; + } + } else { + ast_log(LOG_DEBUG, "New existing extension: %s\n", exten); + pos = 0; + continue; + } + } + } + pos++; + } + ast_log(LOG_DEBUG, "Stopping option '%s', res is %d\n", exten, res); + pos = 0; + if (!strcasecmp(exten, "s")) + strcpy(exten, "g"); + else + break; + } + return res; +} + +int ast_ivr_menu_run(struct ast_channel *chan, struct ast_ivr_menu *menu, void *cbdata) +{ + int res = ast_ivr_menu_run_internal(chan, menu, cbdata); + /* Hide internal coding */ + return res > 0 ? 0 : res; +} + +char *ast_read_textfile(const char *filename) +{ + int fd; + char *output = NULL; + struct stat filesize; + int count = 0; + int res; + if (stat(filename, &filesize) == -1) { + ast_log(LOG_WARNING, "Error can't stat %s\n", filename); + return NULL; + } + count = filesize.st_size + 1; + fd = open(filename, O_RDONLY); + if (fd < 0) { + ast_log(LOG_WARNING, "Cannot open file '%s' for reading: %s\n", filename, strerror(errno)); + return NULL; + } + if ((output = ast_malloc(count))) { + res = read(fd, output, count - 1); + if (res == count - 1) { + output[res] = '\0'; + } else { + ast_log(LOG_WARNING, "Short read of %s (%d of %d): %s\n", filename, res, count - 1, strerror(errno)); + free(output); + output = NULL; + } + } + close(fd); + return output; +} + +int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags *flags, char **args, char *optstr) +{ + char *s; + int curarg; + unsigned int argloc; + char *arg; + int res = 0; + + ast_clear_flag(flags, AST_FLAGS_ALL); + + if (!optstr) + return 0; + + s = optstr; + while (*s) { + curarg = *s++ & 0x7f; /* the array (in app.h) has 128 entries */ + ast_set_flag(flags, options[curarg].flag); + argloc = options[curarg].arg_index; + if (*s == '(') { + /* Has argument */ + arg = ++s; + s = strchr(s, ')'); + if (*s) { + if (argloc) + args[argloc - 1] = arg; + *s++ = '\0'; + } else { + ast_log(LOG_WARNING, "Missing closing parenthesis for argument '%c' in string '%s'\n", curarg, arg); + res = -1; + } + } else if (argloc) { + args[argloc - 1] = NULL; + } + } + + return res; +} + diff --git a/main/ast_expr2.c b/main/ast_expr2.c new file mode 100644 index 000000000..6532093c5 --- /dev/null +++ b/main/ast_expr2.c @@ -0,0 +1,2871 @@ +/* A Bison parser, made by GNU Bison 2.3. */ + +/* Skeleton implementation for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "2.3" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 1 + +/* Using locations. */ +#define YYLSP_NEEDED 1 + +/* Substitute the variable and function names. */ +#define yyparse ast_yyparse +#define yylex ast_yylex +#define yyerror ast_yyerror +#define yylval ast_yylval +#define yychar ast_yychar +#define yydebug ast_yydebug +#define yynerrs ast_yynerrs +#define yylloc ast_yylloc + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + TOK_COLONCOLON = 258, + TOK_COND = 259, + TOK_OR = 260, + TOK_AND = 261, + TOK_NE = 262, + TOK_LE = 263, + TOK_GE = 264, + TOK_LT = 265, + TOK_GT = 266, + TOK_EQ = 267, + TOK_MINUS = 268, + TOK_PLUS = 269, + TOK_MOD = 270, + TOK_DIV = 271, + TOK_MULT = 272, + TOK_COMPL = 273, + TOK_EQTILDE = 274, + TOK_COLON = 275, + TOK_LP = 276, + TOK_RP = 277, + TOKEN = 278 + }; +#endif +/* Tokens. */ +#define TOK_COLONCOLON 258 +#define TOK_COND 259 +#define TOK_OR 260 +#define TOK_AND 261 +#define TOK_NE 262 +#define TOK_LE 263 +#define TOK_GE 264 +#define TOK_LT 265 +#define TOK_GT 266 +#define TOK_EQ 267 +#define TOK_MINUS 268 +#define TOK_PLUS 269 +#define TOK_MOD 270 +#define TOK_DIV 271 +#define TOK_MULT 272 +#define TOK_COMPL 273 +#define TOK_EQTILDE 274 +#define TOK_COLON 275 +#define TOK_LP 276 +#define TOK_RP 277 +#define TOKEN 278 + + + + +/* Copy the first part of user declarations. */ +#line 1 "ast_expr2.y" + +/* Written by Pace Willisson (pace@blitz.com) + * and placed in the public domain. + * + * Largely rewritten by J.T. Conklin (jtc@wimsey.com) + * + * And then overhauled twice by Steve Murphy (murf@e-tools.com) + * to add double-quoted strings, allow mult. spaces, improve + * error messages, and then to fold in a flex scanner for the + * yylex operation. + * + * $FreeBSD: src/bin/expr/expr.y,v 1.16 2000/07/22 10:59:36 se Exp $ + */ + +#include "asterisk.h" + +#ifndef STANDALONE +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 40546 $") +#endif + +#include +#include +#include +#include +#include +#include +#include +#if !defined(SOLARIS) && !defined(__CYGWIN__) +#include +#else +#define quad_t int64_t +#endif +#include +#include +#include + +#include "asterisk/ast_expr.h" +#include "asterisk/logger.h" + +#if defined(LONG_LONG_MIN) && !defined(QUAD_MIN) +#define QUAD_MIN LONG_LONG_MIN +#endif +#if defined(LONG_LONG_MAX) && !defined(QUAD_MAX) +#define QUAD_MAX LONG_LONG_MAX +#endif + +# if ! defined(QUAD_MIN) +# define QUAD_MIN (-0x7fffffffffffffffLL-1) +# endif +# if ! defined(QUAD_MAX) +# define QUAD_MAX (0x7fffffffffffffffLL) +# endif + +#define YYPARSE_PARAM parseio +#define YYLEX_PARAM ((struct parse_io *)parseio)->scanner +#define YYERROR_VERBOSE 1 +extern char extra_error_message[4095]; +extern int extra_error_message_supplied; + +enum valtype { + AST_EXPR_integer, AST_EXPR_numeric_string, AST_EXPR_string +} ; + +#ifdef STANDALONE +void ast_log(int level, const char *file, int line, const char *function, const char *fmt, ...) __attribute__ ((format (printf,5,6))); +#endif + +struct val { + enum valtype type; + union { + char *s; + quad_t i; + } u; +} ; + +typedef void *yyscan_t; + +struct parse_io +{ + char *string; + struct val *val; + yyscan_t scanner; +}; + +static int chk_div __P((quad_t, quad_t)); +static int chk_minus __P((quad_t, quad_t, quad_t)); +static int chk_plus __P((quad_t, quad_t, quad_t)); +static int chk_times __P((quad_t, quad_t, quad_t)); +static void free_value __P((struct val *)); +static int is_zero_or_null __P((struct val *)); +static int isstring __P((struct val *)); +static struct val *make_integer __P((quad_t)); +static struct val *make_str __P((const char *)); +static struct val *op_and __P((struct val *, struct val *)); +static struct val *op_colon __P((struct val *, struct val *)); +static struct val *op_eqtilde __P((struct val *, struct val *)); +static struct val *op_div __P((struct val *, struct val *)); +static struct val *op_eq __P((struct val *, struct val *)); +static struct val *op_ge __P((struct val *, struct val *)); +static struct val *op_gt __P((struct val *, struct val *)); +static struct val *op_le __P((struct val *, struct val *)); +static struct val *op_lt __P((struct val *, struct val *)); +static struct val *op_cond __P((struct val *, struct val *, struct val *)); +static struct val *op_minus __P((struct val *, struct val *)); +static struct val *op_negate __P((struct val *)); +static struct val *op_compl __P((struct val *)); +static struct val *op_ne __P((struct val *, struct val *)); +static struct val *op_or __P((struct val *, struct val *)); +static struct val *op_plus __P((struct val *, struct val *)); +static struct val *op_rem __P((struct val *, struct val *)); +static struct val *op_times __P((struct val *, struct val *)); +static quad_t to_integer __P((struct val *)); +static void to_string __P((struct val *)); + +/* uh, if I want to predeclare yylex with a YYLTYPE, I have to predeclare the yyltype... sigh */ +typedef struct yyltype +{ + int first_line; + int first_column; + + int last_line; + int last_column; +} yyltype; + +# define YYLTYPE yyltype +# define YYLTYPE_IS_TRIVIAL 1 + +/* we will get warning about no prototype for yylex! But we can't + define it here, we have no definition yet for YYSTYPE. */ + +int ast_yyerror(const char *,YYLTYPE *, struct parse_io *); + +/* I wanted to add args to the yyerror routine, so I could print out + some useful info about the error. Not as easy as it looks, but it + is possible. */ +#define ast_yyerror(x) ast_yyerror(x,&yyloc,parseio) +#define DESTROY(x) {if((x)->type == AST_EXPR_numeric_string || (x)->type == AST_EXPR_string) free((x)->u.s); (x)->u.s = 0; free(x);} + + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 0 +#endif + +/* Enabling the token table. */ +#ifndef YYTOKEN_TABLE +# define YYTOKEN_TABLE 0 +#endif + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE +#line 147 "ast_expr2.y" +{ + struct val *val; +} +/* Line 193 of yacc.c. */ +#line 293 "ast_expr2.c" + YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +# define yyltype YYLTYPE /* obsolescent; will be withdrawn */ +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + +/* Copy the second part of user declarations. */ +#line 151 "ast_expr2.y" + +extern int ast_yylex __P((YYSTYPE *, YYLTYPE *, yyscan_t)); + + +/* Line 216 of yacc.c. */ +#line 321 "ast_expr2.c" + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#elif (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +typedef signed char yytype_int8; +#else +typedef short int yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(msgid) dgettext ("bison-runtime", msgid) +# endif +# endif +# ifndef YY_ +# define YY_(msgid) msgid +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(e) ((void) (e)) +#else +# define YYUSE(e) /* empty */ +#endif + +/* Identity function, used to suppress warnings about constant conditions. */ +#ifndef lint +# define YYID(n) (n) +#else +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static int +YYID (int i) +#else +static int +YYID (i) + int i; +#endif +{ + return i; +} +#endif + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0)) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined _STDLIB_H \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL \ + && defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss; + YYSTYPE yyvs; + YYLTYPE yyls; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE) + sizeof (YYLTYPE)) \ + + 2 * YYSTACK_GAP_MAXIMUM) + +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (YYID (0)) +# endif +# endif + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack, Stack, yysize); \ + Stack = &yyptr->Stack; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (YYID (0)) + +#endif + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 10 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 140 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 24 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 3 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 23 +/* YYNRULES -- Number of states. */ +#define YYNSTATES 46 + +/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 278 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +static const yytype_uint8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23 +}; + +#if YYDEBUG +/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ +static const yytype_uint8 yyprhs[] = +{ + 0, 0, 3, 5, 6, 8, 12, 16, 20, 24, + 28, 32, 36, 40, 44, 48, 52, 55, 58, 62, + 66, 70, 74, 78 +}; + +/* YYRHS -- A `-1'-separated list of the rules' RHS. */ +static const yytype_int8 yyrhs[] = +{ + 25, 0, -1, 26, -1, -1, 23, -1, 21, 26, + 22, -1, 26, 5, 26, -1, 26, 6, 26, -1, + 26, 12, 26, -1, 26, 11, 26, -1, 26, 10, + 26, -1, 26, 9, 26, -1, 26, 8, 26, -1, + 26, 7, 26, -1, 26, 14, 26, -1, 26, 13, + 26, -1, 13, 26, -1, 18, 26, -1, 26, 17, + 26, -1, 26, 16, 26, -1, 26, 15, 26, -1, + 26, 20, 26, -1, 26, 19, 26, -1, 26, 4, + 26, 3, 26, -1 +}; + +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const yytype_uint16 yyrline[] = +{ + 0, 175, 175, 183, 190, 191, 195, 199, 203, 207, + 211, 215, 219, 223, 227, 231, 235, 239, 243, 247, + 251, 255, 259, 263 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "$end", "error", "$undefined", "TOK_COLONCOLON", "TOK_COND", "TOK_OR", + "TOK_AND", "TOK_NE", "TOK_LE", "TOK_GE", "TOK_LT", "TOK_GT", "TOK_EQ", + "TOK_MINUS", "TOK_PLUS", "TOK_MOD", "TOK_DIV", "TOK_MULT", "TOK_COMPL", + "TOK_EQTILDE", "TOK_COLON", "TOK_LP", "TOK_RP", "TOKEN", "$accept", + "start", "expr", 0 +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to + token YYLEX-NUM. */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 276, 277, 278 +}; +# endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint8 yyr1[] = +{ + 0, 24, 25, 25, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 1, 0, 1, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 2, 2, 3, 3, + 3, 3, 3, 5 +}; + +/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state + STATE-NUM when YYTABLE doesn't specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 3, 0, 0, 0, 4, 0, 2, 16, 17, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, + 7, 13, 12, 11, 10, 9, 8, 15, 14, 20, + 19, 18, 22, 21, 0, 23 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + -1, 5, 6 +}; + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +#define YYPACT_NINF -13 +static const yytype_int8 yypact[] = +{ + 109, 109, 109, 109, -13, 6, 59, 106, 106, 22, + -13, 109, 109, 109, 109, 109, 109, 109, 109, 109, + 109, 109, 109, 109, 109, 109, 109, -13, 42, 90, + 104, 120, 120, 120, 120, 120, 120, -12, -12, 106, + 106, 106, -13, -13, 109, 75 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -13, -13, -1 +}; + +/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule which + number is the opposite. If zero, do what YYDEFACT says. + If YYTABLE_NINF, syntax error. */ +#define YYTABLE_NINF -1 +static const yytype_uint8 yytable[] = +{ + 7, 8, 9, 22, 23, 24, 10, 25, 26, 0, + 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, + 38, 39, 40, 41, 42, 43, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 0, 25, 26, 45, 27, 44, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 0, 25, 26, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 0, 25, 26, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 0, 25, 26, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 0, 25, + 26, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 1, 25, 26, 25, 26, 2, 0, 0, + 3, 0, 4, 20, 21, 22, 23, 24, 0, 25, + 26 +}; + +static const yytype_int8 yycheck[] = +{ + 1, 2, 3, 15, 16, 17, 0, 19, 20, -1, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + -1, 19, 20, 44, 22, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + -1, 19, 20, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, -1, 19, 20, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, -1, 19, 20, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, -1, 19, + 20, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 13, 19, 20, 19, 20, 18, -1, -1, + 21, -1, 23, 13, 14, 15, 16, 17, -1, 19, + 20 +}; + +/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 13, 18, 21, 23, 25, 26, 26, 26, 26, + 0, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 19, 20, 22, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 3, 26 +}; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. */ + +#define YYFAIL goto yyerrlab + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + yytoken = YYTRANSLATE (yychar); \ + YYPOPSTACK (1); \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (YYID (0)) + + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (YYID (N)) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (YYID (0)) +#endif + + +/* YY_LOCATION_PRINT -- Print the location on the stream. + This macro was not mandated originally: define only if we know + we won't break user code: when these are the locations we know. */ + +#ifndef YY_LOCATION_PRINT +# if YYLTYPE_IS_TRIVIAL +# define YY_LOCATION_PRINT(File, Loc) \ + fprintf (File, "%d.%d-%d.%d", \ + (Loc).first_line, (Loc).first_column, \ + (Loc).last_line, (Loc).last_column) +# else +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#ifdef YYLEX_PARAM +# define YYLEX yylex (&yylval, &yylloc, YYLEX_PARAM) +#else +# define YYLEX yylex (&yylval, &yylloc) +#endif + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (YYID (0)) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value, Location); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (YYID (0)) + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp) +#else +static void +yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + YYLTYPE const * const yylocationp; +#endif +{ + if (!yyvaluep) + return; + YYUSE (yylocationp); +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# else + YYUSE (yyoutput); +# endif + switch (yytype) + { + default: + break; + } +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp) +#else +static void +yy_symbol_print (yyoutput, yytype, yyvaluep, yylocationp) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; + YYLTYPE const * const yylocationp; +#endif +{ + if (yytype < YYNTOKENS) + YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); + else + YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + + YY_LOCATION_PRINT (yyoutput, *yylocationp); + YYFPRINTF (yyoutput, ": "); + yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_stack_print (yytype_int16 *bottom, yytype_int16 *top) +#else +static void +yy_stack_print (bottom, top) + yytype_int16 *bottom; + yytype_int16 *top; +#endif +{ + YYFPRINTF (stderr, "Stack now"); + for (; bottom <= top; ++bottom) + YYFPRINTF (stderr, " %d", *bottom); + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (YYID (0)) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_reduce_print (YYSTYPE *yyvsp, YYLTYPE *yylsp, int yyrule) +#else +static void +yy_reduce_print (yyvsp, yylsp, yyrule) + YYSTYPE *yyvsp; + YYLTYPE *yylsp; + int yyrule; +#endif +{ + int yynrhs = yyr2[yyrule]; + int yyi; + unsigned long int yylno = yyrline[yyrule]; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + fprintf (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi], + &(yyvsp[(yyi + 1) - (yynrhs)]) + , &(yylsp[(yyi + 1) - (yynrhs)]) ); + fprintf (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyvsp, yylsp, Rule); \ +} while (YYID (0)) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static YYSIZE_T +yystrlen (const char *yystr) +#else +static YYSIZE_T +yystrlen (yystr) + const char *yystr; +#endif +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static char * +yystpcpy (char *yydest, const char *yysrc) +#else +static char * +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +#endif +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYSIZE_T yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into YYRESULT an error message about the unexpected token + YYCHAR while in state YYSTATE. Return the number of bytes copied, + including the terminating null byte. If YYRESULT is null, do not + copy anything; just return the number of bytes that would be + copied. As a special case, return 0 if an ordinary "syntax error" + message will do. Return YYSIZE_MAXIMUM if overflow occurs during + size calculation. */ +static YYSIZE_T +yysyntax_error (char *yyresult, int yystate, int yychar) +{ + int yyn = yypact[yystate]; + + if (! (YYPACT_NINF < yyn && yyn <= YYLAST)) + return 0; + else + { + int yytype = YYTRANSLATE (yychar); + YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]); + YYSIZE_T yysize = yysize0; + YYSIZE_T yysize1; + int yysize_overflow = 0; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + int yyx; + +# if 0 + /* This is so xgettext sees the translatable formats that are + constructed on the fly. */ + YY_("syntax error, unexpected %s"); + YY_("syntax error, unexpected %s, expecting %s"); + YY_("syntax error, unexpected %s, expecting %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"); +# endif + char *yyfmt; + char const *yyf; + static char const yyunexpected[] = "syntax error, unexpected %s"; + static char const yyexpecting[] = ", expecting %s"; + static char const yyor[] = " or %s"; + char yyformat[sizeof yyunexpected + + sizeof yyexpecting - 1 + + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2) + * (sizeof yyor - 1))]; + char const *yyprefix = yyexpecting; + + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yycount = 1; + + yyarg[0] = yytname[yytype]; + yyfmt = yystpcpy (yyformat, yyunexpected); + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + yyformat[sizeof yyunexpected - 1] = '\0'; + break; + } + yyarg[yycount++] = yytname[yyx]; + yysize1 = yysize + yytnamerr (0, yytname[yyx]); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + yyfmt = yystpcpy (yyfmt, yyprefix); + yyprefix = yyor; + } + + yyf = YY_(yyformat); + yysize1 = yysize + yystrlen (yyf); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + + if (yysize_overflow) + return YYSIZE_MAXIMUM; + + if (yyresult) + { + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + char *yyp = yyresult; + int yyi = 0; + while ((*yyp = *yyf) != '\0') + { + if (*yyp == '%' && yyf[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyf += 2; + } + else + { + yyp++; + yyf++; + } + } + } + return yysize; + } +} +#endif /* YYERROR_VERBOSE */ + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, YYLTYPE *yylocationp) +#else +static void +yydestruct (yymsg, yytype, yyvaluep, yylocationp) + const char *yymsg; + int yytype; + YYSTYPE *yyvaluep; + YYLTYPE *yylocationp; +#endif +{ + YYUSE (yyvaluep); + YYUSE (yylocationp); + + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + case 3: /* "TOK_COLONCOLON" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1268 "ast_expr2.c" + break; + case 4: /* "TOK_COND" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1273 "ast_expr2.c" + break; + case 5: /* "TOK_OR" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1278 "ast_expr2.c" + break; + case 6: /* "TOK_AND" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1283 "ast_expr2.c" + break; + case 7: /* "TOK_NE" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1288 "ast_expr2.c" + break; + case 8: /* "TOK_LE" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1293 "ast_expr2.c" + break; + case 9: /* "TOK_GE" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1298 "ast_expr2.c" + break; + case 10: /* "TOK_LT" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1303 "ast_expr2.c" + break; + case 11: /* "TOK_GT" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1308 "ast_expr2.c" + break; + case 12: /* "TOK_EQ" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1313 "ast_expr2.c" + break; + case 13: /* "TOK_MINUS" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1318 "ast_expr2.c" + break; + case 14: /* "TOK_PLUS" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1323 "ast_expr2.c" + break; + case 15: /* "TOK_MOD" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1328 "ast_expr2.c" + break; + case 16: /* "TOK_DIV" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1333 "ast_expr2.c" + break; + case 17: /* "TOK_MULT" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1338 "ast_expr2.c" + break; + case 18: /* "TOK_COMPL" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1343 "ast_expr2.c" + break; + case 19: /* "TOK_EQTILDE" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1348 "ast_expr2.c" + break; + case 20: /* "TOK_COLON" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1353 "ast_expr2.c" + break; + case 21: /* "TOK_LP" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1358 "ast_expr2.c" + break; + case 22: /* "TOK_RP" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1363 "ast_expr2.c" + break; + case 23: /* "TOKEN" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1368 "ast_expr2.c" + break; + case 26: /* "expr" */ +#line 169 "ast_expr2.y" + { free_value((yyvaluep->val)); }; +#line 1373 "ast_expr2.c" + break; + + default: + break; + } +} + + +/* Prevent warnings from -Wmissing-prototypes. */ + +#ifdef YYPARSE_PARAM +#if defined __STDC__ || defined __cplusplus +int yyparse (void *YYPARSE_PARAM); +#else +int yyparse (); +#endif +#else /* ! YYPARSE_PARAM */ +#if defined __STDC__ || defined __cplusplus +int yyparse (void); +#else +int yyparse (); +#endif +#endif /* ! YYPARSE_PARAM */ + + + + + + +/*----------. +| yyparse. | +`----------*/ + +#ifdef YYPARSE_PARAM +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void *YYPARSE_PARAM) +#else +int +yyparse (YYPARSE_PARAM) + void *YYPARSE_PARAM; +#endif +#else /* ! YYPARSE_PARAM */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void) +#else +int +yyparse () + +#endif +#endif +{ + /* The look-ahead symbol. */ +int yychar; + +/* The semantic value of the look-ahead symbol. */ +YYSTYPE yylval; + +/* Number of syntax errors so far. */ +int yynerrs; +/* Location data for the look-ahead symbol. */ +YYLTYPE yylloc; + + int yystate; + int yyn; + int yyresult; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + /* Look-ahead token as an internal (translated) token number. */ + int yytoken = 0; +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + + /* Three stacks and their tools: + `yyss': related to states, + `yyvs': related to semantic values, + `yyls': related to locations. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss = yyssa; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs = yyvsa; + YYSTYPE *yyvsp; + + /* The location stack. */ + YYLTYPE yylsa[YYINITDEPTH]; + YYLTYPE *yyls = yylsa; + YYLTYPE *yylsp; + /* The locations where the error started and ended. */ + YYLTYPE yyerror_range[2]; + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N), yylsp -= (N)) + + YYSIZE_T yystacksize = YYINITDEPTH; + + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + YYLTYPE yyloc; + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + + yyssp = yyss; + yyvsp = yyvs; + yylsp = yyls; +#if YYLTYPE_IS_TRIVIAL + /* Initialize the default location before parsing starts. */ + yylloc.first_line = yylloc.last_line = 1; + yylloc.first_column = yylloc.last_column = 0; +#endif + + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + YYLTYPE *yyls1 = yyls; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yyls1, yysize * sizeof (*yylsp), + &yystacksize); + yyls = yyls1; + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss); + YYSTACK_RELOCATE (yyvs); + YYSTACK_RELOCATE (yyls); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + yylsp = yyls + yysize - 1; + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + look-ahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to look-ahead token. */ + yyn = yypact[yystate]; + if (yyn == YYPACT_NINF) + goto yydefault; + + /* Not known => get a look-ahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yyn == 0 || yyn == YYTABLE_NINF) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + if (yyn == YYFINAL) + YYACCEPT; + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the look-ahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token unless it is eof. */ + if (yychar != YYEOF) + yychar = YYEMPTY; + + yystate = yyn; + *++yyvsp = yylval; + *++yylsp = yylloc; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + /* Default location. */ + YYLLOC_DEFAULT (yyloc, (yylsp - yylen), yylen); + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 2: +#line 175 "ast_expr2.y" + { ((struct parse_io *)parseio)->val = (struct val *)calloc(sizeof(struct val),1); + ((struct parse_io *)parseio)->val->type = (yyvsp[(1) - (1)].val)->type; + if( (yyvsp[(1) - (1)].val)->type == AST_EXPR_integer ) + ((struct parse_io *)parseio)->val->u.i = (yyvsp[(1) - (1)].val)->u.i; + else + ((struct parse_io *)parseio)->val->u.s = (yyvsp[(1) - (1)].val)->u.s; + free((yyvsp[(1) - (1)].val)); + ;} + break; + + case 3: +#line 183 "ast_expr2.y" + {/* nothing */ ((struct parse_io *)parseio)->val = (struct val *)calloc(sizeof(struct val),1); + ((struct parse_io *)parseio)->val->type = AST_EXPR_string; + ((struct parse_io *)parseio)->val->u.s = strdup(""); + ;} + break; + + case 4: +#line 190 "ast_expr2.y" + { (yyval.val)= (yyvsp[(1) - (1)].val);;} + break; + + case 5: +#line 191 "ast_expr2.y" + { (yyval.val) = (yyvsp[(2) - (3)].val); + (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0; + DESTROY((yyvsp[(1) - (3)].val)); DESTROY((yyvsp[(3) - (3)].val)); ;} + break; + + case 6: +#line 195 "ast_expr2.y" + { (yyval.val) = op_or ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val)); + DESTROY((yyvsp[(2) - (3)].val)); + (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0;;} + break; + + case 7: +#line 199 "ast_expr2.y" + { (yyval.val) = op_and ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val)); + DESTROY((yyvsp[(2) - (3)].val)); + (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0;;} + break; + + case 8: +#line 203 "ast_expr2.y" + { (yyval.val) = op_eq ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val)); + DESTROY((yyvsp[(2) - (3)].val)); + (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0;;} + break; + + case 9: +#line 207 "ast_expr2.y" + { (yyval.val) = op_gt ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val)); + DESTROY((yyvsp[(2) - (3)].val)); + (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0;;} + break; + + case 10: +#line 211 "ast_expr2.y" + { (yyval.val) = op_lt ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val)); + DESTROY((yyvsp[(2) - (3)].val)); + (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0;;} + break; + + case 11: +#line 215 "ast_expr2.y" + { (yyval.val) = op_ge ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val)); + DESTROY((yyvsp[(2) - (3)].val)); + (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0;;} + break; + + case 12: +#line 219 "ast_expr2.y" + { (yyval.val) = op_le ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val)); + DESTROY((yyvsp[(2) - (3)].val)); + (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0;;} + break; + + case 13: +#line 223 "ast_expr2.y" + { (yyval.val) = op_ne ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val)); + DESTROY((yyvsp[(2) - (3)].val)); + (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0;;} + break; + + case 14: +#line 227 "ast_expr2.y" + { (yyval.val) = op_plus ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val)); + DESTROY((yyvsp[(2) - (3)].val)); + (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0;;} + break; + + case 15: +#line 231 "ast_expr2.y" + { (yyval.val) = op_minus ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val)); + DESTROY((yyvsp[(2) - (3)].val)); + (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0;;} + break; + + case 16: +#line 235 "ast_expr2.y" + { (yyval.val) = op_negate ((yyvsp[(2) - (2)].val)); + DESTROY((yyvsp[(1) - (2)].val)); + (yyloc).first_column = (yylsp[(1) - (2)]).first_column; (yyloc).last_column = (yylsp[(2) - (2)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0;;} + break; + + case 17: +#line 239 "ast_expr2.y" + { (yyval.val) = op_compl ((yyvsp[(2) - (2)].val)); + DESTROY((yyvsp[(1) - (2)].val)); + (yyloc).first_column = (yylsp[(1) - (2)]).first_column; (yyloc).last_column = (yylsp[(2) - (2)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0;;} + break; + + case 18: +#line 243 "ast_expr2.y" + { (yyval.val) = op_times ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val)); + DESTROY((yyvsp[(2) - (3)].val)); + (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0;;} + break; + + case 19: +#line 247 "ast_expr2.y" + { (yyval.val) = op_div ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val)); + DESTROY((yyvsp[(2) - (3)].val)); + (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0;;} + break; + + case 20: +#line 251 "ast_expr2.y" + { (yyval.val) = op_rem ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val)); + DESTROY((yyvsp[(2) - (3)].val)); + (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0;;} + break; + + case 21: +#line 255 "ast_expr2.y" + { (yyval.val) = op_colon ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val)); + DESTROY((yyvsp[(2) - (3)].val)); + (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0;;} + break; + + case 22: +#line 259 "ast_expr2.y" + { (yyval.val) = op_eqtilde ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val)); + DESTROY((yyvsp[(2) - (3)].val)); + (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0;;} + break; + + case 23: +#line 263 "ast_expr2.y" + { (yyval.val) = op_cond ((yyvsp[(1) - (5)].val), (yyvsp[(3) - (5)].val), (yyvsp[(5) - (5)].val)); + DESTROY((yyvsp[(2) - (5)].val)); + DESTROY((yyvsp[(4) - (5)].val)); + (yyloc).first_column = (yylsp[(1) - (5)]).first_column; (yyloc).last_column = (yylsp[(3) - (5)]).last_column; + (yyloc).first_line=0; (yyloc).last_line=0;;} + break; + + +/* Line 1267 of yacc.c. */ +#line 1875 "ast_expr2.c" + default: break; + } + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + *++yylsp = yyloc; + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (YY_("syntax error")); +#else + { + YYSIZE_T yysize = yysyntax_error (0, yystate, yychar); + if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM) + { + YYSIZE_T yyalloc = 2 * yysize; + if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM)) + yyalloc = YYSTACK_ALLOC_MAXIMUM; + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yyalloc); + if (yymsg) + yymsg_alloc = yyalloc; + else + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + } + } + + if (0 < yysize && yysize <= yymsg_alloc) + { + (void) yysyntax_error (yymsg, yystate, yychar); + yyerror (yymsg); + } + else + { + yyerror (YY_("syntax error")); + if (yysize != 0) + goto yyexhaustedlab; + } + } +#endif + } + + yyerror_range[0] = yylloc; + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse look-ahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval, &yylloc); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse look-ahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + yyerror_range[0] = yylsp[1-yylen]; + /* Do not reclaim the symbols of the rule which action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (yyn != YYPACT_NINF) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + yyerror_range[0] = *yylsp; + yydestruct ("Error: popping", + yystos[yystate], yyvsp, yylsp); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + if (yyn == YYFINAL) + YYACCEPT; + + *++yyvsp = yylval; + + yyerror_range[1] = yylloc; + /* Using YYLLOC is tempting, but would change the location of + the look-ahead. YYLOC is available though. */ + YYLLOC_DEFAULT (yyloc, (yyerror_range - 1), 2); + *++yylsp = yyloc; + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#ifndef yyoverflow +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEOF && yychar != YYEMPTY) + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, &yylloc); + /* Do not reclaim the symbols of the rule which action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp, yylsp); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + /* Make sure YYID is used. */ + return YYID (yyresult); +} + + +#line 270 "ast_expr2.y" + + +static struct val * +make_integer (quad_t i) +{ + struct val *vp; + + vp = (struct val *) malloc (sizeof (*vp)); + if (vp == NULL) { + ast_log(LOG_WARNING, "malloc() failed\n"); + return(NULL); + } + + vp->type = AST_EXPR_integer; + vp->u.i = i; + return vp; +} + +static struct val * +make_str (const char *s) +{ + struct val *vp; + size_t i; + int isint; + + vp = (struct val *) malloc (sizeof (*vp)); + if (vp == NULL || ((vp->u.s = strdup (s)) == NULL)) { + ast_log(LOG_WARNING,"malloc() failed\n"); + return(NULL); + } + + for(i = 1, isint = isdigit(s[0]) || s[0] == '-'; + isint && i < strlen(s); + i++) + { + if(!isdigit(s[i])) + isint = 0; + } + + if (isint) + vp->type = AST_EXPR_numeric_string; + else + vp->type = AST_EXPR_string; + + return vp; +} + + +static void +free_value (struct val *vp) +{ + if (vp==NULL) { + return; + } + if (vp->type == AST_EXPR_string || vp->type == AST_EXPR_numeric_string) + free (vp->u.s); + free(vp); +} + + +static quad_t +to_integer (struct val *vp) +{ + quad_t i; + + if (vp == NULL) { + ast_log(LOG_WARNING,"vp==NULL in to_integer()\n"); + return(0); + } + + if (vp->type == AST_EXPR_integer) + return 1; + + if (vp->type == AST_EXPR_string) + return 0; + + /* vp->type == AST_EXPR_numeric_string, make it numeric */ + errno = 0; + i = strtoll(vp->u.s, (char**)NULL, 10); + if (errno != 0) { + ast_log(LOG_WARNING,"Conversion of %s to integer under/overflowed!\n", vp->u.s); + free(vp->u.s); + vp->u.s = 0; + return(0); + } + free (vp->u.s); + vp->u.i = i; + vp->type = AST_EXPR_integer; + return 1; +} + +static void +strip_quotes(struct val *vp) +{ + if (vp->type != AST_EXPR_string && vp->type != AST_EXPR_numeric_string) + return; + + if( vp->u.s[0] == '"' && vp->u.s[strlen(vp->u.s)-1] == '"' ) + { + char *f, *t; + f = vp->u.s; + t = vp->u.s; + + while( *f ) + { + if( *f && *f != '"' ) + *t++ = *f++; + else + f++; + } + *t = *f; + } +} + +static void +to_string (struct val *vp) +{ + char *tmp; + + if (vp->type == AST_EXPR_string || vp->type == AST_EXPR_numeric_string) + return; + + tmp = malloc ((size_t)25); + if (tmp == NULL) { + ast_log(LOG_WARNING,"malloc() failed\n"); + return; + } + + sprintf(tmp, "%ld", (long int) vp->u.i); + vp->type = AST_EXPR_string; + vp->u.s = tmp; +} + + +static int +isstring (struct val *vp) +{ + /* only TRUE if this string is not a valid integer */ + return (vp->type == AST_EXPR_string); +} + + +static int +is_zero_or_null (struct val *vp) +{ + if (vp->type == AST_EXPR_integer) { + return (vp->u.i == 0); + } else { + return (*vp->u.s == 0 || (to_integer (vp) && vp->u.i == 0)); + } + /* NOTREACHED */ +} + +#ifdef STANDALONE + +void ast_log(int level, const char *file, int line, const char *function, const char *fmt, ...) +{ + va_list vars; + va_start(vars,fmt); + + printf("LOG: lev:%d file:%s line:%d func: %s ", + level, file, line, function); + vprintf(fmt, vars); + fflush(stdout); + va_end(vars); +} + + +int main(int argc,char **argv) { + char s[4096]; + char out[4096]; + FILE *infile; + + if( !argv[1] ) + exit(20); + + if( access(argv[1],F_OK)== 0 ) + { + int ret; + + infile = fopen(argv[1],"r"); + if( !infile ) + { + printf("Sorry, couldn't open %s for reading!\n", argv[1]); + exit(10); + } + while( fgets(s,sizeof(s),infile) ) + { + if( s[strlen(s)-1] == '\n' ) + s[strlen(s)-1] = 0; + + ret = ast_expr(s, out, sizeof(out)); + printf("Expression: %s Result: [%d] '%s'\n", + s, ret, out); + } + fclose(infile); + } + else + { + if (ast_expr(argv[1], s, sizeof(s))) + printf("=====%s======\n",s); + else + printf("No result\n"); + } +} + +#endif + +#undef ast_yyerror +#define ast_yyerror(x) ast_yyerror(x, YYLTYPE *yylloc, struct parse_io *parseio) + +/* I put the ast_yyerror func in the flex input file, + because it refers to the buffer state. Best to + let it access the BUFFER stuff there and not trying + define all the structs, macros etc. in this file! */ + + +static struct val * +op_or (struct val *a, struct val *b) +{ + if (is_zero_or_null (a)) { + free_value (a); + return (b); + } else { + free_value (b); + return (a); + } +} + +static struct val * +op_and (struct val *a, struct val *b) +{ + if (is_zero_or_null (a) || is_zero_or_null (b)) { + free_value (a); + free_value (b); + return (make_integer ((quad_t)0)); + } else { + free_value (b); + return (a); + } +} + +static struct val * +op_eq (struct val *a, struct val *b) +{ + struct val *r; + + if (isstring (a) || isstring (b)) { + to_string (a); + to_string (b); + r = make_integer ((quad_t)(strcoll (a->u.s, b->u.s) == 0)); + } else { +#ifdef DEBUG_FOR_CONVERSIONS + char buffer[2000]; + sprintf(buffer,"Converting '%s' and '%s' ", a->u.s, b->u.s); +#endif + (void)to_integer(a); + (void)to_integer(b); +#ifdef DEBUG_FOR_CONVERSIONS + ast_log(LOG_WARNING,"%s to '%lld' and '%lld'\n", buffer, a->u.i, b->u.i); +#endif + r = make_integer ((quad_t)(a->u.i == b->u.i)); + } + + free_value (a); + free_value (b); + return r; +} + +static struct val * +op_gt (struct val *a, struct val *b) +{ + struct val *r; + + if (isstring (a) || isstring (b)) { + to_string (a); + to_string (b); + r = make_integer ((quad_t)(strcoll (a->u.s, b->u.s) > 0)); + } else { + (void)to_integer(a); + (void)to_integer(b); + r = make_integer ((quad_t)(a->u.i > b->u.i)); + } + + free_value (a); + free_value (b); + return r; +} + +static struct val * +op_lt (struct val *a, struct val *b) +{ + struct val *r; + + if (isstring (a) || isstring (b)) { + to_string (a); + to_string (b); + r = make_integer ((quad_t)(strcoll (a->u.s, b->u.s) < 0)); + } else { + (void)to_integer(a); + (void)to_integer(b); + r = make_integer ((quad_t)(a->u.i < b->u.i)); + } + + free_value (a); + free_value (b); + return r; +} + +static struct val * +op_ge (struct val *a, struct val *b) +{ + struct val *r; + + if (isstring (a) || isstring (b)) { + to_string (a); + to_string (b); + r = make_integer ((quad_t)(strcoll (a->u.s, b->u.s) >= 0)); + } else { + (void)to_integer(a); + (void)to_integer(b); + r = make_integer ((quad_t)(a->u.i >= b->u.i)); + } + + free_value (a); + free_value (b); + return r; +} + +static struct val * +op_le (struct val *a, struct val *b) +{ + struct val *r; + + if (isstring (a) || isstring (b)) { + to_string (a); + to_string (b); + r = make_integer ((quad_t)(strcoll (a->u.s, b->u.s) <= 0)); + } else { + (void)to_integer(a); + (void)to_integer(b); + r = make_integer ((quad_t)(a->u.i <= b->u.i)); + } + + free_value (a); + free_value (b); + return r; +} + +static struct val * +op_cond (struct val *a, struct val *b, struct val *c) +{ + struct val *r; + + if( isstring(a) ) + { + if( strlen(a->u.s) && strcmp(a->u.s, "\"\"") != 0 && strcmp(a->u.s,"0") != 0 ) + { + free_value(a); + free_value(c); + r = b; + } + else + { + free_value(a); + free_value(b); + r = c; + } + } + else + { + (void)to_integer(a); + if( a->u.i ) + { + free_value(a); + free_value(c); + r = b; + } + else + { + free_value(a); + free_value(b); + r = c; + } + } + return r; +} + +static struct val * +op_ne (struct val *a, struct val *b) +{ + struct val *r; + + if (isstring (a) || isstring (b)) { + to_string (a); + to_string (b); + r = make_integer ((quad_t)(strcoll (a->u.s, b->u.s) != 0)); + } else { + (void)to_integer(a); + (void)to_integer(b); + r = make_integer ((quad_t)(a->u.i != b->u.i)); + } + + free_value (a); + free_value (b); + return r; +} + +static int +chk_plus (quad_t a, quad_t b, quad_t r) +{ + /* sum of two positive numbers must be positive */ + if (a > 0 && b > 0 && r <= 0) + return 1; + /* sum of two negative numbers must be negative */ + if (a < 0 && b < 0 && r >= 0) + return 1; + /* all other cases are OK */ + return 0; +} + +static struct val * +op_plus (struct val *a, struct val *b) +{ + struct val *r; + + if (!to_integer (a)) { + if( !extra_error_message_supplied ) + ast_log(LOG_WARNING,"non-numeric argument\n"); + if (!to_integer (b)) { + free_value(a); + free_value(b); + return make_integer(0); + } else { + free_value(a); + return (b); + } + } else if (!to_integer(b)) { + free_value(b); + return (a); + } + + r = make_integer (/*(quad_t)*/(a->u.i + b->u.i)); + if (chk_plus (a->u.i, b->u.i, r->u.i)) { + ast_log(LOG_WARNING,"overflow\n"); + } + free_value (a); + free_value (b); + return r; +} + +static int +chk_minus (quad_t a, quad_t b, quad_t r) +{ + /* special case subtraction of QUAD_MIN */ + if (b == QUAD_MIN) { + if (a >= 0) + return 1; + else + return 0; + } + /* this is allowed for b != QUAD_MIN */ + return chk_plus (a, -b, r); +} + +static struct val * +op_minus (struct val *a, struct val *b) +{ + struct val *r; + + if (!to_integer (a)) { + if( !extra_error_message_supplied ) + ast_log(LOG_WARNING, "non-numeric argument\n"); + if (!to_integer (b)) { + free_value(a); + free_value(b); + return make_integer(0); + } else { + r = make_integer(0 - b->u.i); + free_value(a); + free_value(b); + return (r); + } + } else if (!to_integer(b)) { + if( !extra_error_message_supplied ) + ast_log(LOG_WARNING, "non-numeric argument\n"); + free_value(b); + return (a); + } + + r = make_integer (/*(quad_t)*/(a->u.i - b->u.i)); + if (chk_minus (a->u.i, b->u.i, r->u.i)) { + ast_log(LOG_WARNING, "overflow\n"); + } + free_value (a); + free_value (b); + return r; +} + +static struct val * +op_negate (struct val *a) +{ + struct val *r; + + if (!to_integer (a) ) { + free_value(a); + if( !extra_error_message_supplied ) + ast_log(LOG_WARNING, "non-numeric argument\n"); + return make_integer(0); + } + + r = make_integer (/*(quad_t)*/(- a->u.i)); + if (chk_minus (0, a->u.i, r->u.i)) { + ast_log(LOG_WARNING, "overflow\n"); + } + free_value (a); + return r; +} + +static struct val * +op_compl (struct val *a) +{ + int v1 = 1; + struct val *r; + + if( !a ) + { + v1 = 0; + } + else + { + switch( a->type ) + { + case AST_EXPR_integer: + if( a->u.i == 0 ) + v1 = 0; + break; + + case AST_EXPR_string: + if( a->u.s == 0 ) + v1 = 0; + else + { + if( a->u.s[0] == 0 ) + v1 = 0; + else if (strlen(a->u.s) == 1 && a->u.s[0] == '0' ) + v1 = 0; + } + break; + + case AST_EXPR_numeric_string: + if( a->u.s == 0 ) + v1 = 0; + else + { + if( a->u.s[0] == 0 ) + v1 = 0; + else if (strlen(a->u.s) == 1 && a->u.s[0] == '0' ) + v1 = 0; + } + break; + } + } + + r = make_integer (!v1); + free_value (a); + return r; +} + +static int +chk_times (quad_t a, quad_t b, quad_t r) +{ + /* special case: first operand is 0, no overflow possible */ + if (a == 0) + return 0; + /* cerify that result of division matches second operand */ + if (r / a != b) + return 1; + return 0; +} + +static struct val * +op_times (struct val *a, struct val *b) +{ + struct val *r; + + if (!to_integer (a) || !to_integer (b)) { + free_value(a); + free_value(b); + if( !extra_error_message_supplied ) + ast_log(LOG_WARNING, "non-numeric argument\n"); + return(make_integer(0)); + } + + r = make_integer (/*(quad_t)*/(a->u.i * b->u.i)); + if (chk_times (a->u.i, b->u.i, r->u.i)) { + ast_log(LOG_WARNING, "overflow\n"); + } + free_value (a); + free_value (b); + return (r); +} + +static int +chk_div (quad_t a, quad_t b) +{ + /* div by zero has been taken care of before */ + /* only QUAD_MIN / -1 causes overflow */ + if (a == QUAD_MIN && b == -1) + return 1; + /* everything else is OK */ + return 0; +} + +static struct val * +op_div (struct val *a, struct val *b) +{ + struct val *r; + + if (!to_integer (a)) { + free_value(a); + free_value(b); + if( !extra_error_message_supplied ) + ast_log(LOG_WARNING, "non-numeric argument\n"); + return make_integer(0); + } else if (!to_integer (b)) { + free_value(a); + free_value(b); + if( !extra_error_message_supplied ) + ast_log(LOG_WARNING, "non-numeric argument\n"); + return make_integer(INT_MAX); + } + + if (b->u.i == 0) { + ast_log(LOG_WARNING, "division by zero\n"); + free_value(a); + free_value(b); + return make_integer(INT_MAX); + } + + r = make_integer (/*(quad_t)*/(a->u.i / b->u.i)); + if (chk_div (a->u.i, b->u.i)) { + ast_log(LOG_WARNING, "overflow\n"); + } + free_value (a); + free_value (b); + return r; +} + +static struct val * +op_rem (struct val *a, struct val *b) +{ + struct val *r; + + if (!to_integer (a) || !to_integer (b)) { + if( !extra_error_message_supplied ) + ast_log(LOG_WARNING, "non-numeric argument\n"); + free_value(a); + free_value(b); + return make_integer(0); + } + + if (b->u.i == 0) { + ast_log(LOG_WARNING, "div by zero\n"); + free_value(a); + return(b); + } + + r = make_integer (/*(quad_t)*/(a->u.i % b->u.i)); + /* chk_rem necessary ??? */ + free_value (a); + free_value (b); + return r; +} + + +static struct val * +op_colon (struct val *a, struct val *b) +{ + regex_t rp; + regmatch_t rm[2]; + char errbuf[256]; + int eval; + struct val *v; + + /* coerce to both arguments to strings */ + to_string(a); + to_string(b); + /* strip double quotes from both -- they'll screw up the pattern, and the search string starting at ^ */ + strip_quotes(a); + strip_quotes(b); + /* compile regular expression */ + if ((eval = regcomp (&rp, b->u.s, REG_EXTENDED)) != 0) { + regerror (eval, &rp, errbuf, sizeof(errbuf)); + ast_log(LOG_WARNING,"regcomp() error : %s",errbuf); + free_value(a); + free_value(b); + return make_str(""); + } + + /* compare string against pattern */ + /* remember that patterns are anchored to the beginning of the line */ + if (regexec(&rp, a->u.s, (size_t)2, rm, 0) == 0 && rm[0].rm_so == 0) { + if (rm[1].rm_so >= 0) { + *(a->u.s + rm[1].rm_eo) = '\0'; + v = make_str (a->u.s + rm[1].rm_so); + + } else { + v = make_integer ((quad_t)(rm[0].rm_eo - rm[0].rm_so)); + } + } else { + if (rp.re_nsub == 0) { + v = make_integer ((quad_t)0); + } else { + v = make_str (""); + } + } + + /* free arguments and pattern buffer */ + free_value (a); + free_value (b); + regfree (&rp); + + return v; +} + + +static struct val * +op_eqtilde (struct val *a, struct val *b) +{ + regex_t rp; + regmatch_t rm[2]; + char errbuf[256]; + int eval; + struct val *v; + + /* coerce to both arguments to strings */ + to_string(a); + to_string(b); + /* strip double quotes from both -- they'll screw up the pattern, and the search string starting at ^ */ + strip_quotes(a); + strip_quotes(b); + /* compile regular expression */ + if ((eval = regcomp (&rp, b->u.s, REG_EXTENDED)) != 0) { + regerror (eval, &rp, errbuf, sizeof(errbuf)); + ast_log(LOG_WARNING,"regcomp() error : %s",errbuf); + free_value(a); + free_value(b); + return make_str(""); + } + + /* compare string against pattern */ + /* remember that patterns are anchored to the beginning of the line */ + if (regexec(&rp, a->u.s, (size_t)2, rm, 0) == 0 ) { + if (rm[1].rm_so >= 0) { + *(a->u.s + rm[1].rm_eo) = '\0'; + v = make_str (a->u.s + rm[1].rm_so); + + } else { + v = make_integer ((quad_t)(rm[0].rm_eo - rm[0].rm_so)); + } + } else { + if (rp.re_nsub == 0) { + v = make_integer ((quad_t)0); + } else { + v = make_str (""); + } + } + + /* free arguments and pattern buffer */ + free_value (a); + free_value (b); + regfree (&rp); + + return v; +} + diff --git a/main/ast_expr2.fl b/main/ast_expr2.fl new file mode 100644 index 000000000..df9668fe8 --- /dev/null +++ b/main/ast_expr2.fl @@ -0,0 +1,405 @@ +%{ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Dialplan Expression Lexical Scanner + */ + +#include "asterisk.h" + +#ifndef STANDALONE +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") +#endif + +#include +#include +#include +#include +#include +#include +#if !defined(SOLARIS) && !defined(__CYGWIN__) +#include +#else +#define quad_t int64_t +#endif +#include +#include +#include + +#include "asterisk/ast_expr.h" +#include "asterisk/logger.h" +#include "asterisk/strings.h" + +enum valtype { + AST_EXPR_integer, AST_EXPR_numeric_string, AST_EXPR_string +} ; + +struct val { + enum valtype type; + union { + char *s; + quad_t i; + } u; +} ; + +#include "ast_expr2.h" /* the o/p of the bison on ast_expr2.y */ + +#define SET_COLUMNS do { \ + yylloc_param->first_column = (int)(yyg->yytext_r - YY_CURRENT_BUFFER_LVALUE->yy_ch_buf); \ + yylloc_param->last_column += yyleng - 1; \ + yylloc_param->first_line = yylloc_param->last_line = 1; \ + } while (0) + +#define SET_STRING do { \ + yylval_param->val = calloc(1, sizeof(struct val)); \ + yylval_param->val->type = AST_EXPR_string; \ + yylval_param->val->u.s = strdup(yytext); \ + } while (0) + +#define SET_NUMERIC_STRING do { \ + yylval_param->val = calloc(1, sizeof(struct val)); \ + yylval_param->val->type = AST_EXPR_numeric_string; \ + yylval_param->val->u.s = strdup(yytext); \ + } while (0) + +struct parse_io +{ + char *string; + struct val *val; + yyscan_t scanner; +}; + +void ast_yyset_column(int column_no, yyscan_t yyscanner); +int ast_yyget_column(yyscan_t yyscanner); +static int curlycount = 0; +static char *expr2_token_subst(char *mess); +%} + +%option prefix="ast_yy" +%option batch +%option outfile="ast_expr2f.c" +%option reentrant +%option bison-bridge +%option bison-locations +%option noyywrap +%x var trail + +%% + +\| { SET_COLUMNS; SET_STRING; return TOK_OR;} +\& { SET_COLUMNS; SET_STRING; return TOK_AND;} +\= { SET_COLUMNS; SET_STRING; return TOK_EQ;} +\|\| { SET_COLUMNS; SET_STRING; return TOK_OR;} +\&\& { SET_COLUMNS; SET_STRING; return TOK_AND;} +\=\= { SET_COLUMNS; SET_STRING; return TOK_EQ;} +\=~ { SET_COLUMNS; SET_STRING; return TOK_EQTILDE;} +\> { SET_COLUMNS; SET_STRING; return TOK_GT;} +\< { SET_COLUMNS; SET_STRING; return TOK_LT;} +\>\= { SET_COLUMNS; SET_STRING; return TOK_GE;} +\<\= { SET_COLUMNS; SET_STRING; return TOK_LE;} +\!\= { SET_COLUMNS; SET_STRING; return TOK_NE;} +\+ { SET_COLUMNS; SET_STRING; return TOK_PLUS;} +\- { SET_COLUMNS; SET_STRING; return TOK_MINUS;} +\* { SET_COLUMNS; SET_STRING; return TOK_MULT;} +\/ { SET_COLUMNS; SET_STRING; return TOK_DIV;} +\% { SET_COLUMNS; SET_STRING; return TOK_MOD;} +\? { SET_COLUMNS; SET_STRING; return TOK_COND;} +\! { SET_COLUMNS; SET_STRING; return TOK_COMPL;} +\: { SET_COLUMNS; SET_STRING; return TOK_COLON;} +\:\: { SET_COLUMNS; SET_STRING; return TOK_COLONCOLON;} +\( { SET_COLUMNS; SET_STRING; return TOK_LP;} +\) { SET_COLUMNS; SET_STRING; return TOK_RP;} +\$\{ { + /* gather the contents of ${} expressions, with trailing stuff, + * into a single TOKEN. + * They are much more complex now than they used to be + */ + curlycount = 0; + BEGIN(var); + yymore(); + } + +[ \t\r] {} +\"[^"]*\" {SET_COLUMNS; SET_STRING; return TOKEN;} + +[\n] {/* what to do with eol */} +[0-9]+ { + SET_COLUMNS; + /* the original behavior of the expression parser was + * to bring in numbers as a numeric string + */ + SET_NUMERIC_STRING; + return TOKEN; + } + +[a-zA-Z0-9,.';\\_^$#@]+ { + SET_COLUMNS; + SET_STRING; + return TOKEN; + } + + +[^{}]*\} { + curlycount--; + if (curlycount < 0) { + BEGIN(trail); + yymore(); + } else { + yymore(); + } + } + +[^{}]*\{ { + curlycount++; + yymore(); + } + + +[^-\t\r \n$():?%/+=*<>!|&]* { + BEGIN(0); + SET_COLUMNS; + SET_STRING; + return TOKEN; + } + +[-\t\r \n$():?%/+=*<>!|&] { + char c = yytext[yyleng-1]; + BEGIN(0); + unput(c); + SET_COLUMNS; + SET_STRING; + return TOKEN; + } + +\$\{ { + curlycount = 0; + BEGIN(var); + yymore(); + } + +<> { + BEGIN(0); + SET_COLUMNS; + SET_STRING; + return TOKEN; + /*actually, if an expr is only a variable ref, this could happen a LOT */ + } + +%% + +/* I'm putting the interface routine to the whole parse here in the flexer input file + mainly because of all the flexer initialization that has to be done. Shouldn't matter + where it is, as long as it's somewhere. I didn't want to define a prototype for the + ast_yy_scan_string in the .y file, because then, I'd have to define YY_BUFFER_STATE there... + UGH! that would be inappropriate. */ + +int ast_yyparse(void *); /* need to/should define this prototype for the call to yyparse */ +int ast_yyerror(const char *, YYLTYPE *, struct parse_io *); /* likewise */ + +int ast_expr(char *expr, char *buf, int length) +{ + struct parse_io io; + int return_value = 0; + + memset(&io, 0, sizeof(io)); + io.string = expr; /* to pass to the error routine */ + + ast_yylex_init(&io.scanner); + + ast_yy_scan_string(expr, io.scanner); + + ast_yyparse ((void *) &io); + + ast_yylex_destroy(io.scanner); + + if (!io.val) { + if (length > 1) { + strcpy(buf, "0"); + return_value = 1; + } + } else { + if (io.val->type == AST_EXPR_integer) { + int res_length; + + res_length = snprintf(buf, length, "%ld", (long int) io.val->u.i); + return_value = (res_length <= length) ? res_length : length; + } else { +#ifdef STANDALONE + strncpy(buf, io.val->u.s, length - 1); +#else /* !STANDALONE */ + ast_copy_string(buf, io.val->u.s, length); +#endif /* STANDALONE */ + return_value = strlen(buf); + free(io.val->u.s); + } + free(io.val); + } + return return_value; +} + + +char extra_error_message[4095]; +int extra_error_message_supplied = 0; +void ast_expr_register_extra_error_info(char *message); +void ast_expr_clear_extra_error_info(void); + +void ast_expr_register_extra_error_info(char *message) +{ + extra_error_message_supplied=1; + strcpy(extra_error_message, message); +} + +void ast_expr_clear_extra_error_info(void) +{ + extra_error_message_supplied=0; + extra_error_message[0] = 0; +} + +static char *expr2_token_equivs1[] = +{ + "TOKEN", + "TOK_COND", + "TOK_COLONCOLON", + "TOK_OR", + "TOK_AND", + "TOK_EQ", + "TOK_GT", + "TOK_LT", + "TOK_GE", + "TOK_LE", + "TOK_NE", + "TOK_PLUS", + "TOK_MINUS", + "TOK_MULT", + "TOK_DIV", + "TOK_MOD", + "TOK_COMPL", + "TOK_COLON", + "TOK_EQTILDE", + "TOK_RP", + "TOK_LP" +}; + +static char *expr2_token_equivs2[] = +{ + "", + "?", + "::", + "|", + "&", + "=", + ">", + "<", + ">=", + "<=", + "!=", + "+", + "-", + "*", + "/", + "%", + "!", + ":", + "=~", + ")", + "(" +}; + + +static char *expr2_token_subst(char *mess) +{ + /* calc a length, malloc, fill, and return; yyerror had better free it! */ + int len=0,i; + char *p; + char *res, *s,*t; + int expr2_token_equivs_entries = sizeof(expr2_token_equivs1)/sizeof(char*); + + for (p=mess; *p; p++) { + for (i=0; iscanner); + char spacebuf[8000]; /* best safe than sorry */ + char spacebuf2[8000]; /* best safe than sorry */ + int i=0; + char *s2 = expr2_token_subst((char *)s); + spacebuf[0] = 0; + + for(i=0;i< (int)(yytext - YY_CURRENT_BUFFER_LVALUE->yy_ch_buf);i++) spacebuf2[i] = ' '; /* uh... assuming yyg is defined, then I can use the yycolumn macro, + which is the same thing as... get this: + yyg->yy_buffer_stack[yyg->yy_buffer_stack_top]->yy_bs_column + I was tempted to just use yy_buf_pos in the STATE, but..., well: + a. the yy_buf_pos is the current position in the buffer, which + may not relate to the entire string/buffer because of the + buffering. + b. but, analysis of the situation is that when you use the + yy_scan_string func, it creates a single buffer the size of + string, so the two would be the same... + so, in the end, the yycolumn macro is available, shorter, therefore easier. */ + spacebuf2[i++]='^'; + spacebuf2[i]= 0; + +#ifdef STANDALONE3 + /* easier to read in the standalone version */ + printf("ast_yyerror(): %s syntax error: %s; Input:\n%s\n%s\n", + (extra_error_message_supplied?extra_error_message:""), s2, parseio->string,spacebuf2); +#else + ast_log(LOG_WARNING,"ast_yyerror(): %s syntax error: %s; Input:\n%s\n%s\n", + (extra_error_message_supplied?extra_error_message:""), s2, parseio->string,spacebuf2); +#endif +#ifndef STANDALONE + ast_log(LOG_WARNING,"If you have questions, please refer to doc/channelvariables.txt in the asterisk source.\n"); +#endif + free(s2); + return(0); +} diff --git a/main/ast_expr2.h b/main/ast_expr2.h new file mode 100644 index 000000000..5b3fc5c3b --- /dev/null +++ b/main/ast_expr2.h @@ -0,0 +1,120 @@ +/* A Bison parser, made by GNU Bison 2.3. */ + +/* Skeleton interface for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + TOK_COLONCOLON = 258, + TOK_COND = 259, + TOK_OR = 260, + TOK_AND = 261, + TOK_NE = 262, + TOK_LE = 263, + TOK_GE = 264, + TOK_LT = 265, + TOK_GT = 266, + TOK_EQ = 267, + TOK_MINUS = 268, + TOK_PLUS = 269, + TOK_MOD = 270, + TOK_DIV = 271, + TOK_MULT = 272, + TOK_COMPL = 273, + TOK_EQTILDE = 274, + TOK_COLON = 275, + TOK_LP = 276, + TOK_RP = 277, + TOKEN = 278 + }; +#endif +/* Tokens. */ +#define TOK_COLONCOLON 258 +#define TOK_COND 259 +#define TOK_OR 260 +#define TOK_AND 261 +#define TOK_NE 262 +#define TOK_LE 263 +#define TOK_GE 264 +#define TOK_LT 265 +#define TOK_GT 266 +#define TOK_EQ 267 +#define TOK_MINUS 268 +#define TOK_PLUS 269 +#define TOK_MOD 270 +#define TOK_DIV 271 +#define TOK_MULT 272 +#define TOK_COMPL 273 +#define TOK_EQTILDE 274 +#define TOK_COLON 275 +#define TOK_LP 276 +#define TOK_RP 277 +#define TOKEN 278 + + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE +#line 147 "ast_expr2.y" +{ + struct val *val; +} +/* Line 1529 of yacc.c. */ +#line 99 "ast_expr2.h" + YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + + + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +# define yyltype YYLTYPE /* obsolescent; will be withdrawn */ +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + diff --git a/main/ast_expr2.y b/main/ast_expr2.y new file mode 100644 index 000000000..85c34b2c9 --- /dev/null +++ b/main/ast_expr2.y @@ -0,0 +1,1045 @@ +%{ +/* Written by Pace Willisson (pace@blitz.com) + * and placed in the public domain. + * + * Largely rewritten by J.T. Conklin (jtc@wimsey.com) + * + * And then overhauled twice by Steve Murphy (murf@e-tools.com) + * to add double-quoted strings, allow mult. spaces, improve + * error messages, and then to fold in a flex scanner for the + * yylex operation. + * + * $FreeBSD: src/bin/expr/expr.y,v 1.16 2000/07/22 10:59:36 se Exp $ + */ + +#include "asterisk.h" + +#ifndef STANDALONE +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") +#endif + +#include +#include +#include +#include +#include +#include +#include +#if !defined(SOLARIS) && !defined(__CYGWIN__) +#include +#else +#define quad_t int64_t +#endif +#include +#include +#include + +#include "asterisk/ast_expr.h" +#include "asterisk/logger.h" + +#if defined(LONG_LONG_MIN) && !defined(QUAD_MIN) +#define QUAD_MIN LONG_LONG_MIN +#endif +#if defined(LONG_LONG_MAX) && !defined(QUAD_MAX) +#define QUAD_MAX LONG_LONG_MAX +#endif + +# if ! defined(QUAD_MIN) +# define QUAD_MIN (-0x7fffffffffffffffLL-1) +# endif +# if ! defined(QUAD_MAX) +# define QUAD_MAX (0x7fffffffffffffffLL) +# endif + +#define YYPARSE_PARAM parseio +#define YYLEX_PARAM ((struct parse_io *)parseio)->scanner +#define YYERROR_VERBOSE 1 +extern char extra_error_message[4095]; +extern int extra_error_message_supplied; + +enum valtype { + AST_EXPR_integer, AST_EXPR_numeric_string, AST_EXPR_string +} ; + +#ifdef STANDALONE +void ast_log(int level, const char *file, int line, const char *function, const char *fmt, ...) __attribute__ ((format (printf,5,6))); +#endif + +struct val { + enum valtype type; + union { + char *s; + quad_t i; + } u; +} ; + +typedef void *yyscan_t; + +struct parse_io +{ + char *string; + struct val *val; + yyscan_t scanner; +}; + +static int chk_div __P((quad_t, quad_t)); +static int chk_minus __P((quad_t, quad_t, quad_t)); +static int chk_plus __P((quad_t, quad_t, quad_t)); +static int chk_times __P((quad_t, quad_t, quad_t)); +static void free_value __P((struct val *)); +static int is_zero_or_null __P((struct val *)); +static int isstring __P((struct val *)); +static struct val *make_integer __P((quad_t)); +static struct val *make_str __P((const char *)); +static struct val *op_and __P((struct val *, struct val *)); +static struct val *op_colon __P((struct val *, struct val *)); +static struct val *op_eqtilde __P((struct val *, struct val *)); +static struct val *op_div __P((struct val *, struct val *)); +static struct val *op_eq __P((struct val *, struct val *)); +static struct val *op_ge __P((struct val *, struct val *)); +static struct val *op_gt __P((struct val *, struct val *)); +static struct val *op_le __P((struct val *, struct val *)); +static struct val *op_lt __P((struct val *, struct val *)); +static struct val *op_cond __P((struct val *, struct val *, struct val *)); +static struct val *op_minus __P((struct val *, struct val *)); +static struct val *op_negate __P((struct val *)); +static struct val *op_compl __P((struct val *)); +static struct val *op_ne __P((struct val *, struct val *)); +static struct val *op_or __P((struct val *, struct val *)); +static struct val *op_plus __P((struct val *, struct val *)); +static struct val *op_rem __P((struct val *, struct val *)); +static struct val *op_times __P((struct val *, struct val *)); +static quad_t to_integer __P((struct val *)); +static void to_string __P((struct val *)); + +/* uh, if I want to predeclare yylex with a YYLTYPE, I have to predeclare the yyltype... sigh */ +typedef struct yyltype +{ + int first_line; + int first_column; + + int last_line; + int last_column; +} yyltype; + +# define YYLTYPE yyltype +# define YYLTYPE_IS_TRIVIAL 1 + +/* we will get warning about no prototype for yylex! But we can't + define it here, we have no definition yet for YYSTYPE. */ + +int ast_yyerror(const char *,YYLTYPE *, struct parse_io *); + +/* I wanted to add args to the yyerror routine, so I could print out + some useful info about the error. Not as easy as it looks, but it + is possible. */ +#define ast_yyerror(x) ast_yyerror(x,&yyloc,parseio) +#define DESTROY(x) {if((x)->type == AST_EXPR_numeric_string || (x)->type == AST_EXPR_string) free((x)->u.s); (x)->u.s = 0; free(x);} +%} + +%pure-parser +%locations +/* %debug for when you are having big problems */ + +/* %name-prefix="ast_yy" */ + +%union +{ + struct val *val; +} + +%{ +extern int ast_yylex __P((YYSTYPE *, YYLTYPE *, yyscan_t)); +%} +%left TOK_COND TOK_COLONCOLON +%left TOK_OR +%left TOK_AND +%left TOK_EQ TOK_GT TOK_LT TOK_GE TOK_LE TOK_NE +%left TOK_PLUS TOK_MINUS +%left TOK_MULT TOK_DIV TOK_MOD +%right TOK_COMPL +%left TOK_COLON TOK_EQTILDE +%left TOK_RP TOK_LP + + +%token TOKEN +%type start expr + + +%destructor { free_value($$); } expr TOKEN TOK_COND TOK_COLONCOLON TOK_OR TOK_AND TOK_EQ + TOK_GT TOK_LT TOK_GE TOK_LE TOK_NE TOK_PLUS TOK_MINUS TOK_MULT TOK_DIV TOK_MOD TOK_COMPL TOK_COLON TOK_EQTILDE + TOK_RP TOK_LP + +%% + +start: expr { ((struct parse_io *)parseio)->val = (struct val *)calloc(sizeof(struct val),1); + ((struct parse_io *)parseio)->val->type = $1->type; + if( $1->type == AST_EXPR_integer ) + ((struct parse_io *)parseio)->val->u.i = $1->u.i; + else + ((struct parse_io *)parseio)->val->u.s = $1->u.s; + free($1); + } + | {/* nothing */ ((struct parse_io *)parseio)->val = (struct val *)calloc(sizeof(struct val),1); + ((struct parse_io *)parseio)->val->type = AST_EXPR_string; + ((struct parse_io *)parseio)->val->u.s = strdup(""); + } + + ; + +expr: TOKEN { $$= $1;} + | TOK_LP expr TOK_RP { $$ = $2; + @$.first_column = @1.first_column; @$.last_column = @3.last_column; + @$.first_line=0; @$.last_line=0; + DESTROY($1); DESTROY($3); } + | expr TOK_OR expr { $$ = op_or ($1, $3); + DESTROY($2); + @$.first_column = @1.first_column; @$.last_column = @3.last_column; + @$.first_line=0; @$.last_line=0;} + | expr TOK_AND expr { $$ = op_and ($1, $3); + DESTROY($2); + @$.first_column = @1.first_column; @$.last_column = @3.last_column; + @$.first_line=0; @$.last_line=0;} + | expr TOK_EQ expr { $$ = op_eq ($1, $3); + DESTROY($2); + @$.first_column = @1.first_column; @$.last_column = @3.last_column; + @$.first_line=0; @$.last_line=0;} + | expr TOK_GT expr { $$ = op_gt ($1, $3); + DESTROY($2); + @$.first_column = @1.first_column; @$.last_column = @3.last_column; + @$.first_line=0; @$.last_line=0;} + | expr TOK_LT expr { $$ = op_lt ($1, $3); + DESTROY($2); + @$.first_column = @1.first_column; @$.last_column = @3.last_column; + @$.first_line=0; @$.last_line=0;} + | expr TOK_GE expr { $$ = op_ge ($1, $3); + DESTROY($2); + @$.first_column = @1.first_column; @$.last_column = @3.last_column; + @$.first_line=0; @$.last_line=0;} + | expr TOK_LE expr { $$ = op_le ($1, $3); + DESTROY($2); + @$.first_column = @1.first_column; @$.last_column = @3.last_column; + @$.first_line=0; @$.last_line=0;} + | expr TOK_NE expr { $$ = op_ne ($1, $3); + DESTROY($2); + @$.first_column = @1.first_column; @$.last_column = @3.last_column; + @$.first_line=0; @$.last_line=0;} + | expr TOK_PLUS expr { $$ = op_plus ($1, $3); + DESTROY($2); + @$.first_column = @1.first_column; @$.last_column = @3.last_column; + @$.first_line=0; @$.last_line=0;} + | expr TOK_MINUS expr { $$ = op_minus ($1, $3); + DESTROY($2); + @$.first_column = @1.first_column; @$.last_column = @3.last_column; + @$.first_line=0; @$.last_line=0;} + | TOK_MINUS expr %prec TOK_COMPL { $$ = op_negate ($2); + DESTROY($1); + @$.first_column = @1.first_column; @$.last_column = @2.last_column; + @$.first_line=0; @$.last_line=0;} + | TOK_COMPL expr { $$ = op_compl ($2); + DESTROY($1); + @$.first_column = @1.first_column; @$.last_column = @2.last_column; + @$.first_line=0; @$.last_line=0;} + | expr TOK_MULT expr { $$ = op_times ($1, $3); + DESTROY($2); + @$.first_column = @1.first_column; @$.last_column = @3.last_column; + @$.first_line=0; @$.last_line=0;} + | expr TOK_DIV expr { $$ = op_div ($1, $3); + DESTROY($2); + @$.first_column = @1.first_column; @$.last_column = @3.last_column; + @$.first_line=0; @$.last_line=0;} + | expr TOK_MOD expr { $$ = op_rem ($1, $3); + DESTROY($2); + @$.first_column = @1.first_column; @$.last_column = @3.last_column; + @$.first_line=0; @$.last_line=0;} + | expr TOK_COLON expr { $$ = op_colon ($1, $3); + DESTROY($2); + @$.first_column = @1.first_column; @$.last_column = @3.last_column; + @$.first_line=0; @$.last_line=0;} + | expr TOK_EQTILDE expr { $$ = op_eqtilde ($1, $3); + DESTROY($2); + @$.first_column = @1.first_column; @$.last_column = @3.last_column; + @$.first_line=0; @$.last_line=0;} + | expr TOK_COND expr TOK_COLONCOLON expr { $$ = op_cond ($1, $3, $5); + DESTROY($2); + DESTROY($4); + @$.first_column = @1.first_column; @$.last_column = @3.last_column; + @$.first_line=0; @$.last_line=0;} + ; + +%% + +static struct val * +make_integer (quad_t i) +{ + struct val *vp; + + vp = (struct val *) malloc (sizeof (*vp)); + if (vp == NULL) { + ast_log(LOG_WARNING, "malloc() failed\n"); + return(NULL); + } + + vp->type = AST_EXPR_integer; + vp->u.i = i; + return vp; +} + +static struct val * +make_str (const char *s) +{ + struct val *vp; + size_t i; + int isint; + + vp = (struct val *) malloc (sizeof (*vp)); + if (vp == NULL || ((vp->u.s = strdup (s)) == NULL)) { + ast_log(LOG_WARNING,"malloc() failed\n"); + return(NULL); + } + + for(i = 1, isint = isdigit(s[0]) || s[0] == '-'; + isint && i < strlen(s); + i++) + { + if(!isdigit(s[i])) + isint = 0; + } + + if (isint) + vp->type = AST_EXPR_numeric_string; + else + vp->type = AST_EXPR_string; + + return vp; +} + + +static void +free_value (struct val *vp) +{ + if (vp==NULL) { + return; + } + if (vp->type == AST_EXPR_string || vp->type == AST_EXPR_numeric_string) + free (vp->u.s); + free(vp); +} + + +static quad_t +to_integer (struct val *vp) +{ + quad_t i; + + if (vp == NULL) { + ast_log(LOG_WARNING,"vp==NULL in to_integer()\n"); + return(0); + } + + if (vp->type == AST_EXPR_integer) + return 1; + + if (vp->type == AST_EXPR_string) + return 0; + + /* vp->type == AST_EXPR_numeric_string, make it numeric */ + errno = 0; + i = strtoll(vp->u.s, (char**)NULL, 10); + if (errno != 0) { + ast_log(LOG_WARNING,"Conversion of %s to integer under/overflowed!\n", vp->u.s); + free(vp->u.s); + vp->u.s = 0; + return(0); + } + free (vp->u.s); + vp->u.i = i; + vp->type = AST_EXPR_integer; + return 1; +} + +static void +strip_quotes(struct val *vp) +{ + if (vp->type != AST_EXPR_string && vp->type != AST_EXPR_numeric_string) + return; + + if( vp->u.s[0] == '"' && vp->u.s[strlen(vp->u.s)-1] == '"' ) + { + char *f, *t; + f = vp->u.s; + t = vp->u.s; + + while( *f ) + { + if( *f && *f != '"' ) + *t++ = *f++; + else + f++; + } + *t = *f; + } +} + +static void +to_string (struct val *vp) +{ + char *tmp; + + if (vp->type == AST_EXPR_string || vp->type == AST_EXPR_numeric_string) + return; + + tmp = malloc ((size_t)25); + if (tmp == NULL) { + ast_log(LOG_WARNING,"malloc() failed\n"); + return; + } + + sprintf(tmp, "%ld", (long int) vp->u.i); + vp->type = AST_EXPR_string; + vp->u.s = tmp; +} + + +static int +isstring (struct val *vp) +{ + /* only TRUE if this string is not a valid integer */ + return (vp->type == AST_EXPR_string); +} + + +static int +is_zero_or_null (struct val *vp) +{ + if (vp->type == AST_EXPR_integer) { + return (vp->u.i == 0); + } else { + return (*vp->u.s == 0 || (to_integer (vp) && vp->u.i == 0)); + } + /* NOTREACHED */ +} + +#ifdef STANDALONE + +void ast_log(int level, const char *file, int line, const char *function, const char *fmt, ...) +{ + va_list vars; + va_start(vars,fmt); + + printf("LOG: lev:%d file:%s line:%d func: %s ", + level, file, line, function); + vprintf(fmt, vars); + fflush(stdout); + va_end(vars); +} + + +int main(int argc,char **argv) { + char s[4096]; + char out[4096]; + FILE *infile; + + if( !argv[1] ) + exit(20); + + if( access(argv[1],F_OK)== 0 ) + { + int ret; + + infile = fopen(argv[1],"r"); + if( !infile ) + { + printf("Sorry, couldn't open %s for reading!\n", argv[1]); + exit(10); + } + while( fgets(s,sizeof(s),infile) ) + { + if( s[strlen(s)-1] == '\n' ) + s[strlen(s)-1] = 0; + + ret = ast_expr(s, out, sizeof(out)); + printf("Expression: %s Result: [%d] '%s'\n", + s, ret, out); + } + fclose(infile); + } + else + { + if (ast_expr(argv[1], s, sizeof(s))) + printf("=====%s======\n",s); + else + printf("No result\n"); + } +} + +#endif + +#undef ast_yyerror +#define ast_yyerror(x) ast_yyerror(x, YYLTYPE *yylloc, struct parse_io *parseio) + +/* I put the ast_yyerror func in the flex input file, + because it refers to the buffer state. Best to + let it access the BUFFER stuff there and not trying + define all the structs, macros etc. in this file! */ + + +static struct val * +op_or (struct val *a, struct val *b) +{ + if (is_zero_or_null (a)) { + free_value (a); + return (b); + } else { + free_value (b); + return (a); + } +} + +static struct val * +op_and (struct val *a, struct val *b) +{ + if (is_zero_or_null (a) || is_zero_or_null (b)) { + free_value (a); + free_value (b); + return (make_integer ((quad_t)0)); + } else { + free_value (b); + return (a); + } +} + +static struct val * +op_eq (struct val *a, struct val *b) +{ + struct val *r; + + if (isstring (a) || isstring (b)) { + to_string (a); + to_string (b); + r = make_integer ((quad_t)(strcoll (a->u.s, b->u.s) == 0)); + } else { +#ifdef DEBUG_FOR_CONVERSIONS + char buffer[2000]; + sprintf(buffer,"Converting '%s' and '%s' ", a->u.s, b->u.s); +#endif + (void)to_integer(a); + (void)to_integer(b); +#ifdef DEBUG_FOR_CONVERSIONS + ast_log(LOG_WARNING,"%s to '%lld' and '%lld'\n", buffer, a->u.i, b->u.i); +#endif + r = make_integer ((quad_t)(a->u.i == b->u.i)); + } + + free_value (a); + free_value (b); + return r; +} + +static struct val * +op_gt (struct val *a, struct val *b) +{ + struct val *r; + + if (isstring (a) || isstring (b)) { + to_string (a); + to_string (b); + r = make_integer ((quad_t)(strcoll (a->u.s, b->u.s) > 0)); + } else { + (void)to_integer(a); + (void)to_integer(b); + r = make_integer ((quad_t)(a->u.i > b->u.i)); + } + + free_value (a); + free_value (b); + return r; +} + +static struct val * +op_lt (struct val *a, struct val *b) +{ + struct val *r; + + if (isstring (a) || isstring (b)) { + to_string (a); + to_string (b); + r = make_integer ((quad_t)(strcoll (a->u.s, b->u.s) < 0)); + } else { + (void)to_integer(a); + (void)to_integer(b); + r = make_integer ((quad_t)(a->u.i < b->u.i)); + } + + free_value (a); + free_value (b); + return r; +} + +static struct val * +op_ge (struct val *a, struct val *b) +{ + struct val *r; + + if (isstring (a) || isstring (b)) { + to_string (a); + to_string (b); + r = make_integer ((quad_t)(strcoll (a->u.s, b->u.s) >= 0)); + } else { + (void)to_integer(a); + (void)to_integer(b); + r = make_integer ((quad_t)(a->u.i >= b->u.i)); + } + + free_value (a); + free_value (b); + return r; +} + +static struct val * +op_le (struct val *a, struct val *b) +{ + struct val *r; + + if (isstring (a) || isstring (b)) { + to_string (a); + to_string (b); + r = make_integer ((quad_t)(strcoll (a->u.s, b->u.s) <= 0)); + } else { + (void)to_integer(a); + (void)to_integer(b); + r = make_integer ((quad_t)(a->u.i <= b->u.i)); + } + + free_value (a); + free_value (b); + return r; +} + +static struct val * +op_cond (struct val *a, struct val *b, struct val *c) +{ + struct val *r; + + if( isstring(a) ) + { + if( strlen(a->u.s) && strcmp(a->u.s, "\"\"") != 0 && strcmp(a->u.s,"0") != 0 ) + { + free_value(a); + free_value(c); + r = b; + } + else + { + free_value(a); + free_value(b); + r = c; + } + } + else + { + (void)to_integer(a); + if( a->u.i ) + { + free_value(a); + free_value(c); + r = b; + } + else + { + free_value(a); + free_value(b); + r = c; + } + } + return r; +} + +static struct val * +op_ne (struct val *a, struct val *b) +{ + struct val *r; + + if (isstring (a) || isstring (b)) { + to_string (a); + to_string (b); + r = make_integer ((quad_t)(strcoll (a->u.s, b->u.s) != 0)); + } else { + (void)to_integer(a); + (void)to_integer(b); + r = make_integer ((quad_t)(a->u.i != b->u.i)); + } + + free_value (a); + free_value (b); + return r; +} + +static int +chk_plus (quad_t a, quad_t b, quad_t r) +{ + /* sum of two positive numbers must be positive */ + if (a > 0 && b > 0 && r <= 0) + return 1; + /* sum of two negative numbers must be negative */ + if (a < 0 && b < 0 && r >= 0) + return 1; + /* all other cases are OK */ + return 0; +} + +static struct val * +op_plus (struct val *a, struct val *b) +{ + struct val *r; + + if (!to_integer (a)) { + if( !extra_error_message_supplied ) + ast_log(LOG_WARNING,"non-numeric argument\n"); + if (!to_integer (b)) { + free_value(a); + free_value(b); + return make_integer(0); + } else { + free_value(a); + return (b); + } + } else if (!to_integer(b)) { + free_value(b); + return (a); + } + + r = make_integer (/*(quad_t)*/(a->u.i + b->u.i)); + if (chk_plus (a->u.i, b->u.i, r->u.i)) { + ast_log(LOG_WARNING,"overflow\n"); + } + free_value (a); + free_value (b); + return r; +} + +static int +chk_minus (quad_t a, quad_t b, quad_t r) +{ + /* special case subtraction of QUAD_MIN */ + if (b == QUAD_MIN) { + if (a >= 0) + return 1; + else + return 0; + } + /* this is allowed for b != QUAD_MIN */ + return chk_plus (a, -b, r); +} + +static struct val * +op_minus (struct val *a, struct val *b) +{ + struct val *r; + + if (!to_integer (a)) { + if( !extra_error_message_supplied ) + ast_log(LOG_WARNING, "non-numeric argument\n"); + if (!to_integer (b)) { + free_value(a); + free_value(b); + return make_integer(0); + } else { + r = make_integer(0 - b->u.i); + free_value(a); + free_value(b); + return (r); + } + } else if (!to_integer(b)) { + if( !extra_error_message_supplied ) + ast_log(LOG_WARNING, "non-numeric argument\n"); + free_value(b); + return (a); + } + + r = make_integer (/*(quad_t)*/(a->u.i - b->u.i)); + if (chk_minus (a->u.i, b->u.i, r->u.i)) { + ast_log(LOG_WARNING, "overflow\n"); + } + free_value (a); + free_value (b); + return r; +} + +static struct val * +op_negate (struct val *a) +{ + struct val *r; + + if (!to_integer (a) ) { + free_value(a); + if( !extra_error_message_supplied ) + ast_log(LOG_WARNING, "non-numeric argument\n"); + return make_integer(0); + } + + r = make_integer (/*(quad_t)*/(- a->u.i)); + if (chk_minus (0, a->u.i, r->u.i)) { + ast_log(LOG_WARNING, "overflow\n"); + } + free_value (a); + return r; +} + +static struct val * +op_compl (struct val *a) +{ + int v1 = 1; + struct val *r; + + if( !a ) + { + v1 = 0; + } + else + { + switch( a->type ) + { + case AST_EXPR_integer: + if( a->u.i == 0 ) + v1 = 0; + break; + + case AST_EXPR_string: + if( a->u.s == 0 ) + v1 = 0; + else + { + if( a->u.s[0] == 0 ) + v1 = 0; + else if (strlen(a->u.s) == 1 && a->u.s[0] == '0' ) + v1 = 0; + } + break; + + case AST_EXPR_numeric_string: + if( a->u.s == 0 ) + v1 = 0; + else + { + if( a->u.s[0] == 0 ) + v1 = 0; + else if (strlen(a->u.s) == 1 && a->u.s[0] == '0' ) + v1 = 0; + } + break; + } + } + + r = make_integer (!v1); + free_value (a); + return r; +} + +static int +chk_times (quad_t a, quad_t b, quad_t r) +{ + /* special case: first operand is 0, no overflow possible */ + if (a == 0) + return 0; + /* cerify that result of division matches second operand */ + if (r / a != b) + return 1; + return 0; +} + +static struct val * +op_times (struct val *a, struct val *b) +{ + struct val *r; + + if (!to_integer (a) || !to_integer (b)) { + free_value(a); + free_value(b); + if( !extra_error_message_supplied ) + ast_log(LOG_WARNING, "non-numeric argument\n"); + return(make_integer(0)); + } + + r = make_integer (/*(quad_t)*/(a->u.i * b->u.i)); + if (chk_times (a->u.i, b->u.i, r->u.i)) { + ast_log(LOG_WARNING, "overflow\n"); + } + free_value (a); + free_value (b); + return (r); +} + +static int +chk_div (quad_t a, quad_t b) +{ + /* div by zero has been taken care of before */ + /* only QUAD_MIN / -1 causes overflow */ + if (a == QUAD_MIN && b == -1) + return 1; + /* everything else is OK */ + return 0; +} + +static struct val * +op_div (struct val *a, struct val *b) +{ + struct val *r; + + if (!to_integer (a)) { + free_value(a); + free_value(b); + if( !extra_error_message_supplied ) + ast_log(LOG_WARNING, "non-numeric argument\n"); + return make_integer(0); + } else if (!to_integer (b)) { + free_value(a); + free_value(b); + if( !extra_error_message_supplied ) + ast_log(LOG_WARNING, "non-numeric argument\n"); + return make_integer(INT_MAX); + } + + if (b->u.i == 0) { + ast_log(LOG_WARNING, "division by zero\n"); + free_value(a); + free_value(b); + return make_integer(INT_MAX); + } + + r = make_integer (/*(quad_t)*/(a->u.i / b->u.i)); + if (chk_div (a->u.i, b->u.i)) { + ast_log(LOG_WARNING, "overflow\n"); + } + free_value (a); + free_value (b); + return r; +} + +static struct val * +op_rem (struct val *a, struct val *b) +{ + struct val *r; + + if (!to_integer (a) || !to_integer (b)) { + if( !extra_error_message_supplied ) + ast_log(LOG_WARNING, "non-numeric argument\n"); + free_value(a); + free_value(b); + return make_integer(0); + } + + if (b->u.i == 0) { + ast_log(LOG_WARNING, "div by zero\n"); + free_value(a); + return(b); + } + + r = make_integer (/*(quad_t)*/(a->u.i % b->u.i)); + /* chk_rem necessary ??? */ + free_value (a); + free_value (b); + return r; +} + + +static struct val * +op_colon (struct val *a, struct val *b) +{ + regex_t rp; + regmatch_t rm[2]; + char errbuf[256]; + int eval; + struct val *v; + + /* coerce to both arguments to strings */ + to_string(a); + to_string(b); + /* strip double quotes from both -- they'll screw up the pattern, and the search string starting at ^ */ + strip_quotes(a); + strip_quotes(b); + /* compile regular expression */ + if ((eval = regcomp (&rp, b->u.s, REG_EXTENDED)) != 0) { + regerror (eval, &rp, errbuf, sizeof(errbuf)); + ast_log(LOG_WARNING,"regcomp() error : %s",errbuf); + free_value(a); + free_value(b); + return make_str(""); + } + + /* compare string against pattern */ + /* remember that patterns are anchored to the beginning of the line */ + if (regexec(&rp, a->u.s, (size_t)2, rm, 0) == 0 && rm[0].rm_so == 0) { + if (rm[1].rm_so >= 0) { + *(a->u.s + rm[1].rm_eo) = '\0'; + v = make_str (a->u.s + rm[1].rm_so); + + } else { + v = make_integer ((quad_t)(rm[0].rm_eo - rm[0].rm_so)); + } + } else { + if (rp.re_nsub == 0) { + v = make_integer ((quad_t)0); + } else { + v = make_str (""); + } + } + + /* free arguments and pattern buffer */ + free_value (a); + free_value (b); + regfree (&rp); + + return v; +} + + +static struct val * +op_eqtilde (struct val *a, struct val *b) +{ + regex_t rp; + regmatch_t rm[2]; + char errbuf[256]; + int eval; + struct val *v; + + /* coerce to both arguments to strings */ + to_string(a); + to_string(b); + /* strip double quotes from both -- they'll screw up the pattern, and the search string starting at ^ */ + strip_quotes(a); + strip_quotes(b); + /* compile regular expression */ + if ((eval = regcomp (&rp, b->u.s, REG_EXTENDED)) != 0) { + regerror (eval, &rp, errbuf, sizeof(errbuf)); + ast_log(LOG_WARNING,"regcomp() error : %s",errbuf); + free_value(a); + free_value(b); + return make_str(""); + } + + /* compare string against pattern */ + /* remember that patterns are anchored to the beginning of the line */ + if (regexec(&rp, a->u.s, (size_t)2, rm, 0) == 0 ) { + if (rm[1].rm_so >= 0) { + *(a->u.s + rm[1].rm_eo) = '\0'; + v = make_str (a->u.s + rm[1].rm_so); + + } else { + v = make_integer ((quad_t)(rm[0].rm_eo - rm[0].rm_so)); + } + } else { + if (rp.re_nsub == 0) { + v = make_integer ((quad_t)0); + } else { + v = make_str (""); + } + } + + /* free arguments and pattern buffer */ + free_value (a); + free_value (b); + regfree (&rp); + + return v; +} diff --git a/main/ast_expr2f.c b/main/ast_expr2f.c new file mode 100644 index 000000000..7d790924d --- /dev/null +++ b/main/ast_expr2f.c @@ -0,0 +1,3299 @@ +#line 2 "ast_expr2f.c" + +#line 4 "ast_expr2f.c" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 5 +#define YY_FLEX_SUBMINOR_VERSION 33 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; +#endif /* ! C99 */ + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#endif /* ! FLEXINT_H */ + +#ifdef __cplusplus + +/* The "const" storage-class-modifier is valid. */ +#define YY_USE_CONST + +#else /* ! __cplusplus */ + +#if __STDC__ + +#define YY_USE_CONST + +#endif /* __STDC__ */ +#endif /* ! __cplusplus */ + +#ifdef YY_USE_CONST +#define yyconst const +#else +#define yyconst +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an unsigned + * integer for use as an array index. If the signed char is negative, + * we want to instead treat it as an 8-bit unsigned char, hence the + * double cast. + */ +#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +int ast_yylex_init (yyscan_t* scanner); + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN yyg->yy_start = 1 + 2 * + +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START ((yyg->yy_start - 1) / 2) +#define YYSTATE YY_START + +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) + +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE ast_yyrestart(yyin ,yyscanner ) + +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#define YY_BUF_SIZE 16384 +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = yyg->yy_hold_char; \ + YY_RESTORE_YY_MORE_OFFSET \ + yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) + +#define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner ) + +/* The following is because we cannot portably get our hands on size_t + * (without autoconf's help, which isn't available because we want + * flex-generated scanners to compile on their own). + */ + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef unsigned int yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + yy_size_t yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via ast_yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \ + ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \ + : NULL) + +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] + +void ast_yyrestart (FILE *input_file ,yyscan_t yyscanner ); +void ast_yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner ); +YY_BUFFER_STATE ast_yy_create_buffer (FILE *file,int size ,yyscan_t yyscanner ); +void ast_yy_delete_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner ); +void ast_yy_flush_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner ); +void ast_yypush_buffer_state (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner ); +void ast_yypop_buffer_state (yyscan_t yyscanner ); + +static void ast_yyensure_buffer_stack (yyscan_t yyscanner ); +static void ast_yy_load_buffer_state (yyscan_t yyscanner ); +static void ast_yy_init_buffer (YY_BUFFER_STATE b,FILE *file ,yyscan_t yyscanner ); + +#define YY_FLUSH_BUFFER ast_yy_flush_buffer(YY_CURRENT_BUFFER ,yyscanner) + +YY_BUFFER_STATE ast_yy_scan_buffer (char *base,yy_size_t size ,yyscan_t yyscanner ); +YY_BUFFER_STATE ast_yy_scan_string (yyconst char *yy_str ,yyscan_t yyscanner ); +YY_BUFFER_STATE ast_yy_scan_bytes (yyconst char *bytes,int len ,yyscan_t yyscanner ); + +void *ast_yyalloc (yy_size_t ,yyscan_t yyscanner ); +void *ast_yyrealloc (void *,yy_size_t ,yyscan_t yyscanner ); +void ast_yyfree (void * ,yyscan_t yyscanner ); + +#define yy_new_buffer ast_yy_create_buffer + +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + ast_yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + ast_yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } + +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + ast_yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + ast_yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } + +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define ast_yywrap(n) 1 +#define YY_SKIP_YYWRAP + +typedef char YY_CHAR; + +typedef int yy_state_type; + +#define yytext_ptr yytext_r +static yyconst flex_int16_t yy_nxt[][128] = + { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + }, + + { + 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, + 10, 8, 8, 9, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 9, 11, 12, 13, 14, 15, 16, 13, + 17, 18, 19, 20, 13, 21, 13, 22, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 24, 13, + 25, 26, 27, 28, 13, 13, 13, 13, 13, 13, + + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 8, 13, 8, 13, 13, 8, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 8, 29, 8, 8, 8 + }, + + { + 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, + 10, 8, 8, 9, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 9, 11, 12, 13, 14, 15, 16, 13, + + 17, 18, 19, 20, 13, 21, 13, 22, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 24, 13, + 25, 26, 27, 28, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 8, 13, 8, 13, 13, 8, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 8, 29, 8, 8, 8 + }, + + { + 7, 30, 30, 30, 30, 30, 30, 30, 30, 30, + + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 31, 30, 32, 30, 30 + }, + + { + 7, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 31, 30, 32, 30, 30 + }, + + { + 7, 33, 33, 33, 33, 33, 33, 33, 33, 34, + 34, 33, 33, 34, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 34, 34, 33, 33, 35, 34, 34, 33, + 34, 34, 34, 34, 33, 34, 33, 34, 33, 33, + + 33, 33, 33, 33, 33, 33, 33, 33, 34, 33, + 34, 34, 34, 34, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 34, 33, 33, 33 + }, + + { + 7, 33, 33, 33, 33, 33, 33, 33, 33, 34, + 34, 33, 33, 34, 33, 33, 33, 33, 33, 33, + + 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 34, 34, 33, 33, 35, 34, 34, 33, + 34, 34, 34, 34, 33, 34, 33, 34, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 34, 33, + 34, 34, 34, 34, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + + 33, 33, 33, 33, 34, 33, 33, 33 + }, + + { + -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, + -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, + -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, + -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, + -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, + -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, + -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, + -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, + -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, + + -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, + -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, + -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, + -7, -7, -7, -7, -7, -7, -7, -7 + }, + + { + 7, -8, -8, -8, -8, -8, -8, -8, -8, -8, + -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, + -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, + -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, + -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, + -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, + + -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, + -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, + -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, + -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, + -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, + -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, + -8, -8, -8, -8, -8, -8, -8, -8 + }, + + { + 7, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, + -9, -9, -9, -9, -9, -9, -9, -9 + + }, + + { + 7, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, + -10, -10, -10, -10, -10, -10, -10, -10 + }, + + { + 7, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, 36, -11, -11, -11, -11, -11, -11, -11, -11, + + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11 + }, + + { + 7, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 38, 37, 37, 37, 37, 37, + + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37 + }, + + { + 7, -13, -13, -13, -13, -13, -13, -13, -13, -13, + + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, 39, 39, -13, -13, 39, + -13, -13, -13, -13, 39, -13, 39, -13, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, -13, 39, + -13, -13, -13, -13, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, -13, 39, -13, 39, 39, -13, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, -13, -13, -13, -13, -13 + }, + + { + 7, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, 39, 39, -14, -14, 39, + -14, -14, -14, -14, 39, -14, 39, -14, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, -14, 39, + -14, -14, -14, -14, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, -14, 39, -14, 39, 39, -14, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 40, -14, -14, -14, -14 + }, + + { + 7, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, + -15, -15, -15, -15, -15, -15, -15, -15 + }, + + { + 7, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, 41, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + + -16, -16, -16, -16, -16, -16, -16, -16 + }, + + { + 7, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17, -17 + }, + + { + 7, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, -18, -18, -18, -18, -18 + }, + + { + 7, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19, -19, -19, + -19, -19, -19, -19, -19, -19, -19, -19 + + }, + + { + 7, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20, -20 + }, + + { + 7, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21, -21, -21, + -21, -21, -21, -21, -21, -21, -21, -21 + }, + + { + 7, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22, -22, -22, + -22, -22, -22, -22, -22, -22, -22, -22 + }, + + { + 7, -23, -23, -23, -23, -23, -23, -23, -23, -23, + + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, 39, 39, -23, -23, 39, + -23, -23, -23, -23, 39, -23, 39, -23, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, -23, 39, + -23, -23, -23, -23, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, -23, 39, -23, 39, 39, -23, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, -23, -23, -23, -23, -23 + }, + + { + 7, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, 43, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, -24 + }, + + { + 7, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, 44, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -25, -25, -25 + }, + + { + 7, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, 45, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26, -26, -26, -26, + + -26, -26, -26, -26, -26, -26, 46, -26 + }, + + { + 7, -27, -27, -27, -27, -27, -27, -27, -27, -27, + -27, -27, -27, -27, -27, -27, -27, -27, -27, -27, + -27, -27, -27, -27, -27, -27, -27, -27, -27, -27, + -27, -27, -27, -27, -27, -27, -27, -27, -27, -27, + -27, -27, -27, -27, -27, -27, -27, -27, -27, -27, + -27, -27, -27, -27, -27, -27, -27, -27, -27, -27, + -27, 47, -27, -27, -27, -27, -27, -27, -27, -27, + -27, -27, -27, -27, -27, -27, -27, -27, -27, -27, + -27, -27, -27, -27, -27, -27, -27, -27, -27, -27, + + -27, -27, -27, -27, -27, -27, -27, -27, -27, -27, + -27, -27, -27, -27, -27, -27, -27, -27, -27, -27, + -27, -27, -27, -27, -27, -27, -27, -27, -27, -27, + -27, -27, -27, -27, -27, -27, -27, -27 + }, + + { + 7, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, -28, -28, -28, -28 + }, + + { + 7, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, 48, -29, -29, -29 + + }, + + { + 7, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 50, 49, 51, 49, 49 + }, + + { + 7, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31, -31, -31, + -31, -31, -31, -31, -31, -31, -31, -31 + }, + + { + 7, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32, -32 + }, + + { + 7, 52, 52, 52, 52, 52, 52, 52, 52, -33, + + -33, 52, 52, -33, 52, 52, 52, 52, 52, 52, + 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, + 52, 52, -33, -33, 52, 52, -33, -33, -33, 52, + -33, -33, -33, -33, 52, -33, 52, -33, 52, 52, + 52, 52, 52, 52, 52, 52, 52, 52, -33, 52, + -33, -33, -33, -33, 52, 52, 52, 52, 52, 52, + 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, + 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, + 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, + 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, + + 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, + 52, 52, 52, 52, -33, 52, 52, 52 + }, + + { + 7, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34 + }, + + { + 7, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, 53, -35, -35, -35, -35 + }, + + { + 7, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36, -36, -36, -36, + + -36, -36, -36, -36, -36, -36, -36, -36 + }, + + { + 7, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 38, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37 + }, + + { + 7, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, -38, -38 + }, + + { + 7, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39, -39, -39, -39, + + -39, -39, -39, -39, -39, 39, 39, -39, -39, 39, + -39, -39, -39, -39, 39, -39, 39, -39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, -39, 39, + -39, -39, -39, -39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, -39, 39, -39, 39, 39, -39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, -39, -39, -39, -39, -39 + + }, + + { + 7, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, + + -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40 + }, + + { + 7, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41, -41, -41, + -41, -41, -41, -41, -41, -41, -41, -41 + }, + + { + 7, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, -42, -42, -42, -42, -42, + -42, -42, -42, -42, -42, 39, 39, -42, -42, 39, + + -42, -42, -42, -42, 39, -42, 39, -42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, -42, 39, + -42, -42, -42, -42, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, -42, 39, -42, 39, 39, -42, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, -42, -42, -42, -42, -42 + }, + + { + 7, -43, -43, -43, -43, -43, -43, -43, -43, -43, + + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + + -43, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43, -43 + }, + + { + 7, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, -44, -44, -44, -44, -44 + }, + + { + 7, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45, -45, -45, + -45, -45, -45, -45, -45, -45, -45, -45 + }, + + { + 7, -46, -46, -46, -46, -46, -46, -46, -46, -46, + -46, -46, -46, -46, -46, -46, -46, -46, -46, -46, + + -46, -46, -46, -46, -46, -46, -46, -46, -46, -46, + -46, -46, -46, -46, -46, -46, -46, -46, -46, -46, + -46, -46, -46, -46, -46, -46, -46, -46, -46, -46, + -46, -46, -46, -46, -46, -46, -46, -46, -46, -46, + -46, -46, -46, -46, -46, -46, -46, -46, -46, -46, + -46, -46, -46, -46, -46, -46, -46, -46, -46, -46, + -46, -46, -46, -46, -46, -46, -46, -46, -46, -46, + -46, -46, -46, -46, -46, -46, -46, -46, -46, -46, + -46, -46, -46, -46, -46, -46, -46, -46, -46, -46, + -46, -46, -46, -46, -46, -46, -46, -46, -46, -46, + + -46, -46, -46, -46, -46, -46, -46, -46 + }, + + { + 7, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -47, -47, -47, -47, -47 + }, + + { + 7, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, -48 + }, + + { + 7, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 50, 49, 51, 49, 49 + + }, + + { + 7, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50, -50 + }, + + { + 7, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51, -51 + }, + + { + 7, 52, 52, 52, 52, 52, 52, 52, 52, -52, + -52, 52, 52, -52, 52, 52, 52, 52, 52, 52, + 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, + 52, 52, -52, -52, 52, 52, -52, -52, -52, 52, + + -52, -52, -52, -52, 52, -52, 52, -52, 52, 52, + 52, 52, 52, 52, 52, 52, 52, 52, -52, 52, + -52, -52, -52, -52, 52, 52, 52, 52, 52, 52, + 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, + 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, + 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, + 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, + 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, + 52, 52, 52, 52, -52, 52, 52, 52 + }, + + { + 7, -53, -53, -53, -53, -53, -53, -53, -53, -53, + + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53 + }, + + } ; + +static yy_state_type yy_get_previous_state (yyscan_t yyscanner ); +static yy_state_type yy_try_NUL_trans (yy_state_type current_state ,yyscan_t yyscanner); +static int yy_get_next_buffer (yyscan_t yyscanner ); +static void yy_fatal_error (yyconst char msg[] ,yyscan_t yyscanner ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + yyg->yytext_ptr = yy_bp; \ + yyg->yytext_ptr -= yyg->yy_more_len; \ + yyleng = (size_t) (yy_cp - yyg->yytext_ptr); \ + yyg->yy_hold_char = *yy_cp; \ + *yy_cp = '\0'; \ + yyg->yy_c_buf_p = yy_cp; + +#define YY_NUM_RULES 35 +#define YY_END_OF_BUFFER 36 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static yyconst flex_int16_t yy_accept[54] = + { 0, + 0, 0, 0, 0, 32, 32, 36, 35, 25, 27, + 19, 35, 29, 29, 17, 2, 22, 23, 15, 13, + 14, 16, 28, 20, 9, 3, 8, 18, 1, 35, + 31, 30, 32, 33, 33, 12, 0, 26, 29, 24, + 5, 28, 21, 11, 6, 7, 10, 4, 0, 31, + 30, 32, 34 + } ; + +static yyconst yy_state_type yy_NUL_trans[54] = + { 0, + 8, 8, 30, 30, 33, 33, 0, 0, 0, 0, + 0, 37, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, + 0, 0, 52, 0, 0, 0, 37, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 49, 0, + 0, 52, 0 + } ; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() (yyg->yy_more_flag = 1) +#define YY_MORE_ADJ yyg->yy_more_len +#define YY_RESTORE_YY_MORE_OFFSET +#line 1 "ast_expr2.fl" +#line 2 "ast_expr2.fl" +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Dialplan Expression Lexical Scanner + */ + +#include "asterisk.h" + +#ifndef STANDALONE +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") +#endif + +#include +#include +#include +#include +#include +#include +#if !defined(SOLARIS) && !defined(__CYGWIN__) +#include +#else +#define quad_t int64_t +#endif +#include +#include +#include + +#include "asterisk/ast_expr.h" +#include "asterisk/logger.h" +#include "asterisk/strings.h" + +enum valtype { + AST_EXPR_integer, AST_EXPR_numeric_string, AST_EXPR_string +} ; + +struct val { + enum valtype type; + union { + char *s; + quad_t i; + } u; +} ; + +#include "ast_expr2.h" /* the o/p of the bison on ast_expr2.y */ + +#define SET_COLUMNS do { \ + yylloc_param->first_column = (int)(yyg->yytext_r - YY_CURRENT_BUFFER_LVALUE->yy_ch_buf); \ + yylloc_param->last_column += yyleng - 1; \ + yylloc_param->first_line = yylloc_param->last_line = 1; \ + } while (0) + +#define SET_STRING do { \ + yylval_param->val = calloc(1, sizeof(struct val)); \ + yylval_param->val->type = AST_EXPR_string; \ + yylval_param->val->u.s = strdup(yytext); \ + } while (0) + +#define SET_NUMERIC_STRING do { \ + yylval_param->val = calloc(1, sizeof(struct val)); \ + yylval_param->val->type = AST_EXPR_numeric_string; \ + yylval_param->val->u.s = strdup(yytext); \ + } while (0) + +struct parse_io +{ + char *string; + struct val *val; + yyscan_t scanner; +}; + +void ast_yyset_column(int column_no, yyscan_t yyscanner); +int ast_yyget_column(yyscan_t yyscanner); +static int curlycount = 0; +static char *expr2_token_subst(char *mess); + +#line 1427 "ast_expr2f.c" + +#define INITIAL 0 +#define var 1 +#define trail 2 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* Holds the entire state of the reentrant scanner. */ +struct yyguts_t + { + + /* User-defined. Not touched by flex. */ + YY_EXTRA_TYPE yyextra_r; + + /* The rest are the same as the globals declared in the non-reentrant scanner. */ + FILE *yyin_r, *yyout_r; + size_t yy_buffer_stack_top; /**< index of top of stack. */ + size_t yy_buffer_stack_max; /**< capacity of stack. */ + YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ + char yy_hold_char; + int yy_n_chars; + int yyleng_r; + char *yy_c_buf_p; + int yy_init; + int yy_start; + int yy_did_buffer_switch_on_eof; + int yy_start_stack_ptr; + int yy_start_stack_depth; + int *yy_start_stack; + yy_state_type yy_last_accepting_state; + char* yy_last_accepting_cpos; + + int yylineno_r; + int yy_flex_debug_r; + + char *yytext_r; + int yy_more_flag; + int yy_more_len; + + YYSTYPE * yylval_r; + + YYLTYPE * yylloc_r; + + }; /* end struct yyguts_t */ + +static int yy_init_globals (yyscan_t yyscanner ); + + /* This must go here because YYSTYPE and YYLTYPE are included + * from bison output in section 1.*/ + # define yylval yyg->yylval_r + + # define yylloc yyg->yylloc_r + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int ast_yylex_destroy (yyscan_t yyscanner ); + +int ast_yyget_debug (yyscan_t yyscanner ); + +void ast_yyset_debug (int debug_flag ,yyscan_t yyscanner ); + +YY_EXTRA_TYPE ast_yyget_extra (yyscan_t yyscanner ); + +void ast_yyset_extra (YY_EXTRA_TYPE user_defined ,yyscan_t yyscanner ); + +FILE *ast_yyget_in (yyscan_t yyscanner ); + +void ast_yyset_in (FILE * in_str ,yyscan_t yyscanner ); + +FILE *ast_yyget_out (yyscan_t yyscanner ); + +void ast_yyset_out (FILE * out_str ,yyscan_t yyscanner ); + +int ast_yyget_leng (yyscan_t yyscanner ); + +char *ast_yyget_text (yyscan_t yyscanner ); + +int ast_yyget_lineno (yyscan_t yyscanner ); + +void ast_yyset_lineno (int line_number ,yyscan_t yyscanner ); + +YYSTYPE * ast_yyget_lval (yyscan_t yyscanner ); + +void ast_yyset_lval (YYSTYPE * yylval_param ,yyscan_t yyscanner ); + + YYLTYPE *ast_yyget_lloc (yyscan_t yyscanner ); + + void ast_yyset_lloc (YYLTYPE * yylloc_param ,yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int ast_yywrap (yyscan_t yyscanner ); +#else +extern int ast_yywrap (yyscan_t yyscanner ); +#endif +#endif + + static void yyunput (int c,char *buf_ptr ,yyscan_t yyscanner); + +#ifndef yytext_ptr +static void yy_flex_strncpy (char *,yyconst char *,int ,yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * ,yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT + +#ifdef __cplusplus +static int yyinput (yyscan_t yyscanner ); +#else +static int input (yyscan_t yyscanner ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#define YY_READ_BUF_SIZE 8192 +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO (void) fwrite( yytext, yyleng, 1, yyout ) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + errno=0; \ + while ( (result = read( fileno(yyin), (char *) buf, max_size )) < 0 ) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int ast_yylex \ + (YYSTYPE * yylval_param,YYLTYPE * yylloc_param ,yyscan_t yyscanner); + +#define YY_DECL int ast_yylex \ + (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + register yy_state_type yy_current_state; + register char *yy_cp, *yy_bp; + register int yy_act; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + +#line 104 "ast_expr2.fl" + + +#line 1653 "ast_expr2f.c" + + yylval = yylval_param; + + yylloc = yylloc_param; + + if ( !yyg->yy_init ) + { + yyg->yy_init = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! yyg->yy_start ) + yyg->yy_start = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + ast_yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + ast_yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); + } + + ast_yy_load_buffer_state(yyscanner ); + } + + while ( 1 ) /* loops until end-of-file is reached */ + { + yyg->yy_more_len = 0; + if ( yyg->yy_more_flag ) + { + yyg->yy_more_len = yyg->yy_c_buf_p - yyg->yytext_ptr; + yyg->yy_more_flag = 0; + } + yy_cp = yyg->yy_c_buf_p; + + /* Support of yytext. */ + *yy_cp = yyg->yy_hold_char; + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = yyg->yy_start; +yy_match: + while ( (yy_current_state = yy_nxt[yy_current_state][ YY_SC_TO_UI(*yy_cp) ]) > 0 ) + { + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + + ++yy_cp; + } + + yy_current_state = -yy_current_state; + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = yyg->yy_hold_char; + yy_cp = yyg->yy_last_accepting_cpos + 1; + yy_current_state = yyg->yy_last_accepting_state; + goto yy_find_action; + +case 1: +YY_RULE_SETUP +#line 106 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_OR;} + YY_BREAK +case 2: +YY_RULE_SETUP +#line 107 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_AND;} + YY_BREAK +case 3: +YY_RULE_SETUP +#line 108 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_EQ;} + YY_BREAK +case 4: +YY_RULE_SETUP +#line 109 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_OR;} + YY_BREAK +case 5: +YY_RULE_SETUP +#line 110 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_AND;} + YY_BREAK +case 6: +YY_RULE_SETUP +#line 111 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_EQ;} + YY_BREAK +case 7: +YY_RULE_SETUP +#line 112 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_EQTILDE;} + YY_BREAK +case 8: +YY_RULE_SETUP +#line 113 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_GT;} + YY_BREAK +case 9: +YY_RULE_SETUP +#line 114 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_LT;} + YY_BREAK +case 10: +YY_RULE_SETUP +#line 115 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_GE;} + YY_BREAK +case 11: +YY_RULE_SETUP +#line 116 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_LE;} + YY_BREAK +case 12: +YY_RULE_SETUP +#line 117 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_NE;} + YY_BREAK +case 13: +YY_RULE_SETUP +#line 118 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_PLUS;} + YY_BREAK +case 14: +YY_RULE_SETUP +#line 119 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_MINUS;} + YY_BREAK +case 15: +YY_RULE_SETUP +#line 120 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_MULT;} + YY_BREAK +case 16: +YY_RULE_SETUP +#line 121 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_DIV;} + YY_BREAK +case 17: +YY_RULE_SETUP +#line 122 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_MOD;} + YY_BREAK +case 18: +YY_RULE_SETUP +#line 123 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_COND;} + YY_BREAK +case 19: +YY_RULE_SETUP +#line 124 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_COMPL;} + YY_BREAK +case 20: +YY_RULE_SETUP +#line 125 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_COLON;} + YY_BREAK +case 21: +YY_RULE_SETUP +#line 126 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_COLONCOLON;} + YY_BREAK +case 22: +YY_RULE_SETUP +#line 127 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_LP;} + YY_BREAK +case 23: +YY_RULE_SETUP +#line 128 "ast_expr2.fl" +{ SET_COLUMNS; SET_STRING; return TOK_RP;} + YY_BREAK +case 24: +YY_RULE_SETUP +#line 129 "ast_expr2.fl" +{ + /* gather the contents of ${} expressions, with trailing stuff, + * into a single TOKEN. + * They are much more complex now than they used to be + */ + curlycount = 0; + BEGIN(var); + yymore(); + } + YY_BREAK +case 25: +YY_RULE_SETUP +#line 139 "ast_expr2.fl" +{} + YY_BREAK +case 26: +/* rule 26 can match eol */ +YY_RULE_SETUP +#line 140 "ast_expr2.fl" +{SET_COLUMNS; SET_STRING; return TOKEN;} + YY_BREAK +case 27: +/* rule 27 can match eol */ +YY_RULE_SETUP +#line 142 "ast_expr2.fl" +{/* what to do with eol */} + YY_BREAK +case 28: +YY_RULE_SETUP +#line 143 "ast_expr2.fl" +{ + SET_COLUMNS; + /* the original behavior of the expression parser was + * to bring in numbers as a numeric string + */ + SET_NUMERIC_STRING; + return TOKEN; + } + YY_BREAK +case 29: +YY_RULE_SETUP +#line 152 "ast_expr2.fl" +{ + SET_COLUMNS; + SET_STRING; + return TOKEN; + } + YY_BREAK +case 30: +/* rule 30 can match eol */ +YY_RULE_SETUP +#line 159 "ast_expr2.fl" +{ + curlycount--; + if (curlycount < 0) { + BEGIN(trail); + yymore(); + } else { + yymore(); + } + } + YY_BREAK +case 31: +/* rule 31 can match eol */ +YY_RULE_SETUP +#line 169 "ast_expr2.fl" +{ + curlycount++; + yymore(); + } + YY_BREAK +case 32: +YY_RULE_SETUP +#line 175 "ast_expr2.fl" +{ + BEGIN(0); + SET_COLUMNS; + SET_STRING; + return TOKEN; + } + YY_BREAK +case 33: +/* rule 33 can match eol */ +YY_RULE_SETUP +#line 182 "ast_expr2.fl" +{ + char c = yytext[yyleng-1]; + BEGIN(0); + unput(c); + SET_COLUMNS; + SET_STRING; + return TOKEN; + } + YY_BREAK +case 34: +YY_RULE_SETUP +#line 191 "ast_expr2.fl" +{ + curlycount = 0; + BEGIN(var); + yymore(); + } + YY_BREAK +case YY_STATE_EOF(trail): +#line 197 "ast_expr2.fl" +{ + BEGIN(0); + SET_COLUMNS; + SET_STRING; + return TOKEN; + /*actually, if an expr is only a variable ref, this could happen a LOT */ + } + YY_BREAK +case 35: +YY_RULE_SETUP +#line 205 "ast_expr2.fl" +ECHO; + YY_BREAK +#line 1970 "ast_expr2f.c" +case YY_STATE_EOF(INITIAL): +case YY_STATE_EOF(var): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = yyg->yy_hold_char; + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * ast_yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner); + + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++yyg->yy_c_buf_p; + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = yyg->yy_c_buf_p; + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_END_OF_FILE: + { + yyg->yy_did_buffer_switch_on_eof = 0; + + if ( ast_yywrap(yyscanner ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = + yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + yyg->yy_c_buf_p = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars]; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ +} /* end of ast_yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + register char *source = yyg->yytext_ptr; + register int number_to_move, i; + int ret_val; + + if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr) - 1; + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER; + + int yy_c_buf_p_offset = + (int) (yyg->yy_c_buf_p - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + ast_yyrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ,yyscanner ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = 0; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + yyg->yy_n_chars, (size_t) num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + if ( yyg->yy_n_chars == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + ast_yyrestart(yyin ,yyscanner); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + yyg->yy_n_chars += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; + + yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (yyscan_t yyscanner) +{ + register yy_state_type yy_current_state; + register char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_current_state = yyg->yy_start; + + for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp ) + { + if ( *yy_cp ) + { + yy_current_state = yy_nxt[yy_current_state][YY_SC_TO_UI(*yy_cp)]; + } + else + yy_current_state = yy_NUL_trans[yy_current_state]; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state , yyscan_t yyscanner) +{ + register int yy_is_jam; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */ + register char *yy_cp = yyg->yy_c_buf_p; + + yy_current_state = yy_NUL_trans[yy_current_state]; + yy_is_jam = (yy_current_state == 0); + + if ( ! yy_is_jam ) + { + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + } + + return yy_is_jam ? 0 : yy_current_state; +} + + static void yyunput (int c, register char * yy_bp , yyscan_t yyscanner) +{ + register char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_cp = yyg->yy_c_buf_p; + + /* undo effects of setting up yytext */ + *yy_cp = yyg->yy_hold_char; + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + { /* need to shift things up to make room */ + /* +2 for EOB chars. */ + register int number_to_move = yyg->yy_n_chars + 2; + register char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2]; + register char *source = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]; + + while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + *--dest = *--source; + + yy_cp += (int) (dest - source); + yy_bp += (int) (dest - source); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_buf_size; + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + YY_FATAL_ERROR( "flex scanner push-back overflow" ); + } + + *--yy_cp = (char) c; + + yyg->yytext_ptr = yy_bp; + yyg->yy_hold_char = *yy_cp; + yyg->yy_c_buf_p = yy_cp; +} + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (yyscan_t yyscanner) +#else + static int input (yyscan_t yyscanner) +#endif + +{ + int c; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + *yyg->yy_c_buf_p = yyg->yy_hold_char; + + if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + /* This was really a NUL. */ + *yyg->yy_c_buf_p = '\0'; + + else + { /* need more input */ + int offset = yyg->yy_c_buf_p - yyg->yytext_ptr; + ++yyg->yy_c_buf_p; + + switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + ast_yyrestart(yyin ,yyscanner); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( ast_yywrap(yyscanner ) ) + return EOF; + + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(yyscanner); +#else + return input(yyscanner); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = yyg->yytext_ptr + offset; + break; + } + } + } + + c = *(unsigned char *) yyg->yy_c_buf_p; /* cast for 8-bit char's */ + *yyg->yy_c_buf_p = '\0'; /* preserve yytext */ + yyg->yy_hold_char = *++yyg->yy_c_buf_p; + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * @param yyscanner The scanner object. + * @note This function does not reset the start condition to @c INITIAL . + */ + void ast_yyrestart (FILE * input_file , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! YY_CURRENT_BUFFER ){ + ast_yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + ast_yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); + } + + ast_yy_init_buffer(YY_CURRENT_BUFFER,input_file ,yyscanner); + ast_yy_load_buffer_state(yyscanner ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * @param yyscanner The scanner object. + */ + void ast_yy_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* TODO. We should be able to replace this entire function body + * with + * ast_yypop_buffer_state(); + * ast_yypush_buffer_state(new_buffer); + */ + ast_yyensure_buffer_stack (yyscanner); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + ast_yy_load_buffer_state(yyscanner ); + + /* We don't actually know whether we did this switch during + * EOF (ast_yywrap()) processing, but the only time this flag + * is looked at is after ast_yywrap() is called, so it's safe + * to go ahead and always set it. + */ + yyg->yy_did_buffer_switch_on_eof = 1; +} + +static void ast_yy_load_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + yyg->yy_hold_char = *yyg->yy_c_buf_p; +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * @param yyscanner The scanner object. + * @return the allocated buffer state. + */ + YY_BUFFER_STATE ast_yy_create_buffer (FILE * file, int size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) ast_yyalloc(sizeof( struct yy_buffer_state ) ,yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in ast_yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) ast_yyalloc(b->yy_buf_size + 2 ,yyscanner ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in ast_yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + ast_yy_init_buffer(b,file ,yyscanner); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with ast_yy_create_buffer() + * @param yyscanner The scanner object. + */ + void ast_yy_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + ast_yyfree((void *) b->yy_ch_buf ,yyscanner ); + + ast_yyfree((void *) b ,yyscanner ); +} + +#ifndef __cplusplus +extern int isatty (int ); +#endif /* __cplusplus */ + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a ast_yyrestart() or at EOF. + */ + static void ast_yy_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) + +{ + int oerrno = errno; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + ast_yy_flush_buffer(b ,yyscanner); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then ast_yy_init_buffer was _probably_ + * called from ast_yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * @param yyscanner The scanner object. + */ + void ast_yy_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + ast_yy_load_buffer_state(yyscanner ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * @param yyscanner The scanner object. + */ +void ast_yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (new_buffer == NULL) + return; + + ast_yyensure_buffer_stack(yyscanner); + + /* This block is copied from ast_yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + yyg->yy_buffer_stack_top++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from ast_yy_switch_to_buffer. */ + ast_yy_load_buffer_state(yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * @param yyscanner The scanner object. + */ +void ast_yypop_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (!YY_CURRENT_BUFFER) + return; + + ast_yy_delete_buffer(YY_CURRENT_BUFFER ,yyscanner); + YY_CURRENT_BUFFER_LVALUE = NULL; + if (yyg->yy_buffer_stack_top > 0) + --yyg->yy_buffer_stack_top; + + if (YY_CURRENT_BUFFER) { + ast_yy_load_buffer_state(yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void ast_yyensure_buffer_stack (yyscan_t yyscanner) +{ + int num_to_alloc; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (!yyg->yy_buffer_stack) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; + yyg->yy_buffer_stack = (struct yy_buffer_state**)ast_yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + + memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + yyg->yy_buffer_stack_max = num_to_alloc; + yyg->yy_buffer_stack_top = 0; + return; + } + + if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + int grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = yyg->yy_buffer_stack_max + grow_size; + yyg->yy_buffer_stack = (struct yy_buffer_state**)ast_yyrealloc + (yyg->yy_buffer_stack, + num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + + /* zero only the new slots.*/ + memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*)); + yyg->yy_buffer_stack_max = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE ast_yy_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return 0; + + b = (YY_BUFFER_STATE) ast_yyalloc(sizeof( struct yy_buffer_state ) ,yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in ast_yy_scan_buffer()" ); + + b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = 0; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + ast_yy_switch_to_buffer(b ,yyscanner ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to ast_yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * ast_yy_scan_bytes() instead. + */ +YY_BUFFER_STATE ast_yy_scan_string (yyconst char * yystr , yyscan_t yyscanner) +{ + + return ast_yy_scan_bytes(yystr,strlen(yystr) ,yyscanner); +} + +/** Setup the input buffer state to scan the given bytes. The next call to ast_yylex() will + * scan from a @e copy of @a bytes. + * @param bytes the byte buffer to scan + * @param len the number of bytes in the buffer pointed to by @a bytes. + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE ast_yy_scan_bytes (yyconst char * yybytes, int _yybytes_len , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = _yybytes_len + 2; + buf = (char *) ast_yyalloc(n ,yyscanner ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in ast_yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = ast_yy_scan_buffer(buf,n ,yyscanner); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in ast_yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yy_fatal_error (yyconst char* msg , yyscan_t yyscanner) +{ + (void) fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = yyg->yy_hold_char; \ + yyg->yy_c_buf_p = yytext + yyless_macro_arg; \ + yyg->yy_hold_char = *yyg->yy_c_buf_p; \ + *yyg->yy_c_buf_p = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the user-defined data for this scanner. + * @param yyscanner The scanner object. + */ +YY_EXTRA_TYPE ast_yyget_extra (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyextra; +} + +/** Get the current line number. + * @param yyscanner The scanner object. + */ +int ast_yyget_lineno (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yylineno; +} + +/** Get the current column number. + * @param yyscanner The scanner object. + */ +int ast_yyget_column (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yycolumn; +} + +/** Get the input stream. + * @param yyscanner The scanner object. + */ +FILE *ast_yyget_in (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyin; +} + +/** Get the output stream. + * @param yyscanner The scanner object. + */ +FILE *ast_yyget_out (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyout; +} + +/** Get the length of the current token. + * @param yyscanner The scanner object. + */ +int ast_yyget_leng (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyleng; +} + +/** Get the current token. + * @param yyscanner The scanner object. + */ + +char *ast_yyget_text (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yytext; +} + +/** Set the user-defined data. This data is never touched by the scanner. + * @param user_defined The data to be associated with this scanner. + * @param yyscanner The scanner object. + */ +void ast_yyset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyextra = user_defined ; +} + +/** Set the current line number. + * @param line_number + * @param yyscanner The scanner object. + */ +void ast_yyset_lineno (int line_number , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* lineno is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + yy_fatal_error( "ast_yyset_lineno called with no buffer" , yyscanner); + + yylineno = line_number; +} + +/** Set the current column. + * @param line_number + * @param yyscanner The scanner object. + */ +void ast_yyset_column (int column_no , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* column is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + yy_fatal_error( "ast_yyset_column called with no buffer" , yyscanner); + + yycolumn = column_no; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param in_str A readable stream. + * @param yyscanner The scanner object. + * @see ast_yy_switch_to_buffer + */ +void ast_yyset_in (FILE * in_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyin = in_str ; +} + +void ast_yyset_out (FILE * out_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyout = out_str ; +} + +int ast_yyget_debug (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yy_flex_debug; +} + +void ast_yyset_debug (int bdebug , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yy_flex_debug = bdebug ; +} + +/* Accessor methods for yylval and yylloc */ + +YYSTYPE * ast_yyget_lval (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylval; +} + +void ast_yyset_lval (YYSTYPE * yylval_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylval = yylval_param; +} + +YYLTYPE *ast_yyget_lloc (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylloc; +} + +void ast_yyset_lloc (YYLTYPE * yylloc_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylloc = yylloc_param; +} + +/* User-visible API */ + +/* ast_yylex_init is special because it creates the scanner itself, so it is + * the ONLY reentrant function that doesn't take the scanner as the last argument. + * That's why we explicitly handle the declaration, instead of using our macros. + */ + +int ast_yylex_init(yyscan_t* ptr_yy_globals) + +{ + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) ast_yyalloc ( sizeof( struct yyguts_t ), NULL ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + return yy_init_globals ( *ptr_yy_globals ); +} + +static int yy_init_globals (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from ast_yylex_destroy(), so don't allocate here. + */ + + yyg->yy_buffer_stack = 0; + yyg->yy_buffer_stack_top = 0; + yyg->yy_buffer_stack_max = 0; + yyg->yy_c_buf_p = (char *) 0; + yyg->yy_init = 0; + yyg->yy_start = 0; + + yyg->yy_start_stack_ptr = 0; + yyg->yy_start_stack_depth = 0; + yyg->yy_start_stack = NULL; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = (FILE *) 0; + yyout = (FILE *) 0; +#endif + + /* For future reference: Set errno on error, since we are called by + * ast_yylex_init() + */ + return 0; +} + +/* ast_yylex_destroy is for both reentrant and non-reentrant scanners. */ +int ast_yylex_destroy (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + ast_yy_delete_buffer(YY_CURRENT_BUFFER ,yyscanner ); + YY_CURRENT_BUFFER_LVALUE = NULL; + ast_yypop_buffer_state(yyscanner); + } + + /* Destroy the stack itself. */ + ast_yyfree(yyg->yy_buffer_stack ,yyscanner); + yyg->yy_buffer_stack = NULL; + + /* Destroy the start condition stack. */ + ast_yyfree(yyg->yy_start_stack ,yyscanner ); + yyg->yy_start_stack = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * ast_yylex() is called, initialization will occur. */ + yy_init_globals( yyscanner); + + /* Destroy the main struct (reentrant only). */ + ast_yyfree ( yyscanner , yyscanner ); + yyscanner = NULL; + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, yyconst char * s2, int n , yyscan_t yyscanner) +{ + register int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * s , yyscan_t yyscanner) +{ + register int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *ast_yyalloc (yy_size_t size , yyscan_t yyscanner) +{ + return (void *) malloc( size ); +} + +void *ast_yyrealloc (void * ptr, yy_size_t size , yyscan_t yyscanner) +{ + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return (void *) realloc( (char *) ptr, size ); +} + +void ast_yyfree (void * ptr , yyscan_t yyscanner) +{ + free( (char *) ptr ); /* see ast_yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#line 205 "ast_expr2.fl" + + + +/* I'm putting the interface routine to the whole parse here in the flexer input file + mainly because of all the flexer initialization that has to be done. Shouldn't matter + where it is, as long as it's somewhere. I didn't want to define a prototype for the + ast_yy_scan_string in the .y file, because then, I'd have to define YY_BUFFER_STATE there... + UGH! that would be inappropriate. */ + +int ast_yyparse(void *); /* need to/should define this prototype for the call to yyparse */ +int ast_yyerror(const char *, YYLTYPE *, struct parse_io *); /* likewise */ + +int ast_expr(char *expr, char *buf, int length) +{ + struct parse_io io; + int return_value = 0; + + memset(&io, 0, sizeof(io)); + io.string = expr; /* to pass to the error routine */ + + ast_yylex_init(&io.scanner); + + ast_yy_scan_string(expr, io.scanner); + + ast_yyparse ((void *) &io); + + ast_yylex_destroy(io.scanner); + + if (!io.val) { + if (length > 1) { + strcpy(buf, "0"); + return_value = 1; + } + } else { + if (io.val->type == AST_EXPR_integer) { + int res_length; + + res_length = snprintf(buf, length, "%ld", (long int) io.val->u.i); + return_value = (res_length <= length) ? res_length : length; + } else { +#ifdef STANDALONE + strncpy(buf, io.val->u.s, length - 1); +#else /* !STANDALONE */ + ast_copy_string(buf, io.val->u.s, length); +#endif /* STANDALONE */ + return_value = strlen(buf); + free(io.val->u.s); + } + free(io.val); + } + return return_value; +} + + +char extra_error_message[4095]; +int extra_error_message_supplied = 0; +void ast_expr_register_extra_error_info(char *message); +void ast_expr_clear_extra_error_info(void); + +void ast_expr_register_extra_error_info(char *message) +{ + extra_error_message_supplied=1; + strcpy(extra_error_message, message); +} + +void ast_expr_clear_extra_error_info(void) +{ + extra_error_message_supplied=0; + extra_error_message[0] = 0; +} + +static char *expr2_token_equivs1[] = +{ + "TOKEN", + "TOK_COND", + "TOK_COLONCOLON", + "TOK_OR", + "TOK_AND", + "TOK_EQ", + "TOK_GT", + "TOK_LT", + "TOK_GE", + "TOK_LE", + "TOK_NE", + "TOK_PLUS", + "TOK_MINUS", + "TOK_MULT", + "TOK_DIV", + "TOK_MOD", + "TOK_COMPL", + "TOK_COLON", + "TOK_EQTILDE", + "TOK_RP", + "TOK_LP" +}; + +static char *expr2_token_equivs2[] = +{ + "", + "?", + "::", + "|", + "&", + "=", + ">", + "<", + ">=", + "<=", + "!=", + "+", + "-", + "*", + "/", + "%", + "!", + ":", + "=~", + ")", + "(" +}; + + +static char *expr2_token_subst(char *mess) +{ + /* calc a length, malloc, fill, and return; yyerror had better free it! */ + int len=0,i; + char *p; + char *res, *s,*t; + int expr2_token_equivs_entries = sizeof(expr2_token_equivs1)/sizeof(char*); + + for (p=mess; *p; p++) { + for (i=0; iscanner); + char spacebuf[8000]; /* best safe than sorry */ + char spacebuf2[8000]; /* best safe than sorry */ + int i=0; + char *s2 = expr2_token_subst((char *)s); + spacebuf[0] = 0; + + for(i=0;i< (int)(yytext - YY_CURRENT_BUFFER_LVALUE->yy_ch_buf);i++) spacebuf2[i] = ' '; /* uh... assuming yyg is defined, then I can use the yycolumn macro, + which is the same thing as... get this: + yyg->yy_buffer_stack[yyg->yy_buffer_stack_top]->yy_bs_column + I was tempted to just use yy_buf_pos in the STATE, but..., well: + a. the yy_buf_pos is the current position in the buffer, which + may not relate to the entire string/buffer because of the + buffering. + b. but, analysis of the situation is that when you use the + ast_yy_scan_string func, it creates a single buffer the size of + string, so the two would be the same... + so, in the end, the yycolumn macro is available, shorter, therefore easier. */ + spacebuf2[i++]='^'; + spacebuf2[i]= 0; + +#ifdef STANDALONE3 + /* easier to read in the standalone version */ + printf("ast_yyerror(): %s syntax error: %s; Input:\n%s\n%s\n", + (extra_error_message_supplied?extra_error_message:""), s2, parseio->string,spacebuf2); +#else + ast_log(LOG_WARNING,"ast_yyerror(): %s syntax error: %s; Input:\n%s\n%s\n", + (extra_error_message_supplied?extra_error_message:""), s2, parseio->string,spacebuf2); +#endif +#ifndef STANDALONE + ast_log(LOG_WARNING,"If you have questions, please refer to doc/channelvariables.txt in the asterisk source.\n"); +#endif + free(s2); + return(0); +} + diff --git a/main/asterisk.c b/main/asterisk.c new file mode 100644 index 000000000..84df3b024 --- /dev/null +++ b/main/asterisk.c @@ -0,0 +1,2727 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + + +/* Doxygenified Copyright Header */ +/*! + * \mainpage Asterisk -- An Open Source Telephony Toolkit + * + * \par Developer Documentation for Asterisk + * This is the main developer documentation for Asterisk. It is + * generated by running "make progdocs". + * \par Additional documentation + * \arg \ref DevDoc + * \arg \ref ConfigFiles + * + * \section copyright Copyright and author + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * Asterisk is a trade mark registered by Digium, Inc. + * + * \author Mark Spencer + * Also see \ref AstCREDITS + * + * \section license License + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + * + * \verbinclude LICENSE + * + */ + +/*! \file + \brief Top level source file for Asterisk - the Open Source PBX. Implementation + of PBX core functions and CLI interface. + + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef linux +#include +#endif +#include + +#ifdef linux +#include +#endif + +#if defined(__FreeBSD__) || defined( __NetBSD__ ) || defined(SOLARIS) +#include +#if defined(SOLARIS) +int daemon(int, int); /* defined in libresolv of all places */ +#endif +#endif + +#include "asterisk/logger.h" +#include "asterisk/options.h" +#include "asterisk/cli.h" +#include "asterisk/channel.h" +#include "asterisk/ulaw.h" +#include "asterisk/alaw.h" +#include "asterisk/callerid.h" +#include "asterisk/image.h" +#include "asterisk/tdd.h" +#include "asterisk/term.h" +#include "asterisk/manager.h" +#include "asterisk/cdr.h" +#include "asterisk/pbx.h" +#include "asterisk/enum.h" +#include "asterisk/rtp.h" +#include "asterisk/http.h" +#include "asterisk/udptl.h" +#include "asterisk/app.h" +#include "asterisk/lock.h" +#include "asterisk/utils.h" +#include "asterisk/file.h" +#include "asterisk/io.h" +#include "asterisk/lock.h" +#include "editline/histedit.h" +#include "asterisk/config.h" +#include "asterisk/version.h" +#include "asterisk/linkedlists.h" +#include "asterisk/devicestate.h" + +#include "asterisk/doxyref.h" /* Doxygen documentation */ + +#include "../defaults.h" + +#ifndef AF_LOCAL +#define AF_LOCAL AF_UNIX +#define PF_LOCAL PF_UNIX +#endif + +#define AST_MAX_CONNECTS 128 +#define NUM_MSGS 64 + +/*! \brief Welcome message when starting a CLI interface */ +#define WELCOME_MESSAGE \ + ast_verbose("Asterisk " ASTERISK_VERSION ", Copyright (C) 1999 - 2006 Digium, Inc. and others.\n"); \ + ast_verbose("Created by Mark Spencer \n"); \ + ast_verbose("Asterisk comes with ABSOLUTELY NO WARRANTY; type 'show warranty' for details.\n"); \ + ast_verbose("This is free software, with components licensed under the GNU General Public\n"); \ + ast_verbose("License version 2 and other licenses; you are welcome to redistribute it under\n"); \ + ast_verbose("certain conditions. Type 'show license' for details.\n"); \ + ast_verbose("=========================================================================\n") + +/*! \defgroup main_options + \brief Main configuration options from \ref Config_ast "asterisk.conf" or + the operating system command line when starting Asterisk + Some of them can be changed in the CLI + */ +/*! @{ */ + +extern int ast_language_is_prefix; /* XXX move to some header */ + +struct ast_flags ast_options = { AST_DEFAULT_OPTIONS }; + +int option_verbose = 0; /*!< Verbosity level */ +int option_debug = 0; /*!< Debug level */ + +double option_maxload = 0.0; /*!< Max load avg on system */ +int option_maxcalls = 0; /*!< Max number of active calls */ + +/*! @} */ + +char record_cache_dir[AST_CACHE_DIR_LEN] = AST_TMP_DIR; +char debug_filename[AST_FILENAME_MAX] = ""; + +static int ast_socket = -1; /*!< UNIX Socket for allowing remote control */ +static int ast_consock = -1; /*!< UNIX Socket for controlling another asterisk */ +pid_t ast_mainpid; +struct console { + int fd; /*!< File descriptor */ + int p[2]; /*!< Pipe */ + pthread_t t; /*!< Thread of handler */ + int mute; /*!< Is the console muted for logs */ +}; + +struct ast_atexit { + void (*func)(void); + AST_LIST_ENTRY(ast_atexit) list; +}; + +static AST_LIST_HEAD_STATIC(atexits, ast_atexit); + +time_t ast_startuptime; +time_t ast_lastreloadtime; + +static History *el_hist = NULL; +static EditLine *el = NULL; +static char *remotehostname; + +struct console consoles[AST_MAX_CONNECTS]; + +char defaultlanguage[MAX_LANGUAGE] = DEFAULT_LANGUAGE; + +static int ast_el_add_history(char *); +static int ast_el_read_history(char *); +static int ast_el_write_history(char *); + +char ast_config_AST_CONFIG_DIR[PATH_MAX]; +char ast_config_AST_CONFIG_FILE[PATH_MAX]; +char ast_config_AST_MODULE_DIR[PATH_MAX]; +char ast_config_AST_SPOOL_DIR[PATH_MAX]; +char ast_config_AST_MONITOR_DIR[PATH_MAX]; +char ast_config_AST_VAR_DIR[PATH_MAX]; +char ast_config_AST_DATA_DIR[PATH_MAX]; +char ast_config_AST_LOG_DIR[PATH_MAX]; +char ast_config_AST_AGI_DIR[PATH_MAX]; +char ast_config_AST_DB[PATH_MAX]; +char ast_config_AST_KEY_DIR[PATH_MAX]; +char ast_config_AST_PID[PATH_MAX]; +char ast_config_AST_SOCKET[PATH_MAX]; +char ast_config_AST_RUN_DIR[PATH_MAX]; +char ast_config_AST_RUN_USER[PATH_MAX]; +char ast_config_AST_RUN_GROUP[PATH_MAX]; +char ast_config_AST_CTL_PERMISSIONS[PATH_MAX]; +char ast_config_AST_CTL_OWNER[PATH_MAX] = "\0"; +char ast_config_AST_CTL_GROUP[PATH_MAX] = "\0"; +char ast_config_AST_CTL[PATH_MAX] = "asterisk.ctl"; +char ast_config_AST_SYSTEM_NAME[20] = ""; + +extern const char *ast_build_hostname; +extern const char *ast_build_kernel; +extern const char *ast_build_machine; +extern const char *ast_build_os; +extern const char *ast_build_date; +extern const char *ast_build_user; + +static char *_argv[256]; +static int shuttingdown = 0; +static int restartnow = 0; +static pthread_t consolethread = AST_PTHREADT_NULL; + +static char randompool[256]; + +#if !defined(LOW_MEMORY) +struct file_version { + AST_LIST_ENTRY(file_version) list; + const char *file; + char *version; +}; + +static AST_LIST_HEAD_STATIC(file_versions, file_version); + +void ast_register_file_version(const char *file, const char *version) +{ + struct file_version *new; + char *work; + size_t version_length; + + work = ast_strdupa(version); + work = ast_strip(ast_strip_quoted(work, "$", "$")); + version_length = strlen(work) + 1; + + if (!(new = ast_calloc(1, sizeof(*new) + version_length))) + return; + + new->file = file; + new->version = (char *) new + sizeof(*new); + memcpy(new->version, work, version_length); + AST_LIST_LOCK(&file_versions); + AST_LIST_INSERT_HEAD(&file_versions, new, list); + AST_LIST_UNLOCK(&file_versions); +} + +void ast_unregister_file_version(const char *file) +{ + struct file_version *find; + + AST_LIST_LOCK(&file_versions); + AST_LIST_TRAVERSE_SAFE_BEGIN(&file_versions, find, list) { + if (!strcasecmp(find->file, file)) { + AST_LIST_REMOVE_CURRENT(&file_versions, list); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&file_versions); + if (find) + free(find); +} + +struct thread_list_t { + AST_LIST_ENTRY(thread_list_t) list; + char *name; + pthread_t id; +}; + +static AST_LIST_HEAD_STATIC(thread_list, thread_list_t); + +static char show_threads_help[] = +"Usage: show threads\n" +" List threads currently active in the system.\n"; + +void ast_register_thread(char *name) +{ + struct thread_list_t *new = ast_calloc(1, sizeof(*new)); + + if (!new) + return; + new->id = pthread_self(); + new->name = name; /* this was a copy already */ + AST_LIST_LOCK(&thread_list); + AST_LIST_INSERT_HEAD(&thread_list, new, list); + AST_LIST_UNLOCK(&thread_list); +} + +void ast_unregister_thread(void *id) +{ + struct thread_list_t *x; + + AST_LIST_LOCK(&thread_list); + AST_LIST_TRAVERSE_SAFE_BEGIN(&thread_list, x, list) { + if ((void *)x->id == id) { + AST_LIST_REMOVE_CURRENT(&thread_list, list); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&thread_list); + if (x) { + free(x->name); + free(x); + } +} + +static int handle_show_threads(int fd, int argc, char *argv[]) +{ + int count = 0; + struct thread_list_t *cur; + + AST_LIST_LOCK(&thread_list); + AST_LIST_TRAVERSE(&thread_list, cur, list) { + ast_cli(fd, "%p %s\n", (void *)cur->id, cur->name); + count++; + } + AST_LIST_UNLOCK(&thread_list); + ast_cli(fd, "%d threads listed.\n", count); + return 0; +} + +struct profile_entry { + const char *name; + uint64_t scale; /* if non-zero, values are scaled by this */ + int64_t mark; + int64_t value; + int64_t events; +}; + +struct profile_data { + int entries; + int max_size; + struct profile_entry e[0]; +}; + +static struct profile_data *prof_data; + +/*! \brief allocates a counter with a given name and scale. + * \return Returns the identifier of the counter. + */ +int ast_add_profile(const char *name, uint64_t scale) +{ + int l = sizeof(struct profile_data); + int n = 10; /* default entries */ + + if (prof_data == NULL) { + prof_data = ast_calloc(1, l + n*sizeof(struct profile_entry)); + if (prof_data == NULL) + return -1; + prof_data->entries = 0; + prof_data->max_size = n; + } + if (prof_data->entries >= prof_data->max_size) { + void *p; + n = prof_data->max_size + 20; + p = ast_realloc(prof_data, l + n*sizeof(struct profile_entry)); + if (p == NULL) + return -1; + prof_data = p; + prof_data->max_size = n; + } + n = prof_data->entries++; + prof_data->e[n].name = ast_strdup(name); + prof_data->e[n].value = 0; + prof_data->e[n].events = 0; + prof_data->e[n].mark = 0; + prof_data->e[n].scale = scale; + return n; +} + +int64_t ast_profile(int i, int64_t delta) +{ + if (!prof_data || i < 0 || i > prof_data->entries) /* invalid index */ + return 0; + if (prof_data->e[i].scale > 1) + delta /= prof_data->e[i].scale; + prof_data->e[i].value += delta; + prof_data->e[i].events++; + return prof_data->e[i].value; +} + +#if defined ( __i386__) && (defined(__FreeBSD__) || defined(linux)) +#if defined(__FreeBSD__) +#include +#elif defined(linux) +static __inline u_int64_t +rdtsc(void) +{ + uint64_t rv; + + __asm __volatile(".byte 0x0f, 0x31" : "=A" (rv)); + return (rv); +} +#endif +#else /* supply a dummy function on other platforms */ +static __inline u_int64_t +rdtsc(void) +{ + return 0; +} +#endif + +int64_t ast_mark(int i, int startstop) +{ + if (!prof_data || i < 0 || i > prof_data->entries) /* invalid index */ + return 0; + if (startstop == 1) + prof_data->e[i].mark = rdtsc(); + else { + prof_data->e[i].mark = (rdtsc() - prof_data->e[i].mark); + if (prof_data->e[i].scale > 1) + prof_data->e[i].mark /= prof_data->e[i].scale; + prof_data->e[i].value += prof_data->e[i].mark; + prof_data->e[i].events++; + } + return prof_data->e[i].mark; +} + +static int handle_show_profile(int fd, int argc, char *argv[]) +{ + int i, min, max; + char *search = NULL; + + if (prof_data == NULL) + return 0; + + min = 0; + max = prof_data->entries; + if (argc >= 3) { /* specific entries */ + if (isdigit(argv[2][0])) { + min = atoi(argv[2]); + if (argc == 4 && strcmp(argv[3], "-")) + max = atoi(argv[3]); + } else + search = argv[2]; + } + if (max > prof_data->entries) + max = prof_data->entries; + if (!strcmp(argv[0], "clear")) { + for (i= min; i < max; i++) { + if (!search || strstr(prof_data->e[i].name, search)) { + prof_data->e[i].value = 0; + prof_data->e[i].events = 0; + } + } + return 0; + } + ast_cli(fd, "profile values (%d, allocated %d)\n-------------------\n", + prof_data->entries, prof_data->max_size); + for (i = min; i < max; i++) { + struct profile_entry *e = &prof_data->e[i]; + if (!search || strstr(prof_data->e[i].name, search)) + ast_cli(fd, "%6d: [%8ld] %10ld %12lld %12lld %s\n", + i, + (long)e->scale, + (long)e->events, (long long)e->value, + (long long)(e->events ? e->value / e->events : e->value), + e->name); + } + return 0; +} + +static char show_version_files_help[] = +"Usage: show version files [like ]\n" +" Shows the revision numbers of the files used to build this copy of Asterisk.\n" +" Optional regular expression pattern is used to filter the file list.\n"; + +/*! \brief CLI command to list module versions */ +static int handle_show_version_files(int fd, int argc, char *argv[]) +{ +#define FORMAT "%-25.25s %-40.40s\n" + struct file_version *iterator; + regex_t regexbuf; + int havepattern = 0; + int havename = 0; + int count_files = 0; + + switch (argc) { + case 5: + if (!strcasecmp(argv[3], "like")) { + if (regcomp(®exbuf, argv[4], REG_EXTENDED | REG_NOSUB)) + return RESULT_SHOWUSAGE; + havepattern = 1; + } else + return RESULT_SHOWUSAGE; + break; + case 4: + havename = 1; + break; + case 3: + break; + default: + return RESULT_SHOWUSAGE; + } + + ast_cli(fd, FORMAT, "File", "Revision"); + ast_cli(fd, FORMAT, "----", "--------"); + AST_LIST_LOCK(&file_versions); + AST_LIST_TRAVERSE(&file_versions, iterator, list) { + if (havename && strcasecmp(iterator->file, argv[3])) + continue; + + if (havepattern && regexec(®exbuf, iterator->file, 0, NULL, 0)) + continue; + + ast_cli(fd, FORMAT, iterator->file, iterator->version); + count_files++; + if (havename) + break; + } + AST_LIST_UNLOCK(&file_versions); + if (!havename) { + ast_cli(fd, "%d files listed.\n", count_files); + } + + if (havepattern) + regfree(®exbuf); + + return RESULT_SUCCESS; +#undef FORMAT +} + +static char *complete_show_version_files(const char *line, const char *word, int pos, int state) +{ + struct file_version *find; + int which = 0; + char *ret = NULL; + int matchlen = strlen(word); + + if (pos != 3) + return NULL; + + AST_LIST_LOCK(&file_versions); + AST_LIST_TRAVERSE(&file_versions, find, list) { + if (!strncasecmp(word, find->file, matchlen) && ++which > state) { + ret = ast_strdup(find->file); + break; + } + } + AST_LIST_UNLOCK(&file_versions); + + return ret; +} +#endif /* ! LOW_MEMORY */ + +int ast_register_atexit(void (*func)(void)) +{ + int res = -1; + struct ast_atexit *ae; + ast_unregister_atexit(func); + AST_LIST_LOCK(&atexits); + if ((ae = ast_calloc(1, sizeof(*ae)))) { + AST_LIST_INSERT_HEAD(&atexits, ae, list); + ae->func = func; + res = 0; + } + AST_LIST_UNLOCK(&atexits); + return res; +} + +void ast_unregister_atexit(void (*func)(void)) +{ + struct ast_atexit *ae; + AST_LIST_LOCK(&atexits); + AST_LIST_TRAVERSE_SAFE_BEGIN(&atexits, ae, list) { + if (ae->func == func) { + AST_LIST_REMOVE_CURRENT(&atexits, list); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + AST_LIST_UNLOCK(&atexits); +} + +static int fdprint(int fd, const char *s) +{ + return write(fd, s, strlen(s) + 1); +} + +/*! \brief NULL handler so we can collect the child exit status */ +static void null_sig_handler(int signal) +{ + +} + +AST_MUTEX_DEFINE_STATIC(safe_system_lock); +/*! \brief Keep track of how many threads are currently trying to wait*() on + * a child process */ +static unsigned int safe_system_level = 0; +static void *safe_system_prev_handler; + +void ast_replace_sigchld(void) +{ + unsigned int level; + + ast_mutex_lock(&safe_system_lock); + level = safe_system_level++; + + /* only replace the handler if it has not already been done */ + if (level == 0) + safe_system_prev_handler = signal(SIGCHLD, null_sig_handler); + + ast_mutex_unlock(&safe_system_lock); +} + +void ast_unreplace_sigchld(void) +{ + unsigned int level; + + ast_mutex_lock(&safe_system_lock); + level = --safe_system_level; + + /* only restore the handler if we are the last one */ + if (level == 0) + signal(SIGCHLD, safe_system_prev_handler); + + ast_mutex_unlock(&safe_system_lock); +} + +int ast_safe_system(const char *s) +{ + pid_t pid; + int x; + int res; + struct rusage rusage; + int status; + + ast_replace_sigchld(); + + pid = fork(); + + if (pid == 0) { + if (ast_opt_high_priority) + ast_set_priority(0); + /* Close file descriptors and launch system command */ + for (x = STDERR_FILENO + 1; x < 4096; x++) + close(x); + execl("/bin/sh", "/bin/sh", "-c", s, (char *) NULL); + exit(1); + } else if (pid > 0) { + for(;;) { + res = wait4(pid, &status, 0, &rusage); + if (res > -1) { + res = WIFEXITED(status) ? WEXITSTATUS(status) : -1; + break; + } else if (errno != EINTR) + break; + } + } else { + ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno)); + res = -1; + } + + ast_unreplace_sigchld(); + + return res; +} + +/*! + * \brief mute or unmute a console from logging + */ +void ast_console_toggle_mute(int fd) { + int x; + for (x = 0;x < AST_MAX_CONNECTS; x++) { + if (fd == consoles[x].fd) { + if (consoles[x].mute) { + consoles[x].mute = 0; + ast_cli(fd, "Console is not muted anymore.\n"); + } else { + consoles[x].mute = 1; + ast_cli(fd, "Console is muted.\n"); + } + return; + } + } + ast_cli(fd, "Couldn't find remote console.\n"); +} + +/*! + * \brief log the string to all attached console clients + */ +static void ast_network_puts_mutable(const char *string) +{ + int x; + for (x = 0;x < AST_MAX_CONNECTS; x++) { + if (consoles[x].mute) + continue; + if (consoles[x].fd > -1) + fdprint(consoles[x].p[1], string); + } +} + +/*! + * \brief log the string to the console, and all attached + * console clients + */ +void ast_console_puts_mutable(const char *string) +{ + fputs(string, stdout); + fflush(stdout); + ast_network_puts_mutable(string); +} + +/*! + * \brief write the string to all attached console clients + */ +static void ast_network_puts(const char *string) +{ + int x; + for (x=0; x < AST_MAX_CONNECTS; x++) { + if (consoles[x].fd > -1) + fdprint(consoles[x].p[1], string); + } +} + +/*! + * write the string to the console, and all attached + * console clients + */ +void ast_console_puts(const char *string) +{ + fputs(string, stdout); + fflush(stdout); + ast_network_puts(string); +} + +static void network_verboser(const char *s) +{ + ast_network_puts_mutable(s); +} + +static pthread_t lthread; + +static void *netconsole(void *vconsole) +{ + struct console *con = vconsole; + char hostname[MAXHOSTNAMELEN] = ""; + char tmp[512]; + int res; + struct pollfd fds[2]; + + if (gethostname(hostname, sizeof(hostname)-1)) + ast_copy_string(hostname, "", sizeof(hostname)); + snprintf(tmp, sizeof(tmp), "%s/%ld/%s\n", hostname, (long)ast_mainpid, ASTERISK_VERSION); + fdprint(con->fd, tmp); + for(;;) { + fds[0].fd = con->fd; + fds[0].events = POLLIN; + fds[0].revents = 0; + fds[1].fd = con->p[0]; + fds[1].events = POLLIN; + fds[1].revents = 0; + + res = poll(fds, 2, -1); + if (res < 0) { + if (errno != EINTR) + ast_log(LOG_WARNING, "poll returned < 0: %s\n", strerror(errno)); + continue; + } + if (fds[0].revents) { + res = read(con->fd, tmp, sizeof(tmp)); + if (res < 1) { + break; + } + tmp[res] = 0; + ast_cli_command(con->fd, tmp); + } + if (fds[1].revents) { + res = read(con->p[0], tmp, sizeof(tmp)); + if (res < 1) { + ast_log(LOG_ERROR, "read returned %d\n", res); + break; + } + res = write(con->fd, tmp, res); + if (res < 1) + break; + } + } + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Remote UNIX connection disconnected\n"); + close(con->fd); + close(con->p[0]); + close(con->p[1]); + con->fd = -1; + + return NULL; +} + +static void *listener(void *unused) +{ + struct sockaddr_un sunaddr; + int s; + socklen_t len; + int x; + int flags; + struct pollfd fds[1]; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + for (;;) { + if (ast_socket < 0) + return NULL; + fds[0].fd = ast_socket; + fds[0].events = POLLIN; + s = poll(fds, 1, -1); + pthread_testcancel(); + if (s < 0) { + if (errno != EINTR) + ast_log(LOG_WARNING, "poll returned error: %s\n", strerror(errno)); + continue; + } + len = sizeof(sunaddr); + s = accept(ast_socket, (struct sockaddr *)&sunaddr, &len); + if (s < 0) { + if (errno != EINTR) + ast_log(LOG_WARNING, "Accept returned %d: %s\n", s, strerror(errno)); + } else { + for (x = 0; x < AST_MAX_CONNECTS; x++) { + if (consoles[x].fd < 0) { + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, consoles[x].p)) { + ast_log(LOG_ERROR, "Unable to create pipe: %s\n", strerror(errno)); + consoles[x].fd = -1; + fdprint(s, "Server failed to create pipe\n"); + close(s); + break; + } + flags = fcntl(consoles[x].p[1], F_GETFL); + fcntl(consoles[x].p[1], F_SETFL, flags | O_NONBLOCK); + consoles[x].fd = s; + consoles[x].mute = ast_opt_mute; + if (ast_pthread_create(&consoles[x].t, &attr, netconsole, &consoles[x])) { + ast_log(LOG_ERROR, "Unable to spawn thread to handle connection: %s\n", strerror(errno)); + close(consoles[x].p[0]); + close(consoles[x].p[1]); + consoles[x].fd = -1; + fdprint(s, "Server failed to spawn thread\n"); + close(s); + } + break; + } + } + if (x >= AST_MAX_CONNECTS) { + fdprint(s, "No more connections allowed\n"); + ast_log(LOG_WARNING, "No more connections allowed\n"); + close(s); + } else if (consoles[x].fd > -1) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Remote UNIX connection\n"); + } + } + } + return NULL; +} + +static int ast_makesocket(void) +{ + struct sockaddr_un sunaddr; + int res; + int x; + uid_t uid = -1; + gid_t gid = -1; + + for (x = 0; x < AST_MAX_CONNECTS; x++) + consoles[x].fd = -1; + unlink(ast_config_AST_SOCKET); + ast_socket = socket(PF_LOCAL, SOCK_STREAM, 0); + if (ast_socket < 0) { + ast_log(LOG_WARNING, "Unable to create control socket: %s\n", strerror(errno)); + return -1; + } + memset(&sunaddr, 0, sizeof(sunaddr)); + sunaddr.sun_family = AF_LOCAL; + ast_copy_string(sunaddr.sun_path, ast_config_AST_SOCKET, sizeof(sunaddr.sun_path)); + res = bind(ast_socket, (struct sockaddr *)&sunaddr, sizeof(sunaddr)); + if (res) { + ast_log(LOG_WARNING, "Unable to bind socket to %s: %s\n", ast_config_AST_SOCKET, strerror(errno)); + close(ast_socket); + ast_socket = -1; + return -1; + } + res = listen(ast_socket, 2); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to listen on socket %s: %s\n", ast_config_AST_SOCKET, strerror(errno)); + close(ast_socket); + ast_socket = -1; + return -1; + } + ast_register_verbose(network_verboser); + ast_pthread_create(<hread, NULL, listener, NULL); + + if (!ast_strlen_zero(ast_config_AST_CTL_OWNER)) { + struct passwd *pw; + if ((pw = getpwnam(ast_config_AST_CTL_OWNER)) == NULL) { + ast_log(LOG_WARNING, "Unable to find uid of user %s\n", ast_config_AST_CTL_OWNER); + } else { + uid = pw->pw_uid; + } + } + + if (!ast_strlen_zero(ast_config_AST_CTL_GROUP)) { + struct group *grp; + if ((grp = getgrnam(ast_config_AST_CTL_GROUP)) == NULL) { + ast_log(LOG_WARNING, "Unable to find gid of group %s\n", ast_config_AST_CTL_GROUP); + } else { + gid = grp->gr_gid; + } + } + + if (chown(ast_config_AST_SOCKET, uid, gid) < 0) + ast_log(LOG_WARNING, "Unable to change ownership of %s: %s\n", ast_config_AST_SOCKET, strerror(errno)); + + if (!ast_strlen_zero(ast_config_AST_CTL_PERMISSIONS)) { + int p1; + mode_t p; + sscanf(ast_config_AST_CTL_PERMISSIONS, "%o", &p1); + p = p1; + if ((chmod(ast_config_AST_SOCKET, p)) < 0) + ast_log(LOG_WARNING, "Unable to change file permissions of %s: %s\n", ast_config_AST_SOCKET, strerror(errno)); + } + + return 0; +} + +static int ast_tryconnect(void) +{ + struct sockaddr_un sunaddr; + int res; + ast_consock = socket(PF_LOCAL, SOCK_STREAM, 0); + if (ast_consock < 0) { + ast_log(LOG_WARNING, "Unable to create socket: %s\n", strerror(errno)); + return 0; + } + memset(&sunaddr, 0, sizeof(sunaddr)); + sunaddr.sun_family = AF_LOCAL; + ast_copy_string(sunaddr.sun_path, (char *)ast_config_AST_SOCKET, sizeof(sunaddr.sun_path)); + res = connect(ast_consock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)); + if (res) { + close(ast_consock); + ast_consock = -1; + return 0; + } else + return 1; +} + +/*! \brief Urgent handler + + Called by soft_hangup to interrupt the poll, read, or other + system call. We don't actually need to do anything though. + Remember: Cannot EVER ast_log from within a signal handler + */ +static void urg_handler(int num) +{ + signal(num, urg_handler); + return; +} + +static void hup_handler(int num) +{ + if (option_verbose > 1) + printf("Received HUP signal -- Reloading configs\n"); + if (restartnow) + execvp(_argv[0], _argv); + /* XXX This could deadlock XXX */ + ast_module_reload(NULL); + signal(num, hup_handler); +} + +static void child_handler(int sig) +{ + /* Must not ever ast_log or ast_verbose within signal handler */ + int n, status; + + /* + * Reap all dead children -- not just one + */ + for (n = 0; wait4(-1, &status, WNOHANG, NULL) > 0; n++) + ; + if (n == 0 && option_debug) + printf("Huh? Child handler, but nobody there?\n"); + signal(sig, child_handler); +} + +/*! \brief Set an X-term or screen title */ +static void set_title(char *text) +{ + if (getenv("TERM") && strstr(getenv("TERM"), "xterm")) + fprintf(stdout, "\033]2;%s\007", text); +} + +static void set_icon(char *text) +{ + if (getenv("TERM") && strstr(getenv("TERM"), "xterm")) + fprintf(stdout, "\033]1;%s\007", text); +} + +/*! \brief We set ourselves to a high priority, that we might pre-empt everything + else. If your PBX has heavy activity on it, this is a good thing. */ +int ast_set_priority(int pri) +{ + struct sched_param sched; + memset(&sched, 0, sizeof(sched)); +#ifdef __linux__ + if (pri) { + sched.sched_priority = 10; + if (sched_setscheduler(0, SCHED_RR, &sched)) { + ast_log(LOG_WARNING, "Unable to set high priority\n"); + return -1; + } else + if (option_verbose) + ast_verbose("Set to realtime thread\n"); + } else { + sched.sched_priority = 0; + if (sched_setscheduler(0, SCHED_OTHER, &sched)) { + ast_log(LOG_WARNING, "Unable to set normal priority\n"); + return -1; + } + } +#else + if (pri) { + if (setpriority(PRIO_PROCESS, 0, -10) == -1) { + ast_log(LOG_WARNING, "Unable to set high priority\n"); + return -1; + } else + if (option_verbose) + ast_verbose("Set to high priority\n"); + } else { + if (setpriority(PRIO_PROCESS, 0, 0) == -1) { + ast_log(LOG_WARNING, "Unable to set normal priority\n"); + return -1; + } + } +#endif + return 0; +} + +static void ast_run_atexits(void) +{ + struct ast_atexit *ae; + AST_LIST_LOCK(&atexits); + AST_LIST_TRAVERSE(&atexits, ae, list) { + if (ae->func) + ae->func(); + } + AST_LIST_UNLOCK(&atexits); +} + +static void quit_handler(int num, int nice, int safeshutdown, int restart) +{ + char filename[80] = ""; + time_t s,e; + int x; + /* Try to get as many CDRs as possible submitted to the backend engines (if in batch mode) */ + ast_cdr_engine_term(); + if (safeshutdown) { + shuttingdown = 1; + if (!nice) { + /* Begin shutdown routine, hanging up active channels */ + ast_begin_shutdown(1); + if (option_verbose && ast_opt_console) + ast_verbose("Beginning asterisk %s....\n", restart ? "restart" : "shutdown"); + time(&s); + for (;;) { + time(&e); + /* Wait up to 15 seconds for all channels to go away */ + if ((e - s) > 15) + break; + if (!ast_active_channels()) + break; + if (!shuttingdown) + break; + /* Sleep 1/10 of a second */ + usleep(100000); + } + } else { + if (nice < 2) + ast_begin_shutdown(0); + if (option_verbose && ast_opt_console) + ast_verbose("Waiting for inactivity to perform %s...\n", restart ? "restart" : "halt"); + for (;;) { + if (!ast_active_channels()) + break; + if (!shuttingdown) + break; + sleep(1); + } + } + + if (!shuttingdown) { + if (option_verbose && ast_opt_console) + ast_verbose("Asterisk %s cancelled.\n", restart ? "restart" : "shutdown"); + return; + } + } + if (ast_opt_console || ast_opt_remote) { + if (getenv("HOME")) + snprintf(filename, sizeof(filename), "%s/.asterisk_history", getenv("HOME")); + if (!ast_strlen_zero(filename)) + ast_el_write_history(filename); + if (el != NULL) + el_end(el); + if (el_hist != NULL) + history_end(el_hist); + } + if (option_verbose) + ast_verbose("Executing last minute cleanups\n"); + ast_run_atexits(); + /* Called on exit */ + if (option_verbose && ast_opt_console) + ast_verbose("Asterisk %s ending (%d).\n", ast_active_channels() ? "uncleanly" : "cleanly", num); + else if (option_debug) + ast_log(LOG_DEBUG, "Asterisk ending (%d).\n", num); + manager_event(EVENT_FLAG_SYSTEM, "Shutdown", "Shutdown: %s\r\nRestart: %s\r\n", ast_active_channels() ? "Uncleanly" : "Cleanly", restart ? "True" : "False"); + if (ast_socket > -1) { + pthread_cancel(lthread); + close(ast_socket); + ast_socket = -1; + unlink(ast_config_AST_SOCKET); + } + if (ast_consock > -1) + close(ast_consock); + if (!ast_opt_remote) + unlink(ast_config_AST_PID); + printf(term_quit()); + if (restart) { + if (option_verbose || ast_opt_console) + ast_verbose("Preparing for Asterisk restart...\n"); + /* Mark all FD's for closing on exec */ + for (x=3; x < 32768; x++) { + fcntl(x, F_SETFD, FD_CLOEXEC); + } + if (option_verbose || ast_opt_console) + ast_verbose("Restarting Asterisk NOW...\n"); + restartnow = 1; + + /* close logger */ + close_logger(); + + /* If there is a consolethread running send it a SIGHUP + so it can execvp, otherwise we can do it ourselves */ + if ((consolethread != AST_PTHREADT_NULL) && (consolethread != pthread_self())) { + pthread_kill(consolethread, SIGHUP); + /* Give the signal handler some time to complete */ + sleep(2); + } else + execvp(_argv[0], _argv); + + } else { + /* close logger */ + close_logger(); + } + exit(0); +} + +static void __quit_handler(int num) +{ + quit_handler(num, 0, 1, 0); +} + +static const char *fix_header(char *outbuf, int maxout, const char *s, char *cmp) +{ + const char *c; + if (!strncmp(s, cmp, strlen(cmp))) { + c = s + strlen(cmp); + term_color(outbuf, cmp, COLOR_GRAY, 0, maxout); + return c; + } + return NULL; +} + +static void console_verboser(const char *s) +{ + char tmp[80]; + const char *c = NULL; + + if ((c = fix_header(tmp, sizeof(tmp), s, VERBOSE_PREFIX_4)) || + (c = fix_header(tmp, sizeof(tmp), s, VERBOSE_PREFIX_3)) || + (c = fix_header(tmp, sizeof(tmp), s, VERBOSE_PREFIX_2)) || + (c = fix_header(tmp, sizeof(tmp), s, VERBOSE_PREFIX_1))) { + fputs(tmp, stdout); + fputs(c, stdout); + } else + fputs(s, stdout); + + fflush(stdout); + + /* Wake up a poll()ing console */ + if (ast_opt_console && consolethread != AST_PTHREADT_NULL) + pthread_kill(consolethread, SIGURG); +} + +static int ast_all_zeros(char *s) +{ + while (*s) { + if (*s > 32) + return 0; + s++; + } + return 1; +} + +static void consolehandler(char *s) +{ + printf(term_end()); + fflush(stdout); + + /* Called when readline data is available */ + if (!ast_all_zeros(s)) + ast_el_add_history(s); + /* The real handler for bang */ + if (s[0] == '!') { + if (s[1]) + ast_safe_system(s+1); + else + ast_safe_system(getenv("SHELL") ? getenv("SHELL") : "/bin/sh"); + } else + ast_cli_command(STDOUT_FILENO, s); +} + +static int remoteconsolehandler(char *s) +{ + int ret = 0; + + /* Called when readline data is available */ + if (!ast_all_zeros(s)) + ast_el_add_history(s); + /* The real handler for bang */ + if (s[0] == '!') { + if (s[1]) + ast_safe_system(s+1); + else + ast_safe_system(getenv("SHELL") ? getenv("SHELL") : "/bin/sh"); + ret = 1; + } + if ((strncasecmp(s, "quit", 4) == 0 || strncasecmp(s, "exit", 4) == 0) && + (s[4] == '\0' || isspace(s[4]))) { + quit_handler(0, 0, 0, 0); + ret = 1; + } + + return ret; +} + +static char abort_halt_help[] = +"Usage: abort shutdown\n" +" Causes Asterisk to abort an executing shutdown or restart, and resume normal\n" +" call operations.\n"; + +static char shutdown_now_help[] = +"Usage: stop now\n" +" Shuts down a running Asterisk immediately, hanging up all active calls .\n"; + +static char shutdown_gracefully_help[] = +"Usage: stop gracefully\n" +" Causes Asterisk to not accept new calls, and exit when all\n" +" active calls have terminated normally.\n"; + +static char shutdown_when_convenient_help[] = +"Usage: stop when convenient\n" +" Causes Asterisk to perform a shutdown when all active calls have ended.\n"; + +static char restart_now_help[] = +"Usage: restart now\n" +" Causes Asterisk to hangup all calls and exec() itself performing a cold\n" +" restart.\n"; + +static char restart_gracefully_help[] = +"Usage: restart gracefully\n" +" Causes Asterisk to stop accepting new calls and exec() itself performing a cold\n" +" restart when all active calls have ended.\n"; + +static char restart_when_convenient_help[] = +"Usage: restart when convenient\n" +" Causes Asterisk to perform a cold restart when all active calls have ended.\n"; + +static char bang_help[] = +"Usage: !\n" +" Executes a given shell command\n"; + +static char show_warranty_help[] = +"Usage: show warranty\n" +" Shows the warranty (if any) for this copy of Asterisk.\n"; + +static char show_license_help[] = +"Usage: show license\n" +" Shows the license(s) for this copy of Asterisk.\n"; + +static char version_help[] = +"Usage: show version\n" +" Shows Asterisk version information.\n"; + +static int handle_version(int fd, int argc, char *argv[]) +{ + if (argc != 2) + return RESULT_SHOWUSAGE; + ast_cli(fd, "Asterisk %s built by %s @ %s on a %s running %s on %s\n", + ASTERISK_VERSION, ast_build_user, ast_build_hostname, + ast_build_machine, ast_build_os, ast_build_date); + return RESULT_SUCCESS; +} + +#if 0 +static int handle_quit(int fd, int argc, char *argv[]) +{ + if (argc != 1) + return RESULT_SHOWUSAGE; + quit_handler(0, 0, 1, 0); + return RESULT_SUCCESS; +} +#endif + +static int handle_shutdown_now(int fd, int argc, char *argv[]) +{ + if (argc != 2) + return RESULT_SHOWUSAGE; + quit_handler(0, 0 /* Not nice */, 1 /* safely */, 0 /* not restart */); + return RESULT_SUCCESS; +} + +static int handle_shutdown_gracefully(int fd, int argc, char *argv[]) +{ + if (argc != 2) + return RESULT_SHOWUSAGE; + quit_handler(0, 1 /* nicely */, 1 /* safely */, 0 /* no restart */); + return RESULT_SUCCESS; +} + +static int handle_shutdown_when_convenient(int fd, int argc, char *argv[]) +{ + if (argc != 3) + return RESULT_SHOWUSAGE; + ast_cli(fd, "Waiting for inactivity to perform halt\n"); + quit_handler(0, 2 /* really nicely */, 1 /* safely */, 0 /* don't restart */); + return RESULT_SUCCESS; +} + +static int handle_restart_now(int fd, int argc, char *argv[]) +{ + if (argc != 2) + return RESULT_SHOWUSAGE; + quit_handler(0, 0 /* not nicely */, 1 /* safely */, 1 /* restart */); + return RESULT_SUCCESS; +} + +static int handle_restart_gracefully(int fd, int argc, char *argv[]) +{ + if (argc != 2) + return RESULT_SHOWUSAGE; + quit_handler(0, 1 /* nicely */, 1 /* safely */, 1 /* restart */); + return RESULT_SUCCESS; +} + +static int handle_restart_when_convenient(int fd, int argc, char *argv[]) +{ + if (argc != 3) + return RESULT_SHOWUSAGE; + ast_cli(fd, "Waiting for inactivity to perform restart\n"); + quit_handler(0, 2 /* really nicely */, 1 /* safely */, 1 /* restart */); + return RESULT_SUCCESS; +} + +static int handle_abort_halt(int fd, int argc, char *argv[]) +{ + if (argc != 2) + return RESULT_SHOWUSAGE; + ast_cancel_shutdown(); + shuttingdown = 0; + return RESULT_SUCCESS; +} + +static int handle_bang(int fd, int argc, char *argv[]) +{ + return RESULT_SUCCESS; +} +static const char *warranty_lines[] = { + "\n", + " NO WARRANTY\n", + "\n", + "BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\n", + "FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN\n", + "OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\n", + "PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\n", + "OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\n", + "MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS\n", + "TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE\n", + "PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\n", + "REPAIR OR CORRECTION.\n", + "\n", + "IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\n", + "WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\n", + "REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\n", + "INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\n", + "OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\n", + "TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\n", + "YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\n", + "PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\n", + "POSSIBILITY OF SUCH DAMAGES.\n", +}; + +static int show_warranty(int fd, int argc, char *argv[]) +{ + int x; + + for (x = 0; x < sizeof(warranty_lines) / sizeof(warranty_lines[0]); x++) + ast_cli(fd, (char *) warranty_lines[x]); + + return RESULT_SUCCESS; +} + +static const char *license_lines[] = { + "\n", + "This program is free software; you can redistribute it and/or modify\n", + "it under the terms of the GNU General Public License version 2 as\n", + "published by the Free Software Foundation.\n", + "\n", + "This program also contains components licensed under other licenses.\n", + "They include:\n", + "\n", + "This program is distributed in the hope that it will be useful,\n", + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n", + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n", + "GNU General Public License for more details.\n", + "\n", + "You should have received a copy of the GNU General Public License\n", + "along with this program; if not, write to the Free Software\n", + "Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n", +}; + +static int show_license(int fd, int argc, char *argv[]) +{ + int x; + + for (x = 0; x < sizeof(license_lines) / sizeof(license_lines[0]); x++) + ast_cli(fd, (char *) license_lines[x]); + + return RESULT_SUCCESS; +} + +#define ASTERISK_PROMPT "*CLI> " + +#define ASTERISK_PROMPT2 "%s*CLI> " + +static struct ast_cli_entry core_cli[] = { + { { "abort", "halt", NULL }, handle_abort_halt, + "Cancel a running halt", abort_halt_help }, + { { "stop", "now", NULL }, handle_shutdown_now, + "Shut down Asterisk immediately", shutdown_now_help }, + { { "stop", "gracefully", NULL }, handle_shutdown_gracefully, + "Gracefully shut down Asterisk", shutdown_gracefully_help }, + { { "stop", "when","convenient", NULL }, handle_shutdown_when_convenient, + "Shut down Asterisk at empty call volume", shutdown_when_convenient_help }, + { { "restart", "now", NULL }, handle_restart_now, + "Restart Asterisk immediately", restart_now_help }, + { { "restart", "gracefully", NULL }, handle_restart_gracefully, + "Restart Asterisk gracefully", restart_gracefully_help }, + { { "restart", "when", "convenient", NULL }, handle_restart_when_convenient, + "Restart Asterisk at empty call volume", restart_when_convenient_help }, + { { "show", "warranty", NULL }, show_warranty, + "Show the warranty (if any) for this copy of Asterisk", show_warranty_help }, + { { "show", "license", NULL }, show_license, + "Show the license(s) for this copy of Asterisk", show_license_help }, + { { "show", "version", NULL }, handle_version, + "Display version info", version_help }, + { { "!", NULL }, handle_bang, + "Execute a shell command", bang_help }, +#if !defined(LOW_MEMORY) + { { "show", "version", "files", NULL }, handle_show_version_files, + "Show versions of files used to build Asterisk", show_version_files_help, complete_show_version_files }, + { { "show", "threads", NULL }, handle_show_threads, + "Show running threads", show_threads_help, NULL }, + { { "show", "profile", NULL }, handle_show_profile, + "Show profiling info"}, + { { "clear", "profile", NULL }, handle_show_profile, + "Clear profiling info"}, +#endif /* ! LOW_MEMORY */ +}; + +static int ast_el_read_char(EditLine *el, char *cp) +{ + int num_read = 0; + int lastpos = 0; + struct pollfd fds[2]; + int res; + int max; + char buf[512]; + + for (;;) { + max = 1; + fds[0].fd = ast_consock; + fds[0].events = POLLIN; + if (!ast_opt_exec) { + fds[1].fd = STDIN_FILENO; + fds[1].events = POLLIN; + max++; + } + res = poll(fds, max, -1); + if (res < 0) { + if (errno == EINTR) + continue; + ast_log(LOG_ERROR, "poll failed: %s\n", strerror(errno)); + break; + } + + if (!ast_opt_exec && fds[1].revents) { + num_read = read(STDIN_FILENO, cp, 1); + if (num_read < 1) { + break; + } else + return (num_read); + } + if (fds[0].revents) { + res = read(ast_consock, buf, sizeof(buf) - 1); + /* if the remote side disappears exit */ + if (res < 1) { + fprintf(stderr, "\nDisconnected from Asterisk server\n"); + if (!ast_opt_reconnect) { + quit_handler(0, 0, 0, 0); + } else { + int tries; + int reconnects_per_second = 20; + fprintf(stderr, "Attempting to reconnect for 30 seconds\n"); + for (tries=0; tries < 30 * reconnects_per_second; tries++) { + if (ast_tryconnect()) { + fprintf(stderr, "Reconnect succeeded after %.3f seconds\n", 1.0 / reconnects_per_second * tries); + printf(term_quit()); + WELCOME_MESSAGE; + break; + } else { + usleep(1000000 / reconnects_per_second); + } + } + if (tries >= 30 * reconnects_per_second) { + fprintf(stderr, "Failed to reconnect for 30 seconds. Quitting.\n"); + quit_handler(0, 0, 0, 0); + } + } + } + + buf[res] = '\0'; + + if (!ast_opt_exec && !lastpos) + write(STDOUT_FILENO, "\r", 1); + write(STDOUT_FILENO, buf, res); + if ((buf[res-1] == '\n') || (buf[res-2] == '\n')) { + *cp = CC_REFRESH; + return(1); + } else { + lastpos = 1; + } + } + } + + *cp = '\0'; + return (0); +} + +static char *cli_prompt(EditLine *el) +{ + static char prompt[200]; + char *pfmt; + int color_used = 0; + char term_code[20]; + + if ((pfmt = getenv("ASTERISK_PROMPT"))) { + char *t = pfmt, *p = prompt; + memset(prompt, 0, sizeof(prompt)); + while (*t != '\0' && *p < sizeof(prompt)) { + if (*t == '%') { + char hostname[MAXHOSTNAMELEN]=""; + int i; + time_t ts; + struct tm tm; +#ifdef linux + FILE *LOADAVG; +#endif + int fgcolor = COLOR_WHITE, bgcolor = COLOR_BLACK; + + t++; + switch (*t) { + case 'C': /* color */ + t++; + if (sscanf(t, "%d;%d%n", &fgcolor, &bgcolor, &i) == 2) { + strncat(p, term_color_code(term_code, fgcolor, bgcolor, sizeof(term_code)),sizeof(prompt) - strlen(prompt) - 1); + t += i - 1; + } else if (sscanf(t, "%d%n", &fgcolor, &i) == 1) { + strncat(p, term_color_code(term_code, fgcolor, 0, sizeof(term_code)),sizeof(prompt) - strlen(prompt) - 1); + t += i - 1; + } + + /* If the color has been reset correctly, then there's no need to reset it later */ + if ((fgcolor == COLOR_WHITE) && (bgcolor == COLOR_BLACK)) { + color_used = 0; + } else { + color_used = 1; + } + break; + case 'd': /* date */ + memset(&tm, 0, sizeof(tm)); + time(&ts); + if (localtime_r(&ts, &tm)) { + strftime(p, sizeof(prompt) - strlen(prompt), "%Y-%m-%d", &tm); + } + break; + case 'h': /* hostname */ + if (!gethostname(hostname, sizeof(hostname) - 1)) { + strncat(p, hostname, sizeof(prompt) - strlen(prompt) - 1); + } else { + strncat(p, "localhost", sizeof(prompt) - strlen(prompt) - 1); + } + break; + case 'H': /* short hostname */ + if (!gethostname(hostname, sizeof(hostname) - 1)) { + for (i = 0; i < sizeof(hostname); i++) { + if (hostname[i] == '.') { + hostname[i] = '\0'; + break; + } + } + strncat(p, hostname, sizeof(prompt) - strlen(prompt) - 1); + } else { + strncat(p, "localhost", sizeof(prompt) - strlen(prompt) - 1); + } + break; +#ifdef linux + case 'l': /* load avg */ + t++; + if ((LOADAVG = fopen("/proc/loadavg", "r"))) { + float avg1, avg2, avg3; + int actproc, totproc, npid, which; + fscanf(LOADAVG, "%f %f %f %d/%d %d", + &avg1, &avg2, &avg3, &actproc, &totproc, &npid); + if (sscanf(t, "%d", &which) == 1) { + switch (which) { + case 1: + snprintf(p, sizeof(prompt) - strlen(prompt), "%.2f", avg1); + break; + case 2: + snprintf(p, sizeof(prompt) - strlen(prompt), "%.2f", avg2); + break; + case 3: + snprintf(p, sizeof(prompt) - strlen(prompt), "%.2f", avg3); + break; + case 4: + snprintf(p, sizeof(prompt) - strlen(prompt), "%d/%d", actproc, totproc); + break; + case 5: + snprintf(p, sizeof(prompt) - strlen(prompt), "%d", npid); + break; + } + } + } + break; +#endif + case 's': /* Asterisk system name (from asterisk.conf) */ + strncat(p, ast_config_AST_SYSTEM_NAME, sizeof(prompt) - strlen(prompt) - 1); + break; + case 't': /* time */ + memset(&tm, 0, sizeof(tm)); + time(&ts); + if (localtime_r(&ts, &tm)) { + strftime(p, sizeof(prompt) - strlen(prompt), "%H:%M:%S", &tm); + } + break; + case '#': /* process console or remote? */ + if (!ast_opt_remote) { + strncat(p, "#", sizeof(prompt) - strlen(prompt) - 1); + } else { + strncat(p, ">", sizeof(prompt) - strlen(prompt) - 1); + } + break; + case '%': /* literal % */ + strncat(p, "%", sizeof(prompt) - strlen(prompt) - 1); + break; + case '\0': /* % is last character - prevent bug */ + t--; + break; + } + while (*p != '\0') { + p++; + } + t++; + } else { + *p = *t; + p++; + t++; + } + } + if (color_used) { + /* Force colors back to normal at end */ + term_color_code(term_code, COLOR_WHITE, COLOR_BLACK, sizeof(term_code)); + if (strlen(term_code) > sizeof(prompt) - strlen(prompt)) { + strncat(prompt + sizeof(prompt) - strlen(term_code) - 1, term_code, strlen(term_code)); + } else { + strncat(p, term_code, sizeof(term_code)); + } + } + } else if (remotehostname) + snprintf(prompt, sizeof(prompt), ASTERISK_PROMPT2, remotehostname); + else + snprintf(prompt, sizeof(prompt), ASTERISK_PROMPT); + + return(prompt); +} + +static char **ast_el_strtoarr(char *buf) +{ + char **match_list = NULL, *retstr; + size_t match_list_len; + int matches = 0; + + match_list_len = 1; + while ( (retstr = strsep(&buf, " ")) != NULL) { + + if (!strcmp(retstr, AST_CLI_COMPLETE_EOF)) + break; + if (matches + 1 >= match_list_len) { + match_list_len <<= 1; + if (!(match_list = ast_realloc(match_list, match_list_len * sizeof(char *)))) { + /* TODO: Handle memory allocation failure */ + } + } + + match_list[matches++] = strdup(retstr); + } + + if (!match_list) + return (char **) NULL; + + if (matches >= match_list_len) { + if (!(match_list = ast_realloc(match_list, (match_list_len + 1) * sizeof(char *)))) { + /* TODO: Handle memory allocation failure */ + } + } + + match_list[matches] = (char *) NULL; + + return match_list; +} + +static int ast_el_sort_compare(const void *i1, const void *i2) +{ + char *s1, *s2; + + s1 = ((char **)i1)[0]; + s2 = ((char **)i2)[0]; + + return strcasecmp(s1, s2); +} + +static int ast_cli_display_match_list(char **matches, int len, int max) +{ + int i, idx, limit, count; + int screenwidth = 0; + int numoutput = 0, numoutputline = 0; + + screenwidth = ast_get_termcols(STDOUT_FILENO); + + /* find out how many entries can be put on one line, with two spaces between strings */ + limit = screenwidth / (max + 2); + if (limit == 0) + limit = 1; + + /* how many lines of output */ + count = len / limit; + if (count * limit < len) + count++; + + idx = 1; + + qsort(&matches[0], (size_t)(len), sizeof(char *), ast_el_sort_compare); + + for (; count > 0; count--) { + numoutputline = 0; + for (i=0; i < limit && matches[idx]; i++, idx++) { + + /* Don't print dupes */ + if ( (matches[idx+1] != NULL && strcmp(matches[idx], matches[idx+1]) == 0 ) ) { + i--; + free(matches[idx]); + matches[idx] = NULL; + continue; + } + + numoutput++; + numoutputline++; + fprintf(stdout, "%-*s ", max, matches[idx]); + free(matches[idx]); + matches[idx] = NULL; + } + if (numoutputline > 0) + fprintf(stdout, "\n"); + } + + return numoutput; +} + + +static char *cli_complete(EditLine *el, int ch) +{ + int len = 0; + char *ptr; + int nummatches = 0; + char **matches; + int retval = CC_ERROR; + char buf[2048]; + int res; + + LineInfo *lf = (LineInfo *)el_line(el); + + *(char *)lf->cursor = '\0'; + ptr = (char *)lf->cursor; + if (ptr) { + while (ptr > lf->buffer) { + if (isspace(*ptr)) { + ptr++; + break; + } + ptr--; + } + } + + len = lf->cursor - ptr; + + if (ast_opt_remote) { + snprintf(buf, sizeof(buf),"_COMMAND NUMMATCHES \"%s\" \"%s\"", lf->buffer, ptr); + fdprint(ast_consock, buf); + res = read(ast_consock, buf, sizeof(buf)); + buf[res] = '\0'; + nummatches = atoi(buf); + + if (nummatches > 0) { + char *mbuf; + int mlen = 0, maxmbuf = 2048; + /* Start with a 2048 byte buffer */ + if (!(mbuf = ast_malloc(maxmbuf))) + return (char *)(CC_ERROR); + snprintf(buf, sizeof(buf),"_COMMAND MATCHESARRAY \"%s\" \"%s\"", lf->buffer, ptr); + fdprint(ast_consock, buf); + res = 0; + mbuf[0] = '\0'; + while (!strstr(mbuf, AST_CLI_COMPLETE_EOF) && res != -1) { + if (mlen + 1024 > maxmbuf) { + /* Every step increment buffer 1024 bytes */ + maxmbuf += 1024; + if (!(mbuf = ast_realloc(mbuf, maxmbuf))) + return (char *)(CC_ERROR); + } + /* Only read 1024 bytes at a time */ + res = read(ast_consock, mbuf + mlen, 1024); + if (res > 0) + mlen += res; + } + mbuf[mlen] = '\0'; + + matches = ast_el_strtoarr(mbuf); + free(mbuf); + } else + matches = (char **) NULL; + } else { + char **p, *oldbuf=NULL; + nummatches = 0; + matches = ast_cli_completion_matches((char *)lf->buffer,ptr); + for (p = matches; p && *p; p++) { + if (!oldbuf || strcmp(*p,oldbuf)) + nummatches++; + oldbuf = *p; + } + } + + if (matches) { + int i; + int matches_num, maxlen, match_len; + + if (matches[0][0] != '\0') { + el_deletestr(el, (int) len); + el_insertstr(el, matches[0]); + retval = CC_REFRESH; + } + + if (nummatches == 1) { + /* Found an exact match */ + el_insertstr(el, " "); + retval = CC_REFRESH; + } else { + /* Must be more than one match */ + for (i=1, maxlen=0; matches[i]; i++) { + match_len = strlen(matches[i]); + if (match_len > maxlen) + maxlen = match_len; + } + matches_num = i - 1; + if (matches_num >1) { + fprintf(stdout, "\n"); + ast_cli_display_match_list(matches, nummatches, maxlen); + retval = CC_REDISPLAY; + } else { + el_insertstr(el," "); + retval = CC_REFRESH; + } + } + free(matches); + } + + return (char *)(long)retval; +} + +static int ast_el_initialize(void) +{ + HistEvent ev; + char *editor = getenv("AST_EDITOR"); + + if (el != NULL) + el_end(el); + if (el_hist != NULL) + history_end(el_hist); + + el = el_init("asterisk", stdin, stdout, stderr); + el_set(el, EL_PROMPT, cli_prompt); + + el_set(el, EL_EDITMODE, 1); + el_set(el, EL_EDITOR, editor ? editor : "emacs"); + el_hist = history_init(); + if (!el || !el_hist) + return -1; + + /* setup history with 100 entries */ + history(el_hist, &ev, H_SETSIZE, 100); + + el_set(el, EL_HIST, history, el_hist); + + el_set(el, EL_ADDFN, "ed-complete", "Complete argument", cli_complete); + /* Bind to command completion */ + el_set(el, EL_BIND, "^I", "ed-complete", NULL); + /* Bind ? to command completion */ + el_set(el, EL_BIND, "?", "ed-complete", NULL); + /* Bind ^D to redisplay */ + el_set(el, EL_BIND, "^D", "ed-redisplay", NULL); + + return 0; +} + +static int ast_el_add_history(char *buf) +{ + HistEvent ev; + + if (el_hist == NULL || el == NULL) + ast_el_initialize(); + if (strlen(buf) > 256) + return 0; + return (history(el_hist, &ev, H_ENTER, buf)); +} + +static int ast_el_write_history(char *filename) +{ + HistEvent ev; + + if (el_hist == NULL || el == NULL) + ast_el_initialize(); + + return (history(el_hist, &ev, H_SAVE, filename)); +} + +static int ast_el_read_history(char *filename) +{ + char buf[256]; + FILE *f; + int ret = -1; + + if (el_hist == NULL || el == NULL) + ast_el_initialize(); + + if ((f = fopen(filename, "r")) == NULL) + return ret; + + while (!feof(f)) { + fgets(buf, sizeof(buf), f); + if (!strcmp(buf, "_HiStOrY_V2_\n")) + continue; + if (ast_all_zeros(buf)) + continue; + if ((ret = ast_el_add_history(buf)) == -1) + break; + } + fclose(f); + + return ret; +} + +static void ast_remotecontrol(char * data) +{ + char buf[80]; + int res; + char filename[80] = ""; + char *hostname; + char *cpid; + char *version; + int pid; + char tmp[80]; + char *stringp = NULL; + + char *ebuf; + int num = 0; + + read(ast_consock, buf, sizeof(buf)); + if (data) + write(ast_consock, data, strlen(data) + 1); + stringp = buf; + hostname = strsep(&stringp, "/"); + cpid = strsep(&stringp, "/"); + version = strsep(&stringp, "\n"); + if (!version) + version = ""; + stringp = hostname; + strsep(&stringp, "."); + if (cpid) + pid = atoi(cpid); + else + pid = -1; + snprintf(tmp, sizeof(tmp), "set verbose atleast %d", option_verbose); + fdprint(ast_consock, tmp); + snprintf(tmp, sizeof(tmp), "set debug atleast %d", option_debug); + fdprint(ast_consock, tmp); + if (ast_opt_mute) { + snprintf(tmp, sizeof(tmp), "log and verbose output currently muted ('logger unmute' to unmute)"); + fdprint(ast_consock, tmp); + } + ast_verbose("Connected to Asterisk %s currently running on %s (pid = %d)\n", version, hostname, pid); + remotehostname = hostname; + if (getenv("HOME")) + snprintf(filename, sizeof(filename), "%s/.asterisk_history", getenv("HOME")); + if (el_hist == NULL || el == NULL) + ast_el_initialize(); + + el_set(el, EL_GETCFN, ast_el_read_char); + + if (!ast_strlen_zero(filename)) + ast_el_read_history(filename); + + if (ast_opt_exec && data) { /* hack to print output then exit if asterisk -rx is used */ + char tempchar; + struct pollfd fds; + fds.fd = ast_consock; + fds.events = POLLIN; + fds.revents = 0; + while (poll(&fds, 1, 100) > 0) + ast_el_read_char(el, &tempchar); + return; + } + for (;;) { + ebuf = (char *)el_gets(el, &num); + + if (!ast_strlen_zero(ebuf)) { + if (ebuf[strlen(ebuf)-1] == '\n') + ebuf[strlen(ebuf)-1] = '\0'; + if (!remoteconsolehandler(ebuf)) { + res = write(ast_consock, ebuf, strlen(ebuf) + 1); + if (res < 1) { + ast_log(LOG_WARNING, "Unable to write: %s\n", strerror(errno)); + break; + } + } + } + } + printf("\nDisconnected from Asterisk server\n"); +} + +static int show_version(void) +{ + printf("Asterisk " ASTERISK_VERSION "\n"); + return 0; +} + +static int show_cli_help(void) { + printf("Asterisk " ASTERISK_VERSION ", Copyright (C) 1999 - 2006, Digium, Inc. and others.\n"); + printf("Usage: asterisk [OPTIONS]\n"); + printf("Valid Options:\n"); + printf(" -V Display version number and exit\n"); + printf(" -C Use an alternate configuration file\n"); + printf(" -G Run as a group other than the caller\n"); + printf(" -U Run as a user other than the caller\n"); + printf(" -c Provide console CLI\n"); + printf(" -d Enable extra debugging\n"); + printf(" -f Do not fork\n"); + printf(" -g Dump core in case of a crash\n"); + printf(" -h This help screen\n"); + printf(" -i Initialize crypto keys at startup\n"); + printf(" -I Enable internal timing if Zaptel timer is available\n"); + printf(" -L Limit the maximum load average before rejecting new calls\n"); + printf(" -M Limit the maximum number of calls to the specified value\n"); + printf(" -m Mute the console from debugging and verbose output\n"); + printf(" -n Disable console colorization\n"); + printf(" -p Run as pseudo-realtime thread\n"); + printf(" -q Quiet mode (suppress output)\n"); + printf(" -r Connect to Asterisk on this machine\n"); + printf(" -R Connect to Asterisk, and attempt to reconnect if disconnected\n"); + printf(" -t Record soundfiles in /var/tmp and move them where they belong after they are done.\n"); + printf(" -T Display the time in [Mmm dd hh:mm:ss] format for each line of output to the CLI.\n"); + printf(" -v Increase verbosity (multiple v's = more verbose)\n"); + printf(" -x Execute command (only valid with -r)\n"); + printf("\n"); + return 0; +} + +static void ast_readconfig(void) +{ + struct ast_config *cfg; + struct ast_variable *v; + char *config = AST_CONFIG_FILE; + + if (ast_opt_override_config) { + cfg = ast_config_load(ast_config_AST_CONFIG_FILE); + if (!cfg) + ast_log(LOG_WARNING, "Unable to open specified master config file '%s', using built-in defaults\n", ast_config_AST_CONFIG_FILE); + } else { + cfg = ast_config_load(config); + } + + /* init with buildtime config */ + ast_copy_string(ast_config_AST_CONFIG_DIR, AST_CONFIG_DIR, sizeof(ast_config_AST_CONFIG_DIR)); + ast_copy_string(ast_config_AST_SPOOL_DIR, AST_SPOOL_DIR, sizeof(ast_config_AST_SPOOL_DIR)); + ast_copy_string(ast_config_AST_MODULE_DIR, AST_MODULE_DIR, sizeof(ast_config_AST_MODULE_DIR)); + snprintf(ast_config_AST_MONITOR_DIR, sizeof(ast_config_AST_MONITOR_DIR) - 1, "%s/monitor", ast_config_AST_SPOOL_DIR); + ast_copy_string(ast_config_AST_VAR_DIR, AST_VAR_DIR, sizeof(ast_config_AST_VAR_DIR)); + ast_copy_string(ast_config_AST_DATA_DIR, AST_DATA_DIR, sizeof(ast_config_AST_DATA_DIR)); + ast_copy_string(ast_config_AST_LOG_DIR, AST_LOG_DIR, sizeof(ast_config_AST_LOG_DIR)); + ast_copy_string(ast_config_AST_AGI_DIR, AST_AGI_DIR, sizeof(ast_config_AST_AGI_DIR)); + ast_copy_string(ast_config_AST_DB, AST_DB, sizeof(ast_config_AST_DB)); + ast_copy_string(ast_config_AST_KEY_DIR, AST_KEY_DIR, sizeof(ast_config_AST_KEY_DIR)); + ast_copy_string(ast_config_AST_PID, AST_PID, sizeof(ast_config_AST_PID)); + ast_copy_string(ast_config_AST_SOCKET, AST_SOCKET, sizeof(ast_config_AST_SOCKET)); + ast_copy_string(ast_config_AST_RUN_DIR, AST_RUN_DIR, sizeof(ast_config_AST_RUN_DIR)); + + /* no asterisk.conf? no problem, use buildtime config! */ + if (!cfg) { + return; + } + + for (v = ast_variable_browse(cfg, "files"); v; v = v->next) { + if (!strcasecmp(v->name, "astctlpermissions")) { + ast_copy_string(ast_config_AST_CTL_PERMISSIONS, v->value, sizeof(ast_config_AST_CTL_PERMISSIONS)); + } else if (!strcasecmp(v->name, "astctlowner")) { + ast_copy_string(ast_config_AST_CTL_OWNER, v->value, sizeof(ast_config_AST_CTL_OWNER)); + } else if (!strcasecmp(v->name, "astctlgroup")) { + ast_copy_string(ast_config_AST_CTL_GROUP, v->value, sizeof(ast_config_AST_CTL_GROUP)); + } else if (!strcasecmp(v->name, "astctl")) { + ast_copy_string(ast_config_AST_CTL, v->value, sizeof(ast_config_AST_CTL)); + } + } + + for (v = ast_variable_browse(cfg, "directories"); v; v = v->next) { + if (!strcasecmp(v->name, "astetcdir")) { + ast_copy_string(ast_config_AST_CONFIG_DIR, v->value, sizeof(ast_config_AST_CONFIG_DIR)); + } else if (!strcasecmp(v->name, "astspooldir")) { + ast_copy_string(ast_config_AST_SPOOL_DIR, v->value, sizeof(ast_config_AST_SPOOL_DIR)); + snprintf(ast_config_AST_MONITOR_DIR, sizeof(ast_config_AST_MONITOR_DIR) - 1, "%s/monitor", v->value); + } else if (!strcasecmp(v->name, "astvarlibdir")) { + ast_copy_string(ast_config_AST_VAR_DIR, v->value, sizeof(ast_config_AST_VAR_DIR)); + snprintf(ast_config_AST_DB, sizeof(ast_config_AST_DB), "%s/astdb", v->value); + } else if (!strcasecmp(v->name, "astdatadir")) { + ast_copy_string(ast_config_AST_DATA_DIR, v->value, sizeof(ast_config_AST_DATA_DIR)); + snprintf(ast_config_AST_KEY_DIR, sizeof(ast_config_AST_KEY_DIR), "%s/keys", v->value); + } else if (!strcasecmp(v->name, "astlogdir")) { + ast_copy_string(ast_config_AST_LOG_DIR, v->value, sizeof(ast_config_AST_LOG_DIR)); + } else if (!strcasecmp(v->name, "astagidir")) { + ast_copy_string(ast_config_AST_AGI_DIR, v->value, sizeof(ast_config_AST_AGI_DIR)); + } else if (!strcasecmp(v->name, "astrundir")) { + snprintf(ast_config_AST_PID, sizeof(ast_config_AST_PID), "%s/%s", v->value, "asterisk.pid"); + snprintf(ast_config_AST_SOCKET, sizeof(ast_config_AST_SOCKET), "%s/%s", v->value, ast_config_AST_CTL); + ast_copy_string(ast_config_AST_RUN_DIR, v->value, sizeof(ast_config_AST_RUN_DIR)); + } else if (!strcasecmp(v->name, "astmoddir")) { + ast_copy_string(ast_config_AST_MODULE_DIR, v->value, sizeof(ast_config_AST_MODULE_DIR)); + } + } + + for (v = ast_variable_browse(cfg, "options"); v; v = v->next) { + /* verbose level (-v at startup) */ + if (!strcasecmp(v->name, "verbose")) { + option_verbose = atoi(v->value); + /* whether or not to force timestamping in CLI verbose output. (-T at startup) */ + } else if (!strcasecmp(v->name, "timestamp")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_TIMESTAMP); + /* whether or not to support #exec in config files */ + } else if (!strcasecmp(v->name, "execincludes")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_EXEC_INCLUDES); + /* debug level (-d at startup) */ + } else if (!strcasecmp(v->name, "debug")) { + option_debug = 0; + if (sscanf(v->value, "%d", &option_debug) != 1) { + option_debug = ast_true(v->value); + } + /* Disable forking (-f at startup) */ + } else if (!strcasecmp(v->name, "nofork")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_NO_FORK); + /* Always fork, even if verbose or debug are enabled (-F at startup) */ + } else if (!strcasecmp(v->name, "alwaysfork")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_ALWAYS_FORK); + /* Run quietly (-q at startup ) */ + } else if (!strcasecmp(v->name, "quiet")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_QUIET); + /* Run as console (-c at startup, implies nofork) */ + } else if (!strcasecmp(v->name, "console")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_CONSOLE); + /* Run with high priority if the O/S permits (-p at startup) */ + } else if (!strcasecmp(v->name, "highpriority")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_HIGH_PRIORITY); + /* Initialize RSA auth keys (IAX2) (-i at startup) */ + } else if (!strcasecmp(v->name, "initcrypto")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_INIT_KEYS); + /* Disable ANSI colors for console (-c at startup) */ + } else if (!strcasecmp(v->name, "nocolor")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_NO_COLOR); + /* Disable some usage warnings for picky people :p */ + } else if (!strcasecmp(v->name, "dontwarn")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_DONT_WARN); + /* Dump core in case of crash (-g) */ + } else if (!strcasecmp(v->name, "dumpcore")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_DUMP_CORE); + /* Cache recorded sound files to another directory during recording */ + } else if (!strcasecmp(v->name, "cache_record_files")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_CACHE_RECORD_FILES); + /* Specify cache directory */ + } else if (!strcasecmp(v->name, "record_cache_dir")) { + ast_copy_string(record_cache_dir, v->value, AST_CACHE_DIR_LEN); + /* Build transcode paths via SLINEAR, instead of directly */ + } else if (!strcasecmp(v->name, "transcode_via_sln")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_TRANSCODE_VIA_SLIN); + /* Transmit SLINEAR silence while a channel is being recorded */ + } else if (!strcasecmp(v->name, "transmit_silence_during_record")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_TRANSMIT_SILENCE); + /* Enable internal timing */ + } else if (!strcasecmp(v->name, "internal_timing")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_INTERNAL_TIMING); + } else if (!strcasecmp(v->name, "maxcalls")) { + if ((sscanf(v->value, "%d", &option_maxcalls) != 1) || (option_maxcalls < 0)) { + option_maxcalls = 0; + } + } else if (!strcasecmp(v->name, "maxload")) { + double test[1]; + + if (getloadavg(test, 1) == -1) { + ast_log(LOG_ERROR, "Cannot obtain load average on this system. 'maxload' option disabled.\n"); + option_maxload = 0.0; + } else if ((sscanf(v->value, "%lf", &option_maxload) != 1) || (option_maxload < 0.0)) { + option_maxload = 0.0; + } + /* What user to run as */ + } else if (!strcasecmp(v->name, "runuser")) { + ast_copy_string(ast_config_AST_RUN_USER, v->value, sizeof(ast_config_AST_RUN_USER)); + /* What group to run as */ + } else if (!strcasecmp(v->name, "rungroup")) { + ast_copy_string(ast_config_AST_RUN_GROUP, v->value, sizeof(ast_config_AST_RUN_GROUP)); + } else if (!strcasecmp(v->name, "systemname")) { + ast_copy_string(ast_config_AST_SYSTEM_NAME, v->value, sizeof(ast_config_AST_SYSTEM_NAME)); + } else if (!strcasecmp(v->name, "languageprefix")) { + ast_language_is_prefix = ast_true(v->value); + } + } + ast_config_destroy(cfg); +} + +int main(int argc, char *argv[]) +{ + int c; + char filename[80] = ""; + char hostname[MAXHOSTNAMELEN] = ""; + char tmp[80]; + char * xarg = NULL; + int x; + FILE *f; + sigset_t sigs; + int num; + int is_child_of_nonroot = 0; + char *buf; + char *runuser = NULL, *rungroup = NULL; + + /* Remember original args for restart */ + if (argc > sizeof(_argv) / sizeof(_argv[0]) - 1) { + fprintf(stderr, "Truncating argument size to %d\n", (int)(sizeof(_argv) / sizeof(_argv[0])) - 1); + argc = sizeof(_argv) / sizeof(_argv[0]) - 1; + } + for (x=0; x", sizeof(hostname)); + ast_mainpid = getpid(); + ast_ulaw_init(); + ast_alaw_init(); + callerid_init(); + ast_builtins_init(); + ast_utils_init(); + tdd_init(); + /* When Asterisk restarts after it has dropped the root privileges, + * it can't issue setuid(), setgid(), setgroups() or set_priority() + */ + if (getenv("ASTERISK_ALREADY_NONROOT")) + is_child_of_nonroot=1; + if (getenv("HOME")) + snprintf(filename, sizeof(filename), "%s/.asterisk_history", getenv("HOME")); + /* Check for options */ + while ((c = getopt(argc, argv, "mtThfdvVqprRgciInx:U:G:C:L:M:")) != -1) { + switch (c) { + case 'F': + ast_set_flag(&ast_options, AST_OPT_FLAG_ALWAYS_FORK); + break; + case 'd': + option_debug++; + ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK); + break; + case 'c': + ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK | AST_OPT_FLAG_CONSOLE); + break; + case 'f': + ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK); + break; + case 'n': + ast_set_flag(&ast_options, AST_OPT_FLAG_NO_COLOR); + break; + case 'r': + ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK | AST_OPT_FLAG_REMOTE); + break; + case 'R': + ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK | AST_OPT_FLAG_REMOTE | AST_OPT_FLAG_RECONNECT); + break; + case 'p': + ast_set_flag(&ast_options, AST_OPT_FLAG_HIGH_PRIORITY); + break; + case 'v': + option_verbose++; + ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK); + break; + case 'm': + ast_set_flag(&ast_options, AST_OPT_FLAG_MUTE); + break; + case 'M': + if ((sscanf(optarg, "%d", &option_maxcalls) != 1) || (option_maxcalls < 0)) + option_maxcalls = 0; + break; + case 'L': + if ((sscanf(optarg, "%lf", &option_maxload) != 1) || (option_maxload < 0.0)) + option_maxload = 0.0; + break; + case 'q': + ast_set_flag(&ast_options, AST_OPT_FLAG_QUIET); + break; + case 't': + ast_set_flag(&ast_options, AST_OPT_FLAG_CACHE_RECORD_FILES); + break; + case 'T': + ast_set_flag(&ast_options, AST_OPT_FLAG_TIMESTAMP); + break; + case 'x': + ast_set_flag(&ast_options, AST_OPT_FLAG_EXEC); + xarg = optarg; + break; + case 'C': + ast_copy_string(ast_config_AST_CONFIG_FILE, optarg, sizeof(ast_config_AST_CONFIG_FILE)); + ast_set_flag(&ast_options, AST_OPT_FLAG_OVERRIDE_CONFIG); + break; + case 'I': + ast_set_flag(&ast_options, AST_OPT_FLAG_INTERNAL_TIMING); + break; + case 'i': + ast_set_flag(&ast_options, AST_OPT_FLAG_INIT_KEYS); + break; + case 'g': + ast_set_flag(&ast_options, AST_OPT_FLAG_DUMP_CORE); + break; + case 'h': + show_cli_help(); + exit(0); + case 'V': + show_version(); + exit(0); + case 'U': + runuser = optarg; + break; + case 'G': + rungroup = optarg; + break; + case '?': + exit(1); + } + } + + if (ast_opt_console || option_verbose || (ast_opt_remote && !ast_opt_exec)) { + ast_register_verbose(console_verboser); + WELCOME_MESSAGE; + } + + if (ast_opt_console && !option_verbose) + ast_verbose("[ Booting...\n"); + + if (ast_opt_always_fork && (ast_opt_remote || ast_opt_console)) { + ast_log(LOG_WARNING, "'alwaysfork' is not compatible with console or remote console mode; ignored\n"); + ast_clear_flag(&ast_options, AST_OPT_FLAG_ALWAYS_FORK); + } + + /* For remote connections, change the name of the remote connection. + * We do this for the benefit of init scripts (which need to know if/when + * the main asterisk process has died yet). */ + if (ast_opt_remote) { + strcpy(argv[0], "rasterisk"); + for (x = 1; x < argc; x++) { + argv[x] = argv[0] + 10; + } + } + + if (ast_opt_console && !option_verbose) + ast_verbose("[ Reading Master Configuration ]\n"); + ast_readconfig(); + + if (ast_opt_dump_core) { + struct rlimit l; + memset(&l, 0, sizeof(l)); + l.rlim_cur = RLIM_INFINITY; + l.rlim_max = RLIM_INFINITY; + if (setrlimit(RLIMIT_CORE, &l)) { + ast_log(LOG_WARNING, "Unable to disable core size resource limit: %s\n", strerror(errno)); + } + } + + if ((!rungroup) && !ast_strlen_zero(ast_config_AST_RUN_GROUP)) + rungroup = ast_config_AST_RUN_GROUP; + if ((!runuser) && !ast_strlen_zero(ast_config_AST_RUN_USER)) + runuser = ast_config_AST_RUN_USER; + +#ifndef __CYGWIN__ + + if (!is_child_of_nonroot) + ast_set_priority(ast_opt_high_priority); + + if (!is_child_of_nonroot && rungroup) { + struct group *gr; + gr = getgrnam(rungroup); + if (!gr) { + ast_log(LOG_WARNING, "No such group '%s'!\n", rungroup); + exit(1); + } + if (setgid(gr->gr_gid)) { + ast_log(LOG_WARNING, "Unable to setgid to %d (%s)\n", (int)gr->gr_gid, rungroup); + exit(1); + } + if (setgroups(0, NULL)) { + ast_log(LOG_WARNING, "Unable to drop unneeded groups\n"); + exit(1); + } + if (option_verbose) + ast_verbose("Running as group '%s'\n", rungroup); + } + + if (!is_child_of_nonroot && runuser) { + struct passwd *pw; + pw = getpwnam(runuser); + if (!pw) { + ast_log(LOG_WARNING, "No such user '%s'!\n", runuser); + exit(1); + } + if (!rungroup) { + if (setgid(pw->pw_gid)) { + ast_log(LOG_WARNING, "Unable to setgid to %d!\n", (int)pw->pw_gid); + exit(1); + } + if (initgroups(pw->pw_name, pw->pw_gid)) { + ast_log(LOG_WARNING, "Unable to init groups for '%s'\n", runuser); + exit(1); + } + } + if (setuid(pw->pw_uid)) { + ast_log(LOG_WARNING, "Unable to setuid to %d (%s)\n", (int)pw->pw_uid, runuser); + exit(1); + } + setenv("ASTERISK_ALREADY_NONROOT", "yes", 1); + if (option_verbose) + ast_verbose("Running as user '%s'\n", runuser); + } + +#endif /* __CYGWIN__ */ + +#ifdef linux + if (geteuid() && ast_opt_dump_core) { + if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0) { + ast_log(LOG_WARNING, "Unable to set the process for core dumps after changing to a non-root user. %s\n", strerror(errno)); + } + } +#endif + + term_init(); + printf(term_end()); + fflush(stdout); + + if (ast_opt_console && !option_verbose) + ast_verbose("[ Initializing Custom Configuration Options ]\n"); + /* custom config setup */ + register_config_cli(); + read_config_maps(); + + if (ast_opt_console) { + if (el_hist == NULL || el == NULL) + ast_el_initialize(); + + if (!ast_strlen_zero(filename)) + ast_el_read_history(filename); + } + + if (ast_tryconnect()) { + /* One is already running */ + if (ast_opt_remote) { + if (ast_opt_exec) { + ast_remotecontrol(xarg); + quit_handler(0, 0, 0, 0); + exit(0); + } + printf(term_quit()); + ast_remotecontrol(NULL); + quit_handler(0, 0, 0, 0); + exit(0); + } else { + ast_log(LOG_ERROR, "Asterisk already running on %s. Use 'asterisk -r' to connect.\n", ast_config_AST_SOCKET); + printf(term_quit()); + exit(1); + } + } else if (ast_opt_remote || ast_opt_exec) { + ast_log(LOG_ERROR, "Unable to connect to remote asterisk (does %s exist?)\n", ast_config_AST_SOCKET); + printf(term_quit()); + exit(1); + } + /* Blindly write pid file since we couldn't connect */ + unlink(ast_config_AST_PID); + f = fopen(ast_config_AST_PID, "w"); + if (f) { + fprintf(f, "%ld\n", (long)getpid()); + fclose(f); + } else + ast_log(LOG_WARNING, "Unable to open pid file '%s': %s\n", ast_config_AST_PID, strerror(errno)); + + if (ast_opt_always_fork || !ast_opt_no_fork) { + daemon(0, 0); + ast_mainpid = getpid(); + /* Blindly re-write pid file since we are forking */ + unlink(ast_config_AST_PID); + f = fopen(ast_config_AST_PID, "w"); + if (f) { + fprintf(f, "%ld\n", (long)ast_mainpid); + fclose(f); + } else + ast_log(LOG_WARNING, "Unable to open pid file '%s': %s\n", ast_config_AST_PID, strerror(errno)); + } + + /* Test recursive mutex locking. */ + if (test_for_thread_safety()) + ast_verbose("Warning! Asterisk is not thread safe.\n"); + + ast_makesocket(); + sigemptyset(&sigs); + sigaddset(&sigs, SIGHUP); + sigaddset(&sigs, SIGTERM); + sigaddset(&sigs, SIGINT); + sigaddset(&sigs, SIGPIPE); + sigaddset(&sigs, SIGWINCH); + pthread_sigmask(SIG_BLOCK, &sigs, NULL); + signal(SIGURG, urg_handler); + signal(SIGINT, __quit_handler); + signal(SIGTERM, __quit_handler); + signal(SIGHUP, hup_handler); + signal(SIGCHLD, child_handler); + signal(SIGPIPE, SIG_IGN); + + /* ensure that the random number generators are seeded with a different value every time + Asterisk is started + */ + srand((unsigned int) getpid() + (unsigned int) time(NULL)); + initstate((unsigned int) getpid() * 65536 + (unsigned int) time(NULL), randompool, sizeof(randompool)); + + if (init_logger()) { + printf(term_quit()); + exit(1); + } + if (load_modules()) { + printf(term_quit()); + exit(1); + } + + if (dnsmgr_init()) { + printf(term_quit()); + exit(1); + } + + ast_http_init(); + + ast_channels_init(); + + if (init_manager()) { + printf(term_quit()); + exit(1); + } + + if (ast_cdr_engine_init()) { + printf(term_quit()); + exit(1); + } + + if (ast_device_state_engine_init()) { + printf(term_quit()); + exit(1); + } + + ast_rtp_init(); + + ast_udptl_init(); + + if (ast_image_init()) { + printf(term_quit()); + exit(1); + } + + if (ast_file_init()) { + printf(term_quit()); + exit(1); + } + + if (load_pbx()) { + printf(term_quit()); + exit(1); + } + + if (init_framer()) { + printf(term_quit()); + exit(1); + } + + if (astdb_init()) { + printf(term_quit()); + exit(1); + } + + if (ast_enum_init()) { + printf(term_quit()); + exit(1); + } + + dnsmgr_start_refresh(); + + /* We might have the option of showing a console, but for now just + do nothing... */ + if (ast_opt_console && !option_verbose) + ast_verbose(" ]\n"); + if (option_verbose || ast_opt_console) + ast_verbose(term_color(tmp, "Asterisk Ready.\n", COLOR_BRWHITE, COLOR_BLACK, sizeof(tmp))); + if (ast_opt_no_fork) + consolethread = pthread_self(); + + ast_set_flag(&ast_options, AST_OPT_FLAG_FULLY_BOOTED); + pthread_sigmask(SIG_UNBLOCK, &sigs, NULL); + +#ifdef __AST_DEBUG_MALLOC + __ast_mm_init(); +#endif + + time(&ast_startuptime); + ast_cli_register_multiple(core_cli, sizeof(core_cli) / sizeof(core_cli[0])); + + if (ast_opt_console) { + /* Console stuff now... */ + /* Register our quit function */ + char title[256]; + set_icon("Asterisk"); + snprintf(title, sizeof(title), "Asterisk Console on '%s' (pid %ld)", hostname, (long)ast_mainpid); + set_title(title); + + for (;;) { + buf = (char *)el_gets(el, &num); + if (buf) { + if (buf[strlen(buf)-1] == '\n') + buf[strlen(buf)-1] = '\0'; + + consolehandler((char *)buf); + } else { + if (write(STDOUT_FILENO, "\nUse EXIT or QUIT to exit the asterisk console\n", + strlen("\nUse EXIT or QUIT to exit the asterisk console\n")) < 0) { + /* Whoa, stdout disappeared from under us... Make /dev/null's */ + int fd; + fd = open("/dev/null", O_RDWR); + if (fd > -1) { + dup2(fd, STDOUT_FILENO); + dup2(fd, STDIN_FILENO); + } else + ast_log(LOG_WARNING, "Failed to open /dev/null to recover from dead console. Bad things will happen!\n"); + break; + } + } + } + + } + /* Do nothing */ + for(;;) { /* apparently needed for the MACos */ + struct pollfd p = { -1 /* no descriptor */, 0, 0 }; + poll(&p, 0, -1); + } + return 0; +} diff --git a/main/astmm.c b/main/astmm.c new file mode 100644 index 000000000..1a1c171ec --- /dev/null +++ b/main/astmm.c @@ -0,0 +1,443 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Memory Management + * + * \author Mark Spencer + */ + +#ifdef __AST_DEBUG_MALLOC + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include + +#include "asterisk/cli.h" +#include "asterisk/logger.h" +#include "asterisk/options.h" +#include "asterisk/lock.h" +#include "asterisk/strings.h" +#include "asterisk/unaligned.h" + +#define SOME_PRIME 563 + +enum func_type { + FUNC_CALLOC = 1, + FUNC_MALLOC, + FUNC_REALLOC, + FUNC_STRDUP, + FUNC_STRNDUP, + FUNC_VASPRINTF, + FUNC_ASPRINTF +}; + +/* Undefine all our macros */ +#undef malloc +#undef calloc +#undef realloc +#undef strdup +#undef strndup +#undef free +#undef vasprintf +#undef asprintf + +#define FENCE_MAGIC 0xdeadbeef + +static FILE *mmlog; + +static struct ast_region { + struct ast_region *next; + char file[40]; + char func[40]; + int lineno; + enum func_type which; + size_t len; + unsigned int fence; + unsigned char data[0]; +} *regions[SOME_PRIME]; + +#define HASH(a) \ + (((unsigned long)(a)) % SOME_PRIME) + +AST_MUTEX_DEFINE_STATIC(reglock); +AST_MUTEX_DEFINE_STATIC(showmemorylock); + +static inline void *__ast_alloc_region(size_t size, const enum func_type which, const char *file, int lineno, const char *func) +{ + struct ast_region *reg; + void *ptr = NULL; + unsigned int *fence; + int hash; + reg = malloc(size + sizeof(*reg) + sizeof(*fence)); + ast_mutex_lock(®lock); + if (reg) { + ast_copy_string(reg->file, file, sizeof(reg->file)); + reg->file[sizeof(reg->file) - 1] = '\0'; + ast_copy_string(reg->func, func, sizeof(reg->func)); + reg->func[sizeof(reg->func) - 1] = '\0'; + reg->lineno = lineno; + reg->len = size; + reg->which = which; + ptr = reg->data; + hash = HASH(ptr); + reg->next = regions[hash]; + regions[hash] = reg; + reg->fence = FENCE_MAGIC; + fence = (ptr + reg->len); + put_unaligned_uint32(fence, FENCE_MAGIC); + } + ast_mutex_unlock(®lock); + if (!reg) { + fprintf(stderr, "Memory Allocation Failure - '%d' bytes in function %s at line %d of %s\n", (int) size, func, lineno, file); + if (mmlog) { + fprintf(stderr, "%ld - Memory Allocation Failure - '%d' bytes in function %s at line %d of %s\n", time(NULL), (int) size, func, lineno, file); + fflush(mmlog); + } + } + return ptr; +} + +static inline size_t __ast_sizeof_region(void *ptr) +{ + int hash = HASH(ptr); + struct ast_region *reg; + size_t len = 0; + + ast_mutex_lock(®lock); + reg = regions[hash]; + while (reg) { + if (reg->data == ptr) { + len = reg->len; + break; + } + reg = reg->next; + } + ast_mutex_unlock(®lock); + return len; +} + +static void __ast_free_region(void *ptr, const char *file, int lineno, const char *func) +{ + int hash = HASH(ptr); + struct ast_region *reg, *prev = NULL; + unsigned int *fence; + + ast_mutex_lock(®lock); + reg = regions[hash]; + while (reg) { + if (reg->data == ptr) { + if (prev) { + prev->next = reg->next; + } else { + regions[hash] = reg->next; + } + break; + } + prev = reg; + reg = reg->next; + } + ast_mutex_unlock(®lock); + if (reg) { + fence = (unsigned int *)(reg->data + reg->len); + if (reg->fence != FENCE_MAGIC) { + fprintf(stderr, "WARNING: Low fence violation at %p, in %s of %s, line %d\n", reg->data, reg->func, reg->file, reg->lineno); + if (mmlog) { + fprintf(mmlog, "%ld - WARNING: Low fence violation at %p, in %s of %s, line %d\n", time(NULL), reg->data, reg->func, reg->file, reg->lineno); + fflush(mmlog); + } + } + if (get_unaligned_uint32(fence) != FENCE_MAGIC) { + fprintf(stderr, "WARNING: High fence violation at %p, in %s of %s, line %d\n", reg->data, reg->func, reg->file, reg->lineno); + if (mmlog) { + fprintf(mmlog, "%ld - WARNING: High fence violation at %p, in %s of %s, line %d\n", time(NULL), reg->data, reg->func, reg->file, reg->lineno); + fflush(mmlog); + } + } + free(reg); + } else { + fprintf(stderr, "WARNING: Freeing unused memory at %p, in %s of %s, line %d\n", ptr, func, file, lineno); + if (mmlog) { + fprintf(mmlog, "%ld - WARNING: Freeing unused memory at %p, in %s of %s, line %d\n", time(NULL), ptr, func, file, lineno); + fflush(mmlog); + } + } +} + +void *__ast_calloc(size_t nmemb, size_t size, const char *file, int lineno, const char *func) +{ + void *ptr; + if ((ptr = __ast_alloc_region(size * nmemb, FUNC_CALLOC, file, lineno, func))) + memset(ptr, 0, size * nmemb); + return ptr; +} + +void *__ast_malloc(size_t size, const char *file, int lineno, const char *func) +{ + return __ast_alloc_region(size, FUNC_MALLOC, file, lineno, func); +} + +void __ast_free(void *ptr, const char *file, int lineno, const char *func) +{ + __ast_free_region(ptr, file, lineno, func); +} + +void *__ast_realloc(void *ptr, size_t size, const char *file, int lineno, const char *func) +{ + void *tmp; + size_t len = 0; + if (ptr && !(len = __ast_sizeof_region(ptr))) { + fprintf(stderr, "WARNING: Realloc of unalloced memory at %p, in %s of %s, line %d\n", ptr, func, file, lineno); + if (mmlog) { + fprintf(mmlog, "%ld - WARNING: Realloc of unalloced memory at %p, in %s of %s, line %d\n", time(NULL), ptr, func, file, lineno); + fflush(mmlog); + } + return NULL; + } + if ((tmp = __ast_alloc_region(size, FUNC_REALLOC, file, lineno, func))) { + if (len > size) + len = size; + if (ptr) { + memcpy(tmp, ptr, len); + __ast_free_region(ptr, file, lineno, func); + } + } + return tmp; +} + +char *__ast_strdup(const char *s, const char *file, int lineno, const char *func) +{ + size_t len; + void *ptr; + if (!s) + return NULL; + len = strlen(s) + 1; + if ((ptr = __ast_alloc_region(len, FUNC_STRDUP, file, lineno, func))) + strcpy(ptr, s); + return ptr; +} + +char *__ast_strndup(const char *s, size_t n, const char *file, int lineno, const char *func) +{ + size_t len; + void *ptr; + if (!s) + return NULL; + len = strlen(s) + 1; + if (len > n) + len = n; + if ((ptr = __ast_alloc_region(len, FUNC_STRNDUP, file, lineno, func))) + strcpy(ptr, s); + return ptr; +} + +int __ast_asprintf(const char *file, int lineno, const char *func, char **strp, const char *fmt, ...) +{ + int size; + va_list ap, ap2; + char s; + + *strp = NULL; + va_start(ap, fmt); + va_copy(ap2, ap); + size = vsnprintf(&s, 1, fmt, ap2); + va_end(ap2); + if (!(*strp = __ast_alloc_region(size + 1, FUNC_ASPRINTF, file, lineno, func))) { + va_end(ap); + return -1; + } + vsnprintf(*strp, size + 1, fmt, ap); + va_end(ap); + + return size; +} + +int __ast_vasprintf(char **strp, const char *fmt, va_list ap, const char *file, int lineno, const char *func) +{ + int size; + va_list ap2; + char s; + + *strp = NULL; + va_copy(ap2, ap); + size = vsnprintf(&s, 1, fmt, ap2); + va_end(ap2); + if (!(*strp = __ast_alloc_region(size + 1, FUNC_VASPRINTF, file, lineno, func))) { + va_end(ap); + return -1; + } + vsnprintf(*strp, size + 1, fmt, ap); + + return size; +} + +static int handle_show_memory(int fd, int argc, char *argv[]) +{ + char *fn = NULL; + int x; + struct ast_region *reg; + unsigned int len = 0; + int count = 0; + unsigned int *fence; + if (argc > 3) + fn = argv[3]; + + /* try to lock applications list ... */ + ast_mutex_lock(&showmemorylock); + + for (x = 0; x < SOME_PRIME; x++) { + reg = regions[x]; + while (reg) { + if (!fn || !strcasecmp(fn, reg->file) || !strcasecmp(fn, "anomolies")) { + fence = (unsigned int *)(reg->data + reg->len); + if (reg->fence != FENCE_MAGIC) { + fprintf(stderr, "WARNING: Low fence violation at %p, in %s of %s, line %d\n", reg->data, reg->func, reg->file, reg->lineno); + if (mmlog) { + fprintf(mmlog, "%ld - WARNING: Low fence violation at %p, in %s of %s, line %d\n", time(NULL), reg->data, reg->func, reg-> file, reg->lineno); + fflush(mmlog); + } + } + if (get_unaligned_uint32(fence) != FENCE_MAGIC) { + fprintf(stderr, "WARNING: High fence violation at %p, in %s of %s, line %d\n", reg->data, reg->func, reg->file, reg->lineno); + if (mmlog) { + fprintf(mmlog, "%ld - WARNING: High fence violation at %p, in %s of %s, line %d\n", time(NULL), reg->data, reg->func, reg->file, reg->lineno); + fflush(mmlog); + } + } + } + if (!fn || !strcasecmp(fn, reg->file)) { + ast_cli(fd, "%10d bytes allocated in %20s at line %5d of %s\n", (int) reg->len, reg->func, reg->lineno, reg->file); + len += reg->len; + count++; + } + reg = reg->next; + } + } + ast_cli(fd, "%d bytes allocated %d units total\n", len, count); + ast_mutex_unlock(&showmemorylock); + return RESULT_SUCCESS; +} + +struct file_summary { + char fn[80]; + int len; + int count; + struct file_summary *next; +}; + +static int handle_show_memory_summary(int fd, int argc, char *argv[]) +{ + char *fn = NULL; + int x; + struct ast_region *reg; + unsigned int len = 0; + int count = 0; + struct file_summary *list = NULL, *cur; + + if (argc > 3) + fn = argv[3]; + + /* try to lock applications list ... */ + ast_mutex_lock(®lock); + + for (x = 0; x < SOME_PRIME; x++) { + reg = regions[x]; + while (reg) { + if (!fn || !strcasecmp(fn, reg->file)) { + cur = list; + while (cur) { + if ((!fn && !strcmp(cur->fn, reg->file)) || (fn && !strcmp(cur->fn, reg->func))) + break; + cur = cur->next; + } + if (!cur) { + cur = alloca(sizeof(*cur)); + memset(cur, 0, sizeof(*cur)); + ast_copy_string(cur->fn, fn ? reg->func : reg->file, sizeof(cur->fn)); + cur->next = list; + list = cur; + } + cur->len += reg->len; + cur->count++; + } + reg = reg->next; + } + } + ast_mutex_unlock(®lock); + + /* Dump the whole list */ + while (list) { + cur = list; + len += list->len; + count += list->count; + if (fn) { + ast_cli(fd, "%10d bytes in %5d allocations in function '%s' of '%s'\n", list->len, list->count, list->fn, fn); + } else { + ast_cli(fd, "%10d bytes in %5d allocations in file '%s'\n", list->len, list->count, list->fn); + } + list = list->next; +#if 0 + free(cur); +#endif + } + ast_cli(fd, "%d bytes allocated %d units total\n", len, count); + return RESULT_SUCCESS; +} + +static char show_memory_help[] = +"Usage: show memory allocations []\n" +" Dumps a list of all segments of allocated memory, optionally\n" +"limited to those from a specific file\n"; + +static char show_memory_summary_help[] = +"Usage: show memory summary []\n" +" Summarizes heap memory allocations by file, or optionally\n" +"by function, if a file is specified\n"; + +static struct ast_cli_entry show_memory_allocations_cli = + { { "show", "memory", "allocations", NULL }, + handle_show_memory, "Display outstanding memory allocations", + show_memory_help }; + +static struct ast_cli_entry show_memory_summary_cli = + { { "show", "memory", "summary", NULL }, + handle_show_memory_summary, "Summarize outstanding memory allocations", + show_memory_summary_help }; + +void __ast_mm_init(void) +{ + char filename[80] = ""; + ast_cli_register(&show_memory_allocations_cli); + ast_cli_register(&show_memory_summary_cli); + + snprintf(filename, sizeof(filename), "%s/mmlog", (char *)ast_config_AST_LOG_DIR); + mmlog = fopen(filename, "a+"); + if (option_verbose) + ast_verbose("Asterisk Malloc Debugger Started (see %s))\n", filename); + if (mmlog) { + fprintf(mmlog, "%ld - New session\n", time(NULL)); + fflush(mmlog); + } +} + +#endif diff --git a/main/autoservice.c b/main/autoservice.c new file mode 100644 index 000000000..51a1e1ca6 --- /dev/null +++ b/main/autoservice.c @@ -0,0 +1,154 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Automatic channel service routines + * + * \author Mark Spencer + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/pbx.h" +#include "asterisk/frame.h" +#include "asterisk/sched.h" +#include "asterisk/options.h" +#include "asterisk/channel.h" +#include "asterisk/logger.h" +#include "asterisk/file.h" +#include "asterisk/translate.h" +#include "asterisk/manager.h" +#include "asterisk/chanvars.h" +#include "asterisk/linkedlists.h" +#include "asterisk/indications.h" +#include "asterisk/lock.h" +#include "asterisk/utils.h" + +#define MAX_AUTOMONS 256 + +struct asent { + struct ast_channel *chan; + AST_LIST_ENTRY(asent) list; +}; + +static AST_LIST_HEAD_STATIC(aslist, asent); + +static pthread_t asthread = AST_PTHREADT_NULL; + +static void *autoservice_run(void *ign) +{ + + for(;;) { + struct ast_channel *mons[MAX_AUTOMONS]; + struct ast_channel *chan; + struct asent *as; + int x = 0, ms = 500; + + AST_LIST_LOCK(&aslist); + AST_LIST_TRAVERSE(&aslist, as, list) { + if (!as->chan->_softhangup) { + if (x < MAX_AUTOMONS) + mons[x++] = as->chan; + else + ast_log(LOG_WARNING, "Exceeded maximum number of automatic monitoring events. Fix autoservice.c\n"); + } + } + AST_LIST_UNLOCK(&aslist); + + chan = ast_waitfor_n(mons, x, &ms); + if (chan) { + /* Read and ignore anything that occurs */ + struct ast_frame *f = ast_read(chan); + if (f) + ast_frfree(f); + } + } + asthread = AST_PTHREADT_NULL; + return NULL; +} + +int ast_autoservice_start(struct ast_channel *chan) +{ + int res = -1; + struct asent *as; + AST_LIST_LOCK(&aslist); + + /* Check if the channel already has autoservice */ + AST_LIST_TRAVERSE(&aslist, as, list) { + if (as->chan == chan) + break; + } + + /* If not, start autoservice on channel */ + if (!as && (as = ast_calloc(1, sizeof(*as)))) { + as->chan = chan; + AST_LIST_INSERT_HEAD(&aslist, as, list); + res = 0; + if (asthread == AST_PTHREADT_NULL) { /* need start the thread */ + if (ast_pthread_create(&asthread, NULL, autoservice_run, NULL)) { + ast_log(LOG_WARNING, "Unable to create autoservice thread :(\n"); + /* There will only be a single member in the list at this point, + the one we just added. */ + AST_LIST_REMOVE(&aslist, as, list); + free(as); + res = -1; + } else + pthread_kill(asthread, SIGURG); + } + } + AST_LIST_UNLOCK(&aslist); + return res; +} + +int ast_autoservice_stop(struct ast_channel *chan) +{ + int res = -1; + struct asent *as; + + AST_LIST_LOCK(&aslist); + AST_LIST_TRAVERSE_SAFE_BEGIN(&aslist, as, list) { + if (as->chan == chan) { + AST_LIST_REMOVE_CURRENT(&aslist, list); + free(as); + if (!chan->_softhangup) + res = 0; + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + + if (asthread != AST_PTHREADT_NULL) + pthread_kill(asthread, SIGURG); + AST_LIST_UNLOCK(&aslist); + + /* Wait for it to un-block */ + while(ast_test_flag(chan, AST_FLAG_BLOCKING)) + usleep(1000); + return res; +} diff --git a/main/buildinfo.c b/main/buildinfo.c new file mode 100644 index 000000000..964e06eb3 --- /dev/null +++ b/main/buildinfo.c @@ -0,0 +1,33 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2005, Digium, Inc. + * + * Kevin P. Fleming + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Build timestamp variables + * + * \author Kevin P. Fleming + */ + +#include "asterisk/build.h" + +const char *ast_build_hostname = BUILD_HOSTNAME; +const char *ast_build_kernel = BUILD_KERNEL; +const char *ast_build_machine = BUILD_MACHINE; +const char *ast_build_os = BUILD_OS; +const char *ast_build_date = BUILD_DATE; +const char *ast_build_user = BUILD_USER; diff --git a/main/callerid.c b/main/callerid.c new file mode 100644 index 000000000..98cb7d081 --- /dev/null +++ b/main/callerid.c @@ -0,0 +1,1096 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief CallerID Generation support + * + * \author Mark Spencer + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/ulaw.h" +#include "asterisk/alaw.h" +#include "asterisk/frame.h" +#include "asterisk/channel.h" +#include "asterisk/callerid.h" +#include "asterisk/logger.h" +#include "asterisk/fskmodem.h" +#include "asterisk/options.h" +#include "asterisk/utils.h" + +struct callerid_state { + fsk_data fskd; + char rawdata[256]; + short oldstuff[160]; + int oldlen; + int pos; + int type; + int cksum; + char name[64]; + char number[64]; + int flags; + int sawflag; + int len; + + int skipflag; + unsigned short crc; +}; + + +float cid_dr[4], cid_di[4]; +float clidsb = 8000.0 / 1200.0; +float sasdr, sasdi; +float casdr1, casdi1, casdr2, casdi2; + +#define CALLERID_SPACE 2200.0 /*!< 2200 hz for "0" */ +#define CALLERID_MARK 1200.0 /*!< 1200 hz for "1" */ +#define SAS_FREQ 440.0 +#define CAS_FREQ1 2130.0 +#define CAS_FREQ2 2750.0 + +#define AST_CALLERID_UNKNOWN "" + +static inline void gen_tones(unsigned char *buf, int len, int codec, float ddr1, float ddi1, float ddr2, float ddi2, float *cr1, float *ci1, float *cr2, float *ci2) +{ + int x; + float t; + for (x=0;xfskd.spb = 7.0; /* 1200 baud */ + /* cid->fskd.hdlc = 0; */ /* Async */ + cid->fskd.nbit = 8; /* 8 bits */ + cid->fskd.nstop = 1.0; /* 1 stop bit */ + /* cid->fskd.paridad = 0; */ /* No parity */ + cid->fskd.bw = 1; /* Filter 800 Hz */ + if (cid_signalling == 2) { /* v23 signalling */ + cid->fskd.f_mark_idx = 4; /* 1300 Hz */ + cid->fskd.f_space_idx = 5; /* 2100 Hz */ + } else { /* Bell 202 signalling as default */ + cid->fskd.f_mark_idx = 2; /* 1200 Hz */ + cid->fskd.f_space_idx = 3; /* 2200 Hz */ + } + /* cid->fskd.pcola = 0; */ /* No clue */ + /* cid->fskd.cont = 0.0; */ /* Digital PLL reset */ + /* cid->fskd.x0 = 0.0; */ + /* cid->fskd.state = 0; */ + cid->flags = CID_UNKNOWN_NAME | CID_UNKNOWN_NUMBER; + /* cid->pos = 0; */ + } + + return cid; +} + +void callerid_get(struct callerid_state *cid, char **name, char **number, int *flags) +{ + *flags = cid->flags; + if (cid->flags & (CID_UNKNOWN_NAME | CID_PRIVATE_NUMBER)) + *name = NULL; + else + *name = cid->name; + if (cid->flags & (CID_UNKNOWN_NUMBER | CID_PRIVATE_NUMBER)) + *number = NULL; + else + *number = cid->number; +} + +void callerid_get_dtmf(char *cidstring, char *number, int *flags) +{ + int i; + int code; + + /* "Clear" the number-buffer. */ + number[0] = 0; + + if (strlen(cidstring) < 2) { + ast_log(LOG_DEBUG, "No cid detected\n"); + *flags = CID_UNKNOWN_NUMBER; + return; + } + + /* Detect protocol and special types */ + if (cidstring[0] == 'B') { + /* Handle special codes */ + code = atoi(&cidstring[1]); + if (code == 0) + *flags = CID_UNKNOWN_NUMBER; + else if (code == 10) + *flags = CID_PRIVATE_NUMBER; + else + ast_log(LOG_DEBUG, "Unknown DTMF code %d\n", code); + } else if (cidstring[0] == 'D' && cidstring[2] == '#') { + /* .DK special code */ + if (cidstring[1] == '1') + *flags = CID_PRIVATE_NUMBER; + if (cidstring[1] == '2' || cidstring[1] == '3') + *flags = CID_UNKNOWN_NUMBER; + } else if (cidstring[0] == 'D' || cidstring[0] == 'A') { + /* "Standard" callerid */ + for (i = 1; i < strlen(cidstring); i++ ) { + if (cidstring[i] == 'C' || cidstring[i] == '#') + break; + if (isdigit(cidstring[i])) + number[i-1] = cidstring[i]; + else + ast_log(LOG_DEBUG, "Unknown CID digit '%c'\n", + cidstring[i]); + } + number[i-1] = 0; + } else if (isdigit(cidstring[0])) { + /* It begins with a digit, so we parse it as a number and hope + * for the best */ + ast_log(LOG_WARNING, "Couldn't detect start-character. CID " + "parsing might be unreliable\n"); + for (i = 0; i < strlen(cidstring); i++) { + if (isdigit(cidstring[i])) + number[i] = cidstring[i]; + else + break; + } + number[i] = 0; + } else { + ast_log(LOG_DEBUG, "Unknown CID protocol, start digit '%c'\n", + cidstring[0]); + *flags = CID_UNKNOWN_NUMBER; + } +} + +int ast_gen_cas(unsigned char *outbuf, int sendsas, int len, int codec) +{ + int pos = 0; + int saslen=2400; + float cr1 = 1.0; + float ci1 = 0.0; + float cr2 = 1.0; + float ci2 = 0.0; + if (sendsas) { + if (len < saslen) + return -1; + gen_tone(outbuf, saslen, codec, sasdr, sasdi, &cr1, &ci1); + len -= saslen; + pos += saslen; + cr2 = cr1; + ci2 = ci1; + } + gen_tones(outbuf + pos, len, codec, casdr1, casdi1, casdr2, casdi2, &cr1, &ci1, &cr2, &ci2); + return 0; +} + +static unsigned short calc_crc(unsigned short crc, unsigned char data) +{ + unsigned int i, j, org, dst; + org = data; + dst = 0; + + for (i=0; i < CHAR_BIT; i++) { + org <<= 1; + dst >>= 1; + if (org & 0x100) { + dst |= 0x80; + } + } + data = (unsigned char)dst; + crc ^= (unsigned int)data << (16 - CHAR_BIT); + for ( j=0; joldlen))) { + return -1; + } + + obuf = buf; + memcpy(buf, cid->oldstuff, cid->oldlen); + mylen += cid->oldlen/2; + + for (x=0;xoldlen/2] = AST_XLAW(ubuf[x]); + + while (mylen >= 160) { + b = b2 = 0 ; + olen = mylen; + res = fsk_serie(&cid->fskd, buf, &mylen, &b); + + if (mylen < 0) { + ast_log(LOG_ERROR, "fsk_serie made mylen < 0 (%d)\n", mylen); + return -1; + } + + buf += (olen - mylen); + + if (res < 0) { + ast_log(LOG_NOTICE, "fsk_serie failed\n"); + return -1; + } + + if (res == 1) { + + b2 = b ; + b = b & 0x7f ; + + /* crc checksum calculation */ + if ( cid->sawflag > 1 ) { + cid->crc = calc_crc(cid->crc, (unsigned char)b2); + } + + /* Ignore invalid bytes */ + if (b > 0xff) { + continue; + } + + /* skip DLE if needed */ + if ( cid->sawflag > 0 ) { + if ( cid->sawflag != 5 && cid->skipflag == 0 && b == 0x10 ) { + cid->skipflag = 1 ; + continue ; + } + } + if ( cid->skipflag == 1 ) { + cid->skipflag = 0 ; + } + + /* caller id retrieval */ + switch(cid->sawflag) { + case 0: /* DLE */ + if (b == 0x10) { + cid->sawflag = 1; + cid->skipflag = 0; + cid->crc = 0; + } + break; + case 1: /* SOH */ + if (b == 0x01) { + cid->sawflag = 2; + } + break ; + case 2: /* HEADER */ + if (b == 0x07) { + cid->sawflag = 3; + } + break; + case 3: /* STX */ + if (b == 0x02) { + cid->sawflag = 4; + } + break; + case 4: /* SERVICE TYPE */ + if (b == 0x40) { + cid->sawflag = 5; + } + break; + case 5: /* Frame Length */ + cid->sawflag = 6; + break; + case 6: /* NUMBER TYPE */ + cid->sawflag = 7; + cid->pos = 0; + cid->rawdata[cid->pos++] = b; + break; + case 7: /* NUMBER LENGTH */ + cid->sawflag = 8; + cid->len = b; + if ( (cid->len+2) >= sizeof( cid->rawdata ) ) { + ast_log(LOG_WARNING, "too long caller id string\n" ) ; + return -1; + } + cid->rawdata[cid->pos++] = b; + break; + case 8: /* Retrieve message */ + cid->rawdata[cid->pos++] = b; + cid->len--; + if (cid->len<=0) { + cid->rawdata[cid->pos] = '\0'; + cid->sawflag = 9; + } + break; + case 9: /* ETX */ + cid->sawflag = 10; + break; + case 10: /* CRC Checksum 1 */ + cid->sawflag = 11; + break; + case 11: /* CRC Checksum 2 */ + cid->sawflag = 12; + if ( cid->crc != 0 ) { + ast_log(LOG_WARNING, "crc checksum error\n" ) ; + return -1; + } + /* extract caller id data */ + for (x=0; xpos; ) { + switch (cid->rawdata[x++]) { + case 0x02: /* caller id number */ + cid->number[0] = '\0'; + cid->name[0] = '\0'; + cid->flags = 0; + res = cid->rawdata[x++]; + ast_copy_string(cid->number, &cid->rawdata[x], res+1 ); + x += res; + break; + case 0x21: /* additional information */ + /* length */ + x++; + /* number type */ + switch (cid->rawdata[x]) { + case 0x00: /* unknown */ + case 0x01: /* international number */ + case 0x02: /* domestic number */ + case 0x03: /* network */ + case 0x04: /* local call */ + case 0x06: /* short dial number */ + case 0x07: /* reserved */ + default: /* reserved */ + if (option_debug > 1) + ast_log(LOG_DEBUG, "cid info:#1=%X\n", cid->rawdata[x]); + break ; + } + x++; + /* numbering plan octed 4 */ + x++; + /* numbering plan octed 5 */ + switch (cid->rawdata[x]) { + case 0x00: /* unknown */ + case 0x01: /* recommendation E.164 ISDN */ + case 0x03: /* recommendation X.121 */ + case 0x04: /* telex dial plan */ + case 0x08: /* domestic dial plan */ + case 0x09: /* private dial plan */ + case 0x05: /* reserved */ + default: /* reserved */ + if (option_debug > 1) + ast_log(LOG_DEBUG, "cid info:#2=%X\n", cid->rawdata[x]); + break ; + } + x++; + break ; + case 0x04: /* no callerid reason */ + /* length */ + x++; + /* no callerid reason code */ + switch (cid->rawdata[x]) { + case 'P': /* caller id denied by user */ + case 'O': /* service not available */ + case 'C': /* pay phone */ + case 'S': /* service congested */ + cid->flags |= CID_UNKNOWN_NUMBER; + if (option_debug > 1) + ast_log(LOG_DEBUG, "no cid reason:%c\n",cid->rawdata[x]); + break ; + } + x++; + break ; + case 0x09: /* dialed number */ + /* length */ + res = cid->rawdata[x++]; + /* dialed number */ + x += res; + break ; + case 0x22: /* dialed number additional information */ + /* length */ + x++; + /* number type */ + switch (cid->rawdata[x]) { + case 0x00: /* unknown */ + case 0x01: /* international number */ + case 0x02: /* domestic number */ + case 0x03: /* network */ + case 0x04: /* local call */ + case 0x06: /* short dial number */ + case 0x07: /* reserved */ + default: /* reserved */ + if (option_debug > 1) + ast_log(LOG_NOTICE, "did info:#1=%X\n", cid->rawdata[x]); + break ; + } + x++; + /* numbering plan octed 4 */ + x++; + /* numbering plan octed 5 */ + switch (cid->rawdata[x]) { + case 0x00: /* unknown */ + case 0x01: /* recommendation E.164 ISDN */ + case 0x03: /* recommendation X.121 */ + case 0x04: /* telex dial plan */ + case 0x08: /* domestic dial plan */ + case 0x09: /* private dial plan */ + case 0x05: /* reserved */ + default: /* reserved */ + if (option_debug > 1) + ast_log(LOG_DEBUG, "did info:#2=%X\n", cid->rawdata[x]); + break ; + } + x++; + break ; + } + } + return 1; + break; + default: + ast_log(LOG_ERROR, "invalid value in sawflag %d\n", cid->sawflag); + } + } + } + if (mylen) { + memcpy(cid->oldstuff, buf, mylen * 2); + cid->oldlen = mylen * 2; + } else + cid->oldlen = 0; + free(obuf); + return 0; +} + + +int callerid_feed(struct callerid_state *cid, unsigned char *ubuf, int len, int codec) +{ + int mylen = len; + int olen; + int b = 'X'; + int res; + int x; + short *buf; + short *obuf; + + if (!(buf = ast_calloc(1, 2 * len + cid->oldlen))) { + return -1; + } + + obuf = buf; + memcpy(buf, cid->oldstuff, cid->oldlen); + mylen += cid->oldlen/2; + + for (x=0;xoldlen/2] = AST_XLAW(ubuf[x]); + while(mylen >= 160) { + olen = mylen; + res = fsk_serie(&cid->fskd, buf, &mylen, &b); + if (mylen < 0) { + ast_log(LOG_ERROR, "fsk_serie made mylen < 0 (%d)\n", mylen); + free(obuf); + return -1; + } + buf += (olen - mylen); + if (res < 0) { + ast_log(LOG_NOTICE, "fsk_serie failed\n"); + return -1; + } + if (res == 1) { + /* Ignore invalid bytes */ + if (b > 0xff) + continue; + switch(cid->sawflag) { + case 0: /* Look for flag */ + if (b == 'U') + cid->sawflag = 2; + break; + case 2: /* Get lead-in */ + if ((b == 0x04) || (b == 0x80)) { + cid->type = b; + cid->sawflag = 3; + cid->cksum = b; + } + break; + case 3: /* Get length */ + /* Not a lead in. We're ready */ + cid->sawflag = 4; + cid->len = b; + cid->pos = 0; + cid->cksum += b; + break; + case 4: /* Retrieve message */ + if (cid->pos >= 128) { + ast_log(LOG_WARNING, "Caller ID too long???\n"); + free(obuf); + return -1; + } + cid->rawdata[cid->pos++] = b; + cid->len--; + cid->cksum += b; + if (!cid->len) { + cid->rawdata[cid->pos] = '\0'; + cid->sawflag = 5; + } + break; + case 5: /* Check checksum */ + if (b != (256 - (cid->cksum & 0xff))) { + ast_log(LOG_NOTICE, "Caller*ID failed checksum\n"); + /* Try again */ + cid->sawflag = 0; + break; + } + + cid->number[0] = '\0'; + cid->name[0] = '\0'; + /* If we get this far we're fine. */ + if (cid->type == 0x80) { + /* MDMF */ + /* Go through each element and process */ + for (x=0;x< cid->pos;) { + switch(cid->rawdata[x++]) { + case 1: + /* Date */ + break; + case 2: /* Number */ + case 3: /* Number (for Zebble) */ + case 4: /* Number */ + res = cid->rawdata[x]; + if (res > 32) { + ast_log(LOG_NOTICE, "Truncating long caller ID number from %d bytes to 32\n", cid->rawdata[x]); + res = 32; + } + if (ast_strlen_zero(cid->number)) { + memcpy(cid->number, cid->rawdata + x + 1, res); + /* Null terminate */ + cid->number[res] = '\0'; + } + break; + case 6: /* Stentor Call Qualifier (ie. Long Distance call) */ + break; + case 7: /* Name */ + case 8: /* Name */ + res = cid->rawdata[x]; + if (res > 32) { + ast_log(LOG_NOTICE, "Truncating long caller ID name from %d bytes to 32\n", cid->rawdata[x]); + res = 32; + } + memcpy(cid->name, cid->rawdata + x + 1, res); + cid->name[res] = '\0'; + break; + case 17: /* UK: Call type, 1=Voice Call, 2=Ringback when free, 129=Message waiting */ + case 19: /* UK: Network message system status (Number of messages waiting) */ + case 22: /* Something French */ + break; + default: + ast_log(LOG_NOTICE, "Unknown IE %d\n", cid->rawdata[x-1]); + } + x += cid->rawdata[x]; + x++; + } + } else { + /* SDMF */ + ast_copy_string(cid->number, cid->rawdata + 8, sizeof(cid->number)); + } + /* Update flags */ + cid->flags = 0; + if (!strcmp(cid->number, "P")) { + strcpy(cid->number, ""); + cid->flags |= CID_PRIVATE_NUMBER; + } else if (!strcmp(cid->number, "O") || ast_strlen_zero(cid->number)) { + strcpy(cid->number, ""); + cid->flags |= CID_UNKNOWN_NUMBER; + } + if (!strcmp(cid->name, "P")) { + strcpy(cid->name, ""); + cid->flags |= CID_PRIVATE_NAME; + } else if (!strcmp(cid->name, "O") || ast_strlen_zero(cid->name)) { + strcpy(cid->name, ""); + cid->flags |= CID_UNKNOWN_NAME; + } + free(obuf); + return 1; + break; + default: + ast_log(LOG_ERROR, "Dunno what to do with a digit in sawflag %d\n", cid->sawflag); + } + } + } + if (mylen) { + memcpy(cid->oldstuff, buf, mylen * 2); + cid->oldlen = mylen * 2; + } else + cid->oldlen = 0; + free(obuf); + return 0; +} + +void callerid_free(struct callerid_state *cid) +{ + free(cid); +} + +static int callerid_genmsg(char *msg, int size, const char *number, const char *name, int flags) +{ + time_t t; + struct tm tm; + char *ptr; + int res; + int i,x; + /* Get the time */ + time(&t); + localtime_r(&t,&tm); + + ptr = msg; + + /* Format time and message header */ + res = snprintf(ptr, size, "\001\010%02d%02d%02d%02d", tm.tm_mon + 1, + tm.tm_mday, tm.tm_hour, tm.tm_min); + size -= res; + ptr += res; + if (ast_strlen_zero(number) || (flags & CID_UNKNOWN_NUMBER)) { + /* Indicate number not known */ + res = snprintf(ptr, size, "\004\001O"); + size -= res; + ptr += res; + } else if (flags & CID_PRIVATE_NUMBER) { + /* Indicate number is private */ + res = snprintf(ptr, size, "\004\001P"); + size -= res; + ptr += res; + } else { + /* Send up to 16 digits of number MAX */ + i = strlen(number); + if (i > 16) i = 16; + res = snprintf(ptr, size, "\002%c", i); + size -= res; + ptr += res; + for (x = 0; x < i; x++) + ptr[x] = number[x]; + ptr[i] = '\0'; + ptr += i; + size -= i; + } + + if (ast_strlen_zero(name) || (flags & CID_UNKNOWN_NAME)) { + /* Indicate name not known */ + res = snprintf(ptr, size, "\010\001O"); + size -= res; + ptr += res; + } else if (flags & CID_PRIVATE_NAME) { + /* Indicate name is private */ + res = snprintf(ptr, size, "\010\001P"); + size -= res; + ptr += res; + } else { + /* Send up to 16 digits of name MAX */ + i = strlen(name); + if (i > 16) i = 16; + res = snprintf(ptr, size, "\007%c", i); + size -= res; + ptr += res; + for (x=0;x 123 ' foo bar ' (with spaces around) + " foo bar " NULL 'foo bar' (without spaces around) + " foo bar <123>" 123 '" foo bar' + The parsing of leading and trailing space/quotes should be more consistent. +*/ +int ast_callerid_parse(char *instr, char **name, char **location) +{ + char *ns, *ne, *ls, *le; + + /* Try "name" format or name format */ + if ((ls = strchr(instr, '<')) && (le = strchr(ls, '>'))) { + *ls = *le = '\0'; /* location found, trim off the brackets */ + *location = ls + 1; /* and this is the result */ + if ((ns = strchr(instr, '"')) && (ne = strchr(ns + 1, '"'))) { + *ns = *ne = '\0'; /* trim off the quotes */ + *name = ns + 1; /* and this is the name */ + } else { /* no quotes, trim off leading and trailing spaces */ + *name = ast_skip_blanks(instr); + ast_trim_blanks(*name); + } + } else { /* no valid brackets */ + char tmp[256]; + + ast_copy_string(tmp, instr, sizeof(tmp)); + ast_shrink_phone_number(tmp); + if (ast_isphonenumber(tmp)) { /* Assume it's just a location */ + *name = NULL; + strcpy(instr, tmp); /* safe, because tmp will always be the same size or smaller than instr */ + *location = instr; + } else { /* Assume it's just a name. */ + *location = NULL; + if ((ns = strchr(instr, '"')) && (ne = strchr(ns + 1, '"'))) { + *ns = *ne = '\0'; /* trim off the quotes */ + *name = ns + 1; /* and this is the name */ + } else { /* no quotes, trim off leading and trailing spaces */ + *name = ast_skip_blanks(instr); + ast_trim_blanks(*name); + } + } + } + return 0; +} + +static int __ast_callerid_generate(unsigned char *buf, const char *name, const char *number, int callwaiting, int codec) +{ + if (ast_strlen_zero(name)) + name = NULL; + if (ast_strlen_zero(number)) + number = NULL; + return callerid_generate(buf, number, name, 0, callwaiting, codec); +} + +int ast_callerid_generate(unsigned char *buf, const char *name, const char *number, int codec) +{ + return __ast_callerid_generate(buf, name, number, 0, codec); +} + +int ast_callerid_callwaiting_generate(unsigned char *buf, const char *name, const char *number, int codec) +{ + return __ast_callerid_generate(buf, name, number, 1, codec); +} + +char *ast_callerid_merge(char *buf, int bufsiz, const char *name, const char *num, const char *unknown) +{ + if (!unknown) + unknown = ""; + if (name && num) + snprintf(buf, bufsiz, "\"%s\" <%s>", name, num); + else if (name) + ast_copy_string(buf, name, bufsiz); + else if (num) + ast_copy_string(buf, num, bufsiz); + else + ast_copy_string(buf, unknown, bufsiz); + return buf; +} + +int ast_callerid_split(const char *buf, char *name, int namelen, char *num, int numlen) +{ + char *tmp; + char *l = NULL, *n = NULL; + + tmp = ast_strdupa(buf); + ast_callerid_parse(tmp, &n, &l); + if (n) + ast_copy_string(name, n, namelen); + else + name[0] = '\0'; + if (l) { + ast_shrink_phone_number(l); + ast_copy_string(num, l, numlen); + } else + num[0] = '\0'; + return 0; +} + +/*! \brief Translation table for Caller ID Presentation settings */ +static struct { + int val; + char *name; + char *description; +} pres_types[] = { + { AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED, "allowed_not_screened", "Presentation Allowed, Not Screened"}, + { AST_PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN, "allowed_passed_screen", "Presentation Allowed, Passed Screen"}, + { AST_PRES_ALLOWED_USER_NUMBER_FAILED_SCREEN, "allowed_failed_screen", "Presentation Allowed, Failed Screen"}, + { AST_PRES_ALLOWED_NETWORK_NUMBER, "allowed", "Presentation Allowed, Network Number"}, + { AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED, "prohib_not_screened", "Presentation Prohibited, Not Screened"}, + { AST_PRES_PROHIB_USER_NUMBER_PASSED_SCREEN, "prohib_passed_screen", "Presentation Prohibited, Passed Screen"}, + { AST_PRES_PROHIB_USER_NUMBER_FAILED_SCREEN, "prohib_failed_screen", "Presentation Prohibited, Failed Screen"}, + { AST_PRES_PROHIB_NETWORK_NUMBER, "prohib", "Presentation Prohibited, Network Number"}, + { AST_PRES_NUMBER_NOT_AVAILABLE, "unavailable", "Number Unavailable"}, +}; + +/*! \brief Convert caller ID text code to value + used in config file parsing + \param data text string + \return value AST_PRES_ from callerid.h +*/ +int ast_parse_caller_presentation(const char *data) +{ + int i; + + for (i = 0; i < ((sizeof(pres_types) / sizeof(pres_types[0]))); i++) { + if (!strcasecmp(pres_types[i].name, data)) + return pres_types[i].val; + } + + return -1; +} + +/*! \brief Convert caller ID pres value to explanatory string + \param data value (see callerid.h AST_PRES_ ) + \return string for human presentation +*/ +const char *ast_describe_caller_presentation(int data) +{ + int i; + + for (i = 0; i < ((sizeof(pres_types) / sizeof(pres_types[0]))); i++) { + if (pres_types[i].val == data) + return pres_types[i].description; + } + + return "unknown"; +} diff --git a/main/cdr.c b/main/cdr.c new file mode 100644 index 000000000..93c63751f --- /dev/null +++ b/main/cdr.c @@ -0,0 +1,1175 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Call Detail Record API + * + * \author Mark Spencer + * + * \note Includes code and algorithms from the Zapata library. + * + * \note We do a lot of checking here in the CDR code to try to be sure we don't ever let a CDR slip + * through our fingers somehow. If someone allocates a CDR, it must be completely handled normally + * or a WARNING shall be logged, so that we can best keep track of any escape condition where the CDR + * isn't properly generated and posted. + */ + + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/channel.h" +#include "asterisk/cdr.h" +#include "asterisk/logger.h" +#include "asterisk/callerid.h" +#include "asterisk/causes.h" +#include "asterisk/options.h" +#include "asterisk/linkedlists.h" +#include "asterisk/utils.h" +#include "asterisk/sched.h" +#include "asterisk/config.h" +#include "asterisk/cli.h" +#include "asterisk/stringfields.h" + +/*! Default AMA flag for billing records (CDR's) */ +int ast_default_amaflags = AST_CDR_DOCUMENTATION; +char ast_default_accountcode[AST_MAX_ACCOUNT_CODE] = ""; + +struct ast_cdr_beitem { + char name[20]; + char desc[80]; + ast_cdrbe be; + AST_LIST_ENTRY(ast_cdr_beitem) list; +}; + +static AST_LIST_HEAD_STATIC(be_list, ast_cdr_beitem); + +struct ast_cdr_batch_item { + struct ast_cdr *cdr; + struct ast_cdr_batch_item *next; +}; + +static struct ast_cdr_batch { + int size; + struct ast_cdr_batch_item *head; + struct ast_cdr_batch_item *tail; +} *batch = NULL; + +static struct sched_context *sched; +static int cdr_sched = -1; +static pthread_t cdr_thread = AST_PTHREADT_NULL; + +#define BATCH_SIZE_DEFAULT 100 +#define BATCH_TIME_DEFAULT 300 +#define BATCH_SCHEDULER_ONLY_DEFAULT 0 +#define BATCH_SAFE_SHUTDOWN_DEFAULT 1 + +static int enabled; +static int batchmode; +static int batchsize; +static int batchtime; +static int batchscheduleronly; +static int batchsafeshutdown; + +AST_MUTEX_DEFINE_STATIC(cdr_batch_lock); + +/* these are used to wake up the CDR thread when there's work to do */ +AST_MUTEX_DEFINE_STATIC(cdr_pending_lock); +static ast_cond_t cdr_pending_cond; + + +/*! Register a CDR driver. Each registered CDR driver generates a CDR + \return 0 on success, -1 on failure +*/ +int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be) +{ + struct ast_cdr_beitem *i; + + if (!name) + return -1; + if (!be) { + ast_log(LOG_WARNING, "CDR engine '%s' lacks backend\n", name); + return -1; + } + + AST_LIST_LOCK(&be_list); + AST_LIST_TRAVERSE(&be_list, i, list) { + if (!strcasecmp(name, i->name)) + break; + } + AST_LIST_UNLOCK(&be_list); + + if (i) { + ast_log(LOG_WARNING, "Already have a CDR backend called '%s'\n", name); + return -1; + } + + if (!(i = ast_calloc(1, sizeof(*i)))) + return -1; + + i->be = be; + ast_copy_string(i->name, name, sizeof(i->name)); + ast_copy_string(i->desc, desc, sizeof(i->desc)); + + AST_LIST_LOCK(&be_list); + AST_LIST_INSERT_HEAD(&be_list, i, list); + AST_LIST_UNLOCK(&be_list); + + return 0; +} + +/*! unregister a CDR driver */ +void ast_cdr_unregister(const char *name) +{ + struct ast_cdr_beitem *i = NULL; + + AST_LIST_LOCK(&be_list); + AST_LIST_TRAVERSE_SAFE_BEGIN(&be_list, i, list) { + if (!strcasecmp(name, i->name)) { + AST_LIST_REMOVE_CURRENT(&be_list, list); + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Unregistered '%s' CDR backend\n", name); + free(i); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&be_list); +} + +/*! Duplicate a CDR record + \returns Pointer to new CDR record +*/ +struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr) +{ + struct ast_cdr *newcdr = ast_cdr_alloc(); + + if (!newcdr) + return NULL; + + memcpy(newcdr, cdr, sizeof(*newcdr)); + /* The varshead is unusable, volatile even, after the memcpy so we take care of that here */ + memset(&newcdr->varshead, 0, sizeof(newcdr->varshead)); + ast_cdr_copy_vars(newcdr, cdr); + newcdr->next = NULL; + + return newcdr; +} + +static const char *ast_cdr_getvar_internal(struct ast_cdr *cdr, const char *name, int recur) +{ + if (ast_strlen_zero(name)) + return NULL; + + for (; cdr; cdr = recur ? cdr->next : NULL) { + struct ast_var_t *variables; + struct varshead *headp = &cdr->varshead; + AST_LIST_TRAVERSE(headp, variables, entries) { + if (!strcasecmp(name, ast_var_name(variables))) + return ast_var_value(variables); + } + } + + return NULL; +} + +static void cdr_get_tv(struct timeval tv, const char *fmt, char *buf, int bufsize) +{ + if (fmt == NULL) { /* raw mode */ + snprintf(buf, bufsize, "%ld.%06ld", (long)tv.tv_sec, (long)tv.tv_usec); + } else { + time_t t = tv.tv_sec; + if (t) { + struct tm tm; + localtime_r(&t, &tm); + strftime(buf, bufsize, fmt, &tm); + } + } +} + +/*! CDR channel variable retrieval */ +void ast_cdr_getvar(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int recur, int raw) +{ + const char *fmt = "%Y-%m-%d %T"; + const char *varbuf; + + *ret = NULL; + /* special vars (the ones from the struct ast_cdr when requested by name) + I'd almost say we should convert all the stringed vals to vars */ + + if (!strcasecmp(name, "clid")) + ast_copy_string(workspace, cdr->clid, workspacelen); + else if (!strcasecmp(name, "src")) + ast_copy_string(workspace, cdr->src, workspacelen); + else if (!strcasecmp(name, "dst")) + ast_copy_string(workspace, cdr->dst, workspacelen); + else if (!strcasecmp(name, "dcontext")) + ast_copy_string(workspace, cdr->dcontext, workspacelen); + else if (!strcasecmp(name, "channel")) + ast_copy_string(workspace, cdr->channel, workspacelen); + else if (!strcasecmp(name, "dstchannel")) + ast_copy_string(workspace, cdr->dstchannel, workspacelen); + else if (!strcasecmp(name, "lastapp")) + ast_copy_string(workspace, cdr->lastapp, workspacelen); + else if (!strcasecmp(name, "lastdata")) + ast_copy_string(workspace, cdr->lastdata, workspacelen); + else if (!strcasecmp(name, "start")) + cdr_get_tv(cdr->start, raw ? NULL : fmt, workspace, workspacelen); + else if (!strcasecmp(name, "answer")) + cdr_get_tv(cdr->answer, raw ? NULL : fmt, workspace, workspacelen); + else if (!strcasecmp(name, "end")) + cdr_get_tv(cdr->end, raw ? NULL : fmt, workspace, workspacelen); + else if (!strcasecmp(name, "duration")) + snprintf(workspace, workspacelen, "%ld", cdr->duration); + else if (!strcasecmp(name, "billsec")) + snprintf(workspace, workspacelen, "%ld", cdr->billsec); + else if (!strcasecmp(name, "disposition")) { + if (raw) { + snprintf(workspace, workspacelen, "%ld", cdr->disposition); + } else { + ast_copy_string(workspace, ast_cdr_disp2str(cdr->disposition), workspacelen); + } + } else if (!strcasecmp(name, "amaflags")) { + if (raw) { + snprintf(workspace, workspacelen, "%ld", cdr->amaflags); + } else { + ast_copy_string(workspace, ast_cdr_flags2str(cdr->amaflags), workspacelen); + } + } else if (!strcasecmp(name, "accountcode")) + ast_copy_string(workspace, cdr->accountcode, workspacelen); + else if (!strcasecmp(name, "uniqueid")) + ast_copy_string(workspace, cdr->uniqueid, workspacelen); + else if (!strcasecmp(name, "userfield")) + ast_copy_string(workspace, cdr->userfield, workspacelen); + else if ((varbuf = ast_cdr_getvar_internal(cdr, name, recur))) + ast_copy_string(workspace, varbuf, workspacelen); + else + workspace[0] = '\0'; + + if (!ast_strlen_zero(workspace)) + *ret = workspace; +} + +/* readonly cdr variables */ +static const char *cdr_readonly_vars[] = { "clid", "src", "dst", "dcontext", "channel", "dstchannel", + "lastapp", "lastdata", "start", "answer", "end", "duration", + "billsec", "disposition", "amaflags", "accountcode", "uniqueid", + "userfield", NULL }; +/*! Set a CDR channel variable + \note You can't set the CDR variables that belong to the actual CDR record, like "billsec". +*/ +int ast_cdr_setvar(struct ast_cdr *cdr, const char *name, const char *value, int recur) +{ + struct ast_var_t *newvariable; + struct varshead *headp; + int x; + + for(x = 0; cdr_readonly_vars[x]; x++) { + if (!strcasecmp(name, cdr_readonly_vars[x])) { + ast_log(LOG_ERROR, "Attempt to set a read-only variable!.\n"); + return -1; + } + } + + if (!cdr) { + ast_log(LOG_ERROR, "Attempt to set a variable on a nonexistent CDR record.\n"); + return -1; + } + + for (; cdr; cdr = recur ? cdr->next : NULL) { + headp = &cdr->varshead; + AST_LIST_TRAVERSE_SAFE_BEGIN(headp, newvariable, entries) { + if (!strcasecmp(ast_var_name(newvariable), name)) { + /* there is already such a variable, delete it */ + AST_LIST_REMOVE_CURRENT(headp, entries); + ast_var_delete(newvariable); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + + if (value) { + newvariable = ast_var_assign(name, value); + AST_LIST_INSERT_HEAD(headp, newvariable, entries); + } + } + + return 0; +} + +int ast_cdr_copy_vars(struct ast_cdr *to_cdr, struct ast_cdr *from_cdr) +{ + struct ast_var_t *variables, *newvariable = NULL; + struct varshead *headpa, *headpb; + const char *var, *val; + int x = 0; + + headpa = &from_cdr->varshead; + headpb = &to_cdr->varshead; + + AST_LIST_TRAVERSE(headpa,variables,entries) { + if (variables && + (var = ast_var_name(variables)) && (val = ast_var_value(variables)) && + !ast_strlen_zero(var) && !ast_strlen_zero(val)) { + newvariable = ast_var_assign(var, val); + AST_LIST_INSERT_HEAD(headpb, newvariable, entries); + x++; + } + } + + return x; +} + +int ast_cdr_serialize_variables(struct ast_cdr *cdr, char *buf, size_t size, char delim, char sep, int recur) +{ + struct ast_var_t *variables; + const char *var, *val; + char *tmp; + char workspace[256]; + int total = 0, x = 0, i; + + memset(buf, 0, size); + + for (; cdr; cdr = recur ? cdr->next : NULL) { + if (++x > 1) + ast_build_string(&buf, &size, "\n"); + + AST_LIST_TRAVERSE(&cdr->varshead, variables, entries) { + if (variables && + (var = ast_var_name(variables)) && (val = ast_var_value(variables)) && + !ast_strlen_zero(var) && !ast_strlen_zero(val)) { + if (ast_build_string(&buf, &size, "level %d: %s%c%s%c", x, var, delim, val, sep)) { + ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n"); + break; + } else + total++; + } else + break; + } + + for (i = 0; cdr_readonly_vars[i]; i++) { + ast_cdr_getvar(cdr, cdr_readonly_vars[i], &tmp, workspace, sizeof(workspace), 0, 0); + if (!tmp) + continue; + + if (ast_build_string(&buf, &size, "level %d: %s%c%s%c", x, cdr_readonly_vars[i], delim, tmp, sep)) { + ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n"); + break; + } else + total++; + } + } + + return total; +} + + +void ast_cdr_free_vars(struct ast_cdr *cdr, int recur) +{ + + /* clear variables */ + for (; cdr; cdr = recur ? cdr->next : NULL) { + struct ast_var_t *vardata; + struct varshead *headp = &cdr->varshead; + while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries))) + ast_var_delete(vardata); + } +} + +/*! \brief print a warning if cdr already posted */ +static void check_post(struct ast_cdr *cdr) +{ + if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED)) + ast_log(LOG_WARNING, "CDR on channel '%s' already posted\n", S_OR(cdr->channel, "")); +} + +/*! \brief print a warning if cdr already started */ +static void check_start(struct ast_cdr *cdr) +{ + if (!ast_tvzero(cdr->start)) + ast_log(LOG_WARNING, "CDR on channel '%s' already started\n", S_OR(cdr->channel, "")); +} + +void ast_cdr_free(struct ast_cdr *cdr) +{ + + while (cdr) { + struct ast_cdr *next = cdr->next; + char *chan = S_OR(cdr->channel, ""); + if (!ast_test_flag(cdr, AST_CDR_FLAG_POSTED) && !ast_test_flag(cdr, AST_CDR_FLAG_POST_DISABLED)) + ast_log(LOG_WARNING, "CDR on channel '%s' not posted\n", chan); + if (ast_tvzero(cdr->end)) + ast_log(LOG_WARNING, "CDR on channel '%s' lacks end\n", chan); + if (ast_tvzero(cdr->start)) + ast_log(LOG_WARNING, "CDR on channel '%s' lacks start\n", chan); + + ast_cdr_free_vars(cdr, 0); + free(cdr); + cdr = next; + } +} + +struct ast_cdr *ast_cdr_alloc(void) +{ + return ast_calloc(1, sizeof(struct ast_cdr)); +} + +void ast_cdr_start(struct ast_cdr *cdr) +{ + char *chan; + + for (; cdr; cdr = cdr->next) { + if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { + chan = S_OR(cdr->channel, ""); + check_post(cdr); + check_start(cdr); + cdr->start = ast_tvnow(); + } + } +} + +void ast_cdr_answer(struct ast_cdr *cdr) +{ + + for (; cdr; cdr = cdr->next) { + check_post(cdr); + if (cdr->disposition < AST_CDR_ANSWERED) + cdr->disposition = AST_CDR_ANSWERED; + if (ast_tvzero(cdr->answer)) + cdr->answer = ast_tvnow(); + } +} + +void ast_cdr_busy(struct ast_cdr *cdr) +{ + + for (; cdr; cdr = cdr->next) { + if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { + check_post(cdr); + if (cdr->disposition < AST_CDR_BUSY) + cdr->disposition = AST_CDR_BUSY; + } + } +} + +void ast_cdr_failed(struct ast_cdr *cdr) +{ + for (; cdr; cdr = cdr->next) { + check_post(cdr); + if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { + if (cdr->disposition < AST_CDR_FAILED) + cdr->disposition = AST_CDR_FAILED; + } + } +} + +int ast_cdr_disposition(struct ast_cdr *cdr, int cause) +{ + int res = 0; + + for (; cdr; cdr = cdr->next) { + switch(cause) { + case AST_CAUSE_BUSY: + ast_cdr_busy(cdr); + break; + case AST_CAUSE_FAILURE: + ast_cdr_failed(cdr); + break; + case AST_CAUSE_NORMAL: + break; + case AST_CAUSE_NOTDEFINED: + res = -1; + break; + default: + res = -1; + ast_log(LOG_WARNING, "Cause not handled\n"); + } + } + return res; +} + +void ast_cdr_setdestchan(struct ast_cdr *cdr, const char *chann) +{ + for (; cdr; cdr = cdr->next) { + check_post(cdr); + if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) + ast_copy_string(cdr->dstchannel, chann, sizeof(cdr->dstchannel)); + } +} + +void ast_cdr_setapp(struct ast_cdr *cdr, char *app, char *data) +{ + + for (; cdr; cdr = cdr->next) { + if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { + check_post(cdr); + if (!app) + app = ""; + ast_copy_string(cdr->lastapp, app, sizeof(cdr->lastapp)); + if (!data) + data = ""; + ast_copy_string(cdr->lastdata, data, sizeof(cdr->lastdata)); + } + } +} + +/* set cid info for one record */ +static void set_one_cid(struct ast_cdr *cdr, struct ast_channel *c) +{ + /* Grab source from ANI or normal Caller*ID */ + const char *num = S_OR(c->cid.cid_ani, c->cid.cid_num); + + if (!ast_strlen_zero(c->cid.cid_name)) { + if (!ast_strlen_zero(num)) /* both name and number */ + snprintf(cdr->clid, sizeof(cdr->clid), "\"%s\" <%s>", c->cid.cid_name, num); + else /* only name */ + ast_copy_string(cdr->clid, c->cid.cid_name, sizeof(cdr->clid)); + } else if (!ast_strlen_zero(num)) { /* only number */ + ast_copy_string(cdr->clid, num, sizeof(cdr->clid)); + } else { /* nothing known */ + cdr->clid[0] = '\0'; + } + ast_copy_string(cdr->src, S_OR(num, ""), sizeof(cdr->src)); + +} +int ast_cdr_setcid(struct ast_cdr *cdr, struct ast_channel *c) +{ + for (; cdr; cdr = cdr->next) { + if (ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) + set_one_cid(cdr, c); + } + return 0; +} + +int ast_cdr_init(struct ast_cdr *cdr, struct ast_channel *c) +{ + char *chan; + + for ( ; cdr ; cdr = cdr->next) { + if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { + chan = S_OR(cdr->channel, ""); + if (!ast_strlen_zero(cdr->channel)) + ast_log(LOG_WARNING, "CDR already initialized on '%s'\n", chan); + ast_copy_string(cdr->channel, c->name, sizeof(cdr->channel)); + set_one_cid(cdr, c); + + cdr->disposition = (c->_state == AST_STATE_UP) ? AST_CDR_ANSWERED : AST_CDR_NOANSWER; + cdr->amaflags = c->amaflags ? c->amaflags : ast_default_amaflags; + ast_copy_string(cdr->accountcode, c->accountcode, sizeof(cdr->accountcode)); + /* Destination information */ + ast_copy_string(cdr->dst, c->exten, sizeof(cdr->dst)); + ast_copy_string(cdr->dcontext, c->context, sizeof(cdr->dcontext)); + /* Unique call identifier */ + ast_copy_string(cdr->uniqueid, c->uniqueid, sizeof(cdr->uniqueid)); + } + } + return 0; +} + +void ast_cdr_end(struct ast_cdr *cdr) +{ + for ( ; cdr ; cdr = cdr->next) { + check_post(cdr); + if (ast_tvzero(cdr->end)) + cdr->end = ast_tvnow(); + if (ast_tvzero(cdr->start)) { + ast_log(LOG_WARNING, "CDR on channel '%s' has not started\n", S_OR(cdr->channel, "")); + cdr->disposition = AST_CDR_FAILED; + } else + cdr->duration = cdr->end.tv_sec - cdr->start.tv_sec; + cdr->billsec = ast_tvzero(cdr->answer) ? 0 : cdr->end.tv_sec - cdr->answer.tv_sec; + } +} + +char *ast_cdr_disp2str(int disposition) +{ + switch (disposition) { + case AST_CDR_NOANSWER: + return "NO ANSWER"; + case AST_CDR_FAILED: + return "FAILED"; + case AST_CDR_BUSY: + return "BUSY"; + case AST_CDR_ANSWERED: + return "ANSWERED"; + } + return "UNKNOWN"; +} + +/*! Converts AMA flag to printable string */ +char *ast_cdr_flags2str(int flag) +{ + switch(flag) { + case AST_CDR_OMIT: + return "OMIT"; + case AST_CDR_BILLING: + return "BILLING"; + case AST_CDR_DOCUMENTATION: + return "DOCUMENTATION"; + } + return "Unknown"; +} + +int ast_cdr_setaccount(struct ast_channel *chan, const char *account) +{ + struct ast_cdr *cdr = chan->cdr; + + ast_string_field_set(chan, accountcode, account); + for ( ; cdr ; cdr = cdr->next) { + if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) + ast_copy_string(cdr->accountcode, chan->accountcode, sizeof(cdr->accountcode)); + } + return 0; +} + +int ast_cdr_setamaflags(struct ast_channel *chan, const char *flag) +{ + struct ast_cdr *cdr; + int newflag = ast_cdr_amaflags2int(flag); + if (newflag) { + for (cdr = chan->cdr; cdr; cdr = cdr->next) + cdr->amaflags = newflag; + } + + return 0; +} + +int ast_cdr_setuserfield(struct ast_channel *chan, const char *userfield) +{ + struct ast_cdr *cdr = chan->cdr; + + for ( ; cdr ; cdr = cdr->next) { + if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) + ast_copy_string(cdr->userfield, userfield, sizeof(cdr->userfield)); + } + + return 0; +} + +int ast_cdr_appenduserfield(struct ast_channel *chan, const char *userfield) +{ + struct ast_cdr *cdr = chan->cdr; + + for ( ; cdr ; cdr = cdr->next) { + int len = strlen(cdr->userfield); + + if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) + strncpy(cdr->userfield+len, userfield, sizeof(cdr->userfield) - len - 1); + } + + return 0; +} + +int ast_cdr_update(struct ast_channel *c) +{ + struct ast_cdr *cdr = c->cdr; + + for ( ; cdr ; cdr = cdr->next) { + if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { + set_one_cid(cdr, c); + + /* Copy account code et-al */ + ast_copy_string(cdr->accountcode, c->accountcode, sizeof(cdr->accountcode)); + /* Destination information */ /* XXX privilege macro* ? */ + ast_copy_string(cdr->dst, S_OR(c->macroexten, c->exten), sizeof(cdr->dst)); + ast_copy_string(cdr->dcontext, S_OR(c->macrocontext, c->context), sizeof(cdr->dcontext)); + } + } + + return 0; +} + +int ast_cdr_amaflags2int(const char *flag) +{ + if (!strcasecmp(flag, "default")) + return 0; + if (!strcasecmp(flag, "omit")) + return AST_CDR_OMIT; + if (!strcasecmp(flag, "billing")) + return AST_CDR_BILLING; + if (!strcasecmp(flag, "documentation")) + return AST_CDR_DOCUMENTATION; + return -1; +} + +static void post_cdr(struct ast_cdr *cdr) +{ + char *chan; + struct ast_cdr_beitem *i; + + for ( ; cdr ; cdr = cdr->next) { + chan = S_OR(cdr->channel, ""); + check_post(cdr); + if (ast_tvzero(cdr->end)) + ast_log(LOG_WARNING, "CDR on channel '%s' lacks end\n", chan); + if (ast_tvzero(cdr->start)) + ast_log(LOG_WARNING, "CDR on channel '%s' lacks start\n", chan); + ast_set_flag(cdr, AST_CDR_FLAG_POSTED); + AST_LIST_LOCK(&be_list); + AST_LIST_TRAVERSE(&be_list, i, list) { + i->be(cdr); + } + AST_LIST_UNLOCK(&be_list); + } +} + +void ast_cdr_reset(struct ast_cdr *cdr, struct ast_flags *_flags) +{ + struct ast_cdr *dup; + struct ast_flags flags = { 0 }; + + if (_flags) + ast_copy_flags(&flags, _flags, AST_FLAGS_ALL); + + for ( ; cdr ; cdr = cdr->next) { + /* Detach if post is requested */ + if (ast_test_flag(&flags, AST_CDR_FLAG_LOCKED) || !ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { + if (ast_test_flag(&flags, AST_CDR_FLAG_POSTED)) { + ast_cdr_end(cdr); + if ((dup = ast_cdr_dup(cdr))) { + ast_cdr_detach(dup); + } + ast_set_flag(cdr, AST_CDR_FLAG_POSTED); + } + + /* clear variables */ + if (!ast_test_flag(&flags, AST_CDR_FLAG_KEEP_VARS)) { + ast_cdr_free_vars(cdr, 0); + } + + /* Reset to initial state */ + ast_clear_flag(cdr, AST_FLAGS_ALL); + memset(&cdr->start, 0, sizeof(cdr->start)); + memset(&cdr->end, 0, sizeof(cdr->end)); + memset(&cdr->answer, 0, sizeof(cdr->answer)); + cdr->billsec = 0; + cdr->duration = 0; + ast_cdr_start(cdr); + cdr->disposition = AST_CDR_NOANSWER; + } + } +} + +struct ast_cdr *ast_cdr_append(struct ast_cdr *cdr, struct ast_cdr *newcdr) +{ + struct ast_cdr *ret; + + if (cdr) { + ret = cdr; + + while (cdr->next) + cdr = cdr->next; + cdr->next = newcdr; + } else { + ret = newcdr; + } + + return ret; +} + +/*! \note Don't call without cdr_batch_lock */ +static void reset_batch(void) +{ + batch->size = 0; + batch->head = NULL; + batch->tail = NULL; +} + +/*! \note Don't call without cdr_batch_lock */ +static int init_batch(void) +{ + /* This is the single meta-batch used to keep track of all CDRs during the entire life of the program */ + if (!(batch = ast_malloc(sizeof(*batch)))) + return -1; + + reset_batch(); + + return 0; +} + +static void *do_batch_backend_process(void *data) +{ + struct ast_cdr_batch_item *processeditem; + struct ast_cdr_batch_item *batchitem = data; + + /* Push each CDR into storage mechanism(s) and free all the memory */ + while (batchitem) { + post_cdr(batchitem->cdr); + ast_cdr_free(batchitem->cdr); + processeditem = batchitem; + batchitem = batchitem->next; + free(processeditem); + } + + return NULL; +} + +void ast_cdr_submit_batch(int shutdown) +{ + struct ast_cdr_batch_item *oldbatchitems = NULL; + pthread_attr_t attr; + pthread_t batch_post_thread = AST_PTHREADT_NULL; + + /* if there's no batch, or no CDRs in the batch, then there's nothing to do */ + if (!batch || !batch->head) + return; + + /* move the old CDRs aside, and prepare a new CDR batch */ + ast_mutex_lock(&cdr_batch_lock); + oldbatchitems = batch->head; + reset_batch(); + ast_mutex_unlock(&cdr_batch_lock); + + /* if configured, spawn a new thread to post these CDRs, + also try to save as much as possible if we are shutting down safely */ + if (batchscheduleronly || shutdown) { + if (option_debug) + ast_log(LOG_DEBUG, "CDR single-threaded batch processing begins now\n"); + do_batch_backend_process(oldbatchitems); + } else { + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (ast_pthread_create(&batch_post_thread, &attr, do_batch_backend_process, oldbatchitems)) { + ast_log(LOG_WARNING, "CDR processing thread could not detach, now trying in this thread\n"); + do_batch_backend_process(oldbatchitems); + } else { + if (option_debug) + ast_log(LOG_DEBUG, "CDR multi-threaded batch processing begins now\n"); + } + } +} + +static int submit_scheduled_batch(void *data) +{ + ast_cdr_submit_batch(0); + /* manually reschedule from this point in time */ + cdr_sched = ast_sched_add(sched, batchtime * 1000, submit_scheduled_batch, NULL); + /* returning zero so the scheduler does not automatically reschedule */ + return 0; +} + +static void submit_unscheduled_batch(void) +{ + /* this is okay since we are not being called from within the scheduler */ + if (cdr_sched > -1) + ast_sched_del(sched, cdr_sched); + /* schedule the submission to occur ASAP (1 ms) */ + cdr_sched = ast_sched_add(sched, 1, submit_scheduled_batch, NULL); + /* signal the do_cdr thread to wakeup early and do some work (that lazy thread ;) */ + ast_mutex_lock(&cdr_pending_lock); + ast_cond_signal(&cdr_pending_cond); + ast_mutex_unlock(&cdr_pending_lock); +} + +void ast_cdr_detach(struct ast_cdr *cdr) +{ + struct ast_cdr_batch_item *newtail; + int curr; + + /* maybe they disabled CDR stuff completely, so just drop it */ + if (!enabled) { + if (option_debug) + ast_log(LOG_DEBUG, "Dropping CDR !\n"); + ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED); + ast_cdr_free(cdr); + return; + } + + /* post stuff immediately if we are not in batch mode, this is legacy behaviour */ + if (!batchmode) { + post_cdr(cdr); + ast_cdr_free(cdr); + return; + } + + /* otherwise, each CDR gets put into a batch list (at the end) */ + if (option_debug) + ast_log(LOG_DEBUG, "CDR detaching from this thread\n"); + + /* we'll need a new tail for every CDR */ + if (!(newtail = ast_calloc(1, sizeof(*newtail)))) { + post_cdr(cdr); + ast_cdr_free(cdr); + return; + } + + /* don't traverse a whole list (just keep track of the tail) */ + ast_mutex_lock(&cdr_batch_lock); + if (!batch) + init_batch(); + if (!batch->head) { + /* new batch is empty, so point the head at the new tail */ + batch->head = newtail; + } else { + /* already got a batch with something in it, so just append a new tail */ + batch->tail->next = newtail; + } + newtail->cdr = cdr; + batch->tail = newtail; + curr = batch->size++; + ast_mutex_unlock(&cdr_batch_lock); + + /* if we have enough stuff to post, then do it */ + if (curr >= (batchsize - 1)) + submit_unscheduled_batch(); +} + +static void *do_cdr(void *data) +{ + struct timespec timeout; + int schedms; + int numevents = 0; + + for(;;) { + struct timeval now = ast_tvnow(); + schedms = ast_sched_wait(sched); + /* this shouldn't happen, but provide a 1 second default just in case */ + if (schedms <= 0) + schedms = 1000; + timeout.tv_sec = now.tv_sec + (schedms / 1000); + timeout.tv_nsec = (now.tv_usec * 1000) + ((schedms % 1000) * 1000); + /* prevent stuff from clobbering cdr_pending_cond, then wait on signals sent to it until the timeout expires */ + ast_mutex_lock(&cdr_pending_lock); + ast_cond_timedwait(&cdr_pending_cond, &cdr_pending_lock, &timeout); + numevents = ast_sched_runq(sched); + ast_mutex_unlock(&cdr_pending_lock); + if (option_debug > 1) + ast_log(LOG_DEBUG, "Processed %d scheduled CDR batches from the run queue\n", numevents); + } + + return NULL; +} + +static int handle_cli_status(int fd, int argc, char *argv[]) +{ + struct ast_cdr_beitem *beitem=NULL; + int cnt=0; + long nextbatchtime=0; + + if (argc > 2) + return RESULT_SHOWUSAGE; + + ast_cli(fd, "CDR logging: %s\n", enabled ? "enabled" : "disabled"); + ast_cli(fd, "CDR mode: %s\n", batchmode ? "batch" : "simple"); + if (enabled) { + if (batchmode) { + if (batch) + cnt = batch->size; + if (cdr_sched > -1) + nextbatchtime = ast_sched_when(sched, cdr_sched); + ast_cli(fd, "CDR safe shut down: %s\n", batchsafeshutdown ? "enabled" : "disabled"); + ast_cli(fd, "CDR batch threading model: %s\n", batchscheduleronly ? "scheduler only" : "scheduler plus separate threads"); + ast_cli(fd, "CDR current batch size: %d record%s\n", cnt, (cnt != 1) ? "s" : ""); + ast_cli(fd, "CDR maximum batch size: %d record%s\n", batchsize, (batchsize != 1) ? "s" : ""); + ast_cli(fd, "CDR maximum batch time: %d second%s\n", batchtime, (batchtime != 1) ? "s" : ""); + ast_cli(fd, "CDR next scheduled batch processing time: %ld second%s\n", nextbatchtime, (nextbatchtime != 1) ? "s" : ""); + } + AST_LIST_LOCK(&be_list); + AST_LIST_TRAVERSE(&be_list, beitem, list) { + ast_cli(fd, "CDR registered backend: %s\n", beitem->name); + } + AST_LIST_UNLOCK(&be_list); + } + + return 0; +} + +static int handle_cli_submit(int fd, int argc, char *argv[]) +{ + if (argc > 2) + return RESULT_SHOWUSAGE; + + submit_unscheduled_batch(); + ast_cli(fd, "Submitted CDRs to backend engines for processing. This may take a while.\n"); + + return 0; +} + +static struct ast_cli_entry cli_submit = { + .cmda = { "cdr", "submit", NULL }, + .handler = handle_cli_submit, + .summary = "Posts all pending batched CDR data", + .usage = + "Usage: cdr submit\n" + " Posts all pending batched CDR data to the configured CDR backend engine modules.\n" +}; + +static struct ast_cli_entry cli_status = { + .cmda = { "cdr", "status", NULL }, + .handler = handle_cli_status, + .summary = "Display the CDR status", + .usage = + "Usage: cdr status\n" + " Displays the Call Detail Record engine system status.\n" +}; + +static int do_reload(void) +{ + struct ast_config *config; + const char *enabled_value; + const char *batched_value; + const char *scheduleronly_value; + const char *batchsafeshutdown_value; + const char *size_value; + const char *time_value; + const char *end_before_h_value; + int cfg_size; + int cfg_time; + int was_enabled; + int was_batchmode; + int res=0; + + ast_mutex_lock(&cdr_batch_lock); + + batchsize = BATCH_SIZE_DEFAULT; + batchtime = BATCH_TIME_DEFAULT; + batchscheduleronly = BATCH_SCHEDULER_ONLY_DEFAULT; + batchsafeshutdown = BATCH_SAFE_SHUTDOWN_DEFAULT; + was_enabled = enabled; + was_batchmode = batchmode; + enabled = 1; + batchmode = 0; + + /* don't run the next scheduled CDR posting while reloading */ + if (cdr_sched > -1) + ast_sched_del(sched, cdr_sched); + + if ((config = ast_config_load("cdr.conf"))) { + if ((enabled_value = ast_variable_retrieve(config, "general", "enable"))) { + enabled = ast_true(enabled_value); + } + if ((batched_value = ast_variable_retrieve(config, "general", "batch"))) { + batchmode = ast_true(batched_value); + } + if ((scheduleronly_value = ast_variable_retrieve(config, "general", "scheduleronly"))) { + batchscheduleronly = ast_true(scheduleronly_value); + } + if ((batchsafeshutdown_value = ast_variable_retrieve(config, "general", "safeshutdown"))) { + batchsafeshutdown = ast_true(batchsafeshutdown_value); + } + if ((size_value = ast_variable_retrieve(config, "general", "size"))) { + if (sscanf(size_value, "%d", &cfg_size) < 1) + ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", size_value); + else if (size_value < 0) + ast_log(LOG_WARNING, "Invalid maximum batch size '%d' specified, using default\n", cfg_size); + else + batchsize = cfg_size; + } + if ((time_value = ast_variable_retrieve(config, "general", "time"))) { + if (sscanf(time_value, "%d", &cfg_time) < 1) + ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", time_value); + else if (time_value < 0) + ast_log(LOG_WARNING, "Invalid maximum batch time '%d' specified, using default\n", cfg_time); + else + batchtime = cfg_time; + } + if ((end_before_h_value = ast_variable_retrieve(config, "general", "endbeforehexten"))) + ast_set2_flag(&ast_options, ast_true(end_before_h_value), AST_OPT_FLAG_END_CDR_BEFORE_H_EXTEN); + } + + if (enabled && !batchmode) { + ast_log(LOG_NOTICE, "CDR simple logging enabled.\n"); + } else if (enabled && batchmode) { + cdr_sched = ast_sched_add(sched, batchtime * 1000, submit_scheduled_batch, NULL); + ast_log(LOG_NOTICE, "CDR batch mode logging enabled, first of either size %d or time %d seconds.\n", batchsize, batchtime); + } else { + ast_log(LOG_NOTICE, "CDR logging disabled, data will be lost.\n"); + } + + /* if this reload enabled the CDR batch mode, create the background thread + if it does not exist */ + if (enabled && batchmode && (!was_enabled || !was_batchmode) && (cdr_thread == AST_PTHREADT_NULL)) { + ast_cond_init(&cdr_pending_cond, NULL); + if (ast_pthread_create(&cdr_thread, NULL, do_cdr, NULL) < 0) { + ast_log(LOG_ERROR, "Unable to start CDR thread.\n"); + ast_sched_del(sched, cdr_sched); + } else { + ast_cli_register(&cli_submit); + ast_register_atexit(ast_cdr_engine_term); + res = 0; + } + /* if this reload disabled the CDR and/or batch mode and there is a background thread, + kill it */ + } else if (((!enabled && was_enabled) || (!batchmode && was_batchmode)) && (cdr_thread != AST_PTHREADT_NULL)) { + /* wake up the thread so it will exit */ + pthread_cancel(cdr_thread); + pthread_kill(cdr_thread, SIGURG); + pthread_join(cdr_thread, NULL); + cdr_thread = AST_PTHREADT_NULL; + ast_cond_destroy(&cdr_pending_cond); + ast_cli_unregister(&cli_submit); + ast_unregister_atexit(ast_cdr_engine_term); + res = 0; + /* if leaving batch mode, then post the CDRs in the batch, + and don't reschedule, since we are stopping CDR logging */ + if (!batchmode && was_batchmode) { + ast_cdr_engine_term(); + } + } else { + res = 0; + } + + ast_mutex_unlock(&cdr_batch_lock); + ast_config_destroy(config); + + return res; +} + +int ast_cdr_engine_init(void) +{ + int res; + + sched = sched_context_create(); + if (!sched) { + ast_log(LOG_ERROR, "Unable to create schedule context.\n"); + return -1; + } + + ast_cli_register(&cli_status); + + res = do_reload(); + if (res) { + ast_mutex_lock(&cdr_batch_lock); + res = init_batch(); + ast_mutex_unlock(&cdr_batch_lock); + } + + return res; +} + +/* \note This actually gets called a couple of times at shutdown. Once, before we start + hanging up channels, and then again, after the channel hangup timeout expires */ +void ast_cdr_engine_term(void) +{ + ast_cdr_submit_batch(batchsafeshutdown); +} + +int ast_cdr_engine_reload(void) +{ + return do_reload(); +} + diff --git a/main/channel.c b/main/channel.c new file mode 100644 index 000000000..a56961627 --- /dev/null +++ b/main/channel.c @@ -0,0 +1,4516 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Channel Management + * + * \author Mark Spencer + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ZAPTEL +#include +#include +#endif + +#include "asterisk/pbx.h" +#include "asterisk/frame.h" +#include "asterisk/sched.h" +#include "asterisk/options.h" +#include "asterisk/channel.h" +#include "asterisk/chanspy.h" +#include "asterisk/musiconhold.h" +#include "asterisk/logger.h" +#include "asterisk/say.h" +#include "asterisk/file.h" +#include "asterisk/cli.h" +#include "asterisk/translate.h" +#include "asterisk/manager.h" +#include "asterisk/chanvars.h" +#include "asterisk/linkedlists.h" +#include "asterisk/indications.h" +#include "asterisk/monitor.h" +#include "asterisk/causes.h" +#include "asterisk/callerid.h" +#include "asterisk/utils.h" +#include "asterisk/lock.h" +#include "asterisk/app.h" +#include "asterisk/transcap.h" +#include "asterisk/devicestate.h" +#include "asterisk/sha1.h" +#include "asterisk/threadstorage.h" +#include "asterisk/slinfactory.h" + +struct channel_spy_trans { + int last_format; + struct ast_trans_pvt *path; +}; + +struct ast_channel_spy_list { + struct channel_spy_trans read_translator; + struct channel_spy_trans write_translator; + AST_LIST_HEAD_NOLOCK(, ast_channel_spy) list; +}; + +struct ast_channel_whisper_buffer { + ast_mutex_t lock; + struct ast_slinfactory sf; + unsigned int original_format; +}; + +/* uncomment if you have problems with 'monitoring' synchronized files */ +#if 0 +#define MONITOR_CONSTANT_DELAY +#define MONITOR_DELAY 150 * 8 /* 150 ms of MONITORING DELAY */ +#endif + +/*! Prevent new channel allocation if shutting down. */ +static int shutting_down = 0; + +AST_MUTEX_DEFINE_STATIC(uniquelock); +static int uniqueint = 0; + +unsigned long global_fin = 0, global_fout = 0; + +AST_THREADSTORAGE(state2str_threadbuf, state2str_threadbuf_init); +#define STATE2STR_BUFSIZE 32 + +struct chanlist { + const struct ast_channel_tech *tech; + AST_LIST_ENTRY(chanlist) list; +}; + +/*! the list of registered channel types */ +static AST_LIST_HEAD_NOLOCK_STATIC(backends, chanlist); + +/*! the list of channels we have. Note that the lock for this list is used for + both the channels list and the backends list. */ +static AST_LIST_HEAD_STATIC(channels, ast_channel); + +/*! map AST_CAUSE's to readable string representations */ +const struct ast_cause { + int cause; + const char *name; + const char *desc; +} causes[] = { + { AST_CAUSE_UNALLOCATED, "UNALLOCATED", "Unallocated (unassigned) number" }, + { AST_CAUSE_NO_ROUTE_TRANSIT_NET, "NO_ROUTE_TRANSIT_NET", "No route to specified transmit network" }, + { AST_CAUSE_NO_ROUTE_DESTINATION, "NO_ROUTE_DESTINATION", "No route to destination" }, + { AST_CAUSE_CHANNEL_UNACCEPTABLE, "CHANNEL_UNACCEPTABLE", "Channel unacceptable" }, + { AST_CAUSE_CALL_AWARDED_DELIVERED, "CALL_AWARDED_DELIVERED", "Call awarded and being delivered in an established channel" }, + { AST_CAUSE_NORMAL_CLEARING, "NORMAL_CLEARING", "Normal Clearing" }, + { AST_CAUSE_USER_BUSY, "USER_BUSY", "User busy" }, + { AST_CAUSE_NO_USER_RESPONSE, "NO_USER_RESPONSE", "No user responding" }, + { AST_CAUSE_NO_ANSWER, "NO_ANSWER", "User alerting, no answer" }, + { AST_CAUSE_CALL_REJECTED, "CALL_REJECTED", "Call Rejected" }, + { AST_CAUSE_NUMBER_CHANGED, "NUMBER_CHANGED", "Number changed" }, + { AST_CAUSE_DESTINATION_OUT_OF_ORDER, "DESTINATION_OUT_OF_ORDER", "Destination out of order" }, + { AST_CAUSE_INVALID_NUMBER_FORMAT, "INVALID_NUMBER_FORMAT", "Invalid number format" }, + { AST_CAUSE_FACILITY_REJECTED, "FACILITY_REJECTED", "Facility rejected" }, + { AST_CAUSE_RESPONSE_TO_STATUS_ENQUIRY, "RESPONSE_TO_STATUS_ENQUIRY", "Response to STATus ENQuiry" }, + { AST_CAUSE_NORMAL_UNSPECIFIED, "NORMAL_UNSPECIFIED", "Normal, unspecified" }, + { AST_CAUSE_NORMAL_CIRCUIT_CONGESTION, "NORMAL_CIRCUIT_CONGESTION", "Circuit/channel congestion" }, + { AST_CAUSE_NETWORK_OUT_OF_ORDER, "NETWORK_OUT_OF_ORDER", "Network out of order" }, + { AST_CAUSE_NORMAL_TEMPORARY_FAILURE, "NORMAL_TEMPORARY_FAILURE", "Temporary failure" }, + { AST_CAUSE_SWITCH_CONGESTION, "SWITCH_CONGESTION", "Switching equipment congestion" }, + { AST_CAUSE_ACCESS_INFO_DISCARDED, "ACCESS_INFO_DISCARDED", "Access information discarded" }, + { AST_CAUSE_REQUESTED_CHAN_UNAVAIL, "REQUESTED_CHAN_UNAVAIL", "Requested channel not available" }, + { AST_CAUSE_PRE_EMPTED, "PRE_EMPTED", "Pre-empted" }, + { AST_CAUSE_FACILITY_NOT_SUBSCRIBED, "FACILITY_NOT_SUBSCRIBED", "Facility not subscribed" }, + { AST_CAUSE_OUTGOING_CALL_BARRED, "OUTGOING_CALL_BARRED", "Outgoing call barred" }, + { AST_CAUSE_INCOMING_CALL_BARRED, "INCOMING_CALL_BARRED", "Incoming call barred" }, + { AST_CAUSE_BEARERCAPABILITY_NOTAUTH, "BEARERCAPABILITY_NOTAUTH", "Bearer capability not authorized" }, + { AST_CAUSE_BEARERCAPABILITY_NOTAVAIL, "BEARERCAPABILITY_NOTAVAIL", "Bearer capability not available" }, + { AST_CAUSE_BEARERCAPABILITY_NOTIMPL, "BEARERCAPABILITY_NOTIMPL", "Bearer capability not implemented" }, + { AST_CAUSE_CHAN_NOT_IMPLEMENTED, "CHAN_NOT_IMPLEMENTED", "Channel not implemented" }, + { AST_CAUSE_FACILITY_NOT_IMPLEMENTED, "FACILITY_NOT_IMPLEMENTED", "Facility not implemented" }, + { AST_CAUSE_INVALID_CALL_REFERENCE, "INVALID_CALL_REFERENCE", "Invalid call reference value" }, + { AST_CAUSE_INCOMPATIBLE_DESTINATION, "INCOMPATIBLE_DESTINATION", "Incompatible destination" }, + { AST_CAUSE_INVALID_MSG_UNSPECIFIED, "INVALID_MSG_UNSPECIFIED", "Invalid message unspecified" }, + { AST_CAUSE_MANDATORY_IE_MISSING, "MANDATORY_IE_MISSING", "Mandatory information element is missing" }, + { AST_CAUSE_MESSAGE_TYPE_NONEXIST, "MESSAGE_TYPE_NONEXIST", "Message type nonexist." }, + { AST_CAUSE_WRONG_MESSAGE, "WRONG_MESSAGE", "Wrong message" }, + { AST_CAUSE_IE_NONEXIST, "IE_NONEXIST", "Info. element nonexist or not implemented" }, + { AST_CAUSE_INVALID_IE_CONTENTS, "INVALID_IE_CONTENTS", "Invalid information element contents" }, + { AST_CAUSE_WRONG_CALL_STATE, "WRONG_CALL_STATE", "Message not compatible with call state" }, + { AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE, "RECOVERY_ON_TIMER_EXPIRE", "Recover on timer expiry" }, + { AST_CAUSE_MANDATORY_IE_LENGTH_ERROR, "MANDATORY_IE_LENGTH_ERROR", "Mandatory IE length error" }, + { AST_CAUSE_PROTOCOL_ERROR, "PROTOCOL_ERROR", "Protocol error, unspecified" }, + { AST_CAUSE_INTERWORKING, "INTERWORKING", "Interworking, unspecified" }, +}; + +struct ast_variable *ast_channeltype_list(void) +{ + struct chanlist *cl; + struct ast_variable *var=NULL, *prev = NULL; + AST_LIST_TRAVERSE(&backends, cl, list) { + if (prev) { + if ((prev->next = ast_variable_new(cl->tech->type, cl->tech->description))) + prev = prev->next; + } else { + var = ast_variable_new(cl->tech->type, cl->tech->description); + prev = var; + } + } + return var; +} + +static int show_channeltypes(int fd, int argc, char *argv[]) +{ +#define FORMAT "%-10.10s %-40.40s %-12.12s %-12.12s %-12.12s\n" + struct chanlist *cl; + int count_chan = 0; + + ast_cli(fd, FORMAT, "Type", "Description", "Devicestate", "Indications", "Transfer"); + ast_cli(fd, FORMAT, "----------", "-----------", "-----------", "-----------", "--------"); + if (AST_LIST_LOCK(&channels)) { + ast_log(LOG_WARNING, "Unable to lock channel list\n"); + return -1; + } + AST_LIST_TRAVERSE(&backends, cl, list) { + ast_cli(fd, FORMAT, cl->tech->type, cl->tech->description, + (cl->tech->devicestate) ? "yes" : "no", + (cl->tech->indicate) ? "yes" : "no", + (cl->tech->transfer) ? "yes" : "no"); + count_chan++; + } + AST_LIST_UNLOCK(&channels); + ast_cli(fd, "----------\n%d channel drivers registered.\n", count_chan); + return RESULT_SUCCESS; + +#undef FORMAT + +} + +static int show_channeltype(int fd, int argc, char *argv[]) +{ + struct chanlist *cl = NULL; + + if (argc != 3) + return RESULT_SHOWUSAGE; + + if (AST_LIST_LOCK(&channels)) { + ast_log(LOG_WARNING, "Unable to lock channel list\n"); + return RESULT_FAILURE; + } + + AST_LIST_TRAVERSE(&backends, cl, list) { + if (!strncasecmp(cl->tech->type, argv[2], strlen(cl->tech->type))) { + break; + } + } + + + if (!cl) { + ast_cli(fd, "\n%s is not a registered channel driver.\n", argv[2]); + AST_LIST_UNLOCK(&channels); + return RESULT_FAILURE; + } + + ast_cli(fd, + "-- Info about channel driver: %s --\n" + " Device State: %s\n" + " Indication: %s\n" + " Transfer : %s\n" + " Capabilities: %d\n" + " Send Digit: %s\n" + " Send HTML : %s\n" + " Image Support: %s\n" + " Text Support: %s\n", + cl->tech->type, + (cl->tech->devicestate) ? "yes" : "no", + (cl->tech->indicate) ? "yes" : "no", + (cl->tech->transfer) ? "yes" : "no", + (cl->tech->capabilities) ? cl->tech->capabilities : -1, + (cl->tech->send_digit) ? "yes" : "no", + (cl->tech->send_html) ? "yes" : "no", + (cl->tech->send_image) ? "yes" : "no", + (cl->tech->send_text) ? "yes" : "no" + + ); + + AST_LIST_UNLOCK(&channels); + return RESULT_SUCCESS; +} + +static char *complete_channeltypes(const char *line, const char *word, int pos, int state) +{ + struct chanlist *cl; + int which = 0; + int wordlen; + char *ret = NULL; + + if (pos != 2) + return NULL; + + wordlen = strlen(word); + + AST_LIST_TRAVERSE(&backends, cl, list) { + if (!strncasecmp(word, cl->tech->type, wordlen) && ++which > state) { + ret = strdup(cl->tech->type); + break; + } + } + + return ret; +} + +static char show_channeltypes_usage[] = +"Usage: show channeltypes\n" +" Shows available channel types registered in your Asterisk server.\n"; + +static char show_channeltype_usage[] = +"Usage: show channeltype \n" +" Show details about the specified channel type, .\n"; + +static struct ast_cli_entry cli_show_channeltypes = + { { "show", "channeltypes", NULL }, show_channeltypes, "Show available channel types", show_channeltypes_usage }; + +static struct ast_cli_entry cli_show_channeltype = + { { "show", "channeltype", NULL }, show_channeltype, "Give more details on that channel type", show_channeltype_usage, complete_channeltypes }; + +/*! \brief Checks to see if a channel is needing hang up */ +int ast_check_hangup(struct ast_channel *chan) +{ + if (chan->_softhangup) /* yes if soft hangup flag set */ + return 1; + if (!chan->tech_pvt) /* yes if no technology private data */ + return 1; + if (!chan->whentohangup) /* no if no hangup scheduled */ + return 0; + if (chan->whentohangup > time(NULL)) /* no if hangup time has not come yet. */ + return 0; + chan->_softhangup |= AST_SOFTHANGUP_TIMEOUT; /* record event */ + return 1; +} + +static int ast_check_hangup_locked(struct ast_channel *chan) +{ + int res; + ast_channel_lock(chan); + res = ast_check_hangup(chan); + ast_channel_unlock(chan); + return res; +} + +/*! \brief Initiate system shutdown */ +void ast_begin_shutdown(int hangup) +{ + struct ast_channel *c; + shutting_down = 1; + if (hangup) { + AST_LIST_LOCK(&channels); + AST_LIST_TRAVERSE(&channels, c, chan_list) + ast_softhangup(c, AST_SOFTHANGUP_SHUTDOWN); + AST_LIST_UNLOCK(&channels); + } +} + +/*! \brief returns number of active/allocated channels */ +int ast_active_channels(void) +{ + struct ast_channel *c; + int cnt = 0; + AST_LIST_LOCK(&channels); + AST_LIST_TRAVERSE(&channels, c, chan_list) + cnt++; + AST_LIST_UNLOCK(&channels); + return cnt; +} + +/*! \brief Cancel a shutdown in progress */ +void ast_cancel_shutdown(void) +{ + shutting_down = 0; +} + +/*! \brief Returns non-zero if Asterisk is being shut down */ +int ast_shutting_down(void) +{ + return shutting_down; +} + +/*! \brief Set when to hangup channel */ +void ast_channel_setwhentohangup(struct ast_channel *chan, time_t offset) +{ + chan->whentohangup = offset ? time(NULL) + offset : 0; + ast_queue_frame(chan, &ast_null_frame); + return; +} + +/*! \brief Compare a offset with when to hangup channel */ +int ast_channel_cmpwhentohangup(struct ast_channel *chan, time_t offset) +{ + time_t whentohangup; + + if (chan->whentohangup == 0) { + return (offset == 0) ? 0 : -1; + } else { + if (offset == 0) /* XXX why is this special ? */ + return (1); + else { + whentohangup = offset + time (NULL); + if (chan->whentohangup < whentohangup) + return (1); + else if (chan->whentohangup == whentohangup) + return (0); + else + return (-1); + } + } +} + +/*! \brief Register a new telephony channel in Asterisk */ +int ast_channel_register(const struct ast_channel_tech *tech) +{ + struct chanlist *chan; + + AST_LIST_LOCK(&channels); + + AST_LIST_TRAVERSE(&backends, chan, list) { + if (!strcasecmp(tech->type, chan->tech->type)) { + ast_log(LOG_WARNING, "Already have a handler for type '%s'\n", tech->type); + AST_LIST_UNLOCK(&channels); + return -1; + } + } + + if (!(chan = ast_calloc(1, sizeof(*chan)))) { + AST_LIST_UNLOCK(&channels); + return -1; + } + chan->tech = tech; + AST_LIST_INSERT_HEAD(&backends, chan, list); + + if (option_debug) + ast_log(LOG_DEBUG, "Registered handler for '%s' (%s)\n", chan->tech->type, chan->tech->description); + + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Registered channel type '%s' (%s)\n", chan->tech->type, + chan->tech->description); + + AST_LIST_UNLOCK(&channels); + return 0; +} + +void ast_channel_unregister(const struct ast_channel_tech *tech) +{ + struct chanlist *chan; + + if (option_debug) + ast_log(LOG_DEBUG, "Unregistering channel type '%s'\n", tech->type); + + AST_LIST_LOCK(&channels); + + AST_LIST_TRAVERSE_SAFE_BEGIN(&backends, chan, list) { + if (chan->tech == tech) { + AST_LIST_REMOVE_CURRENT(&backends, list); + free(chan); + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Unregistered channel type '%s'\n", tech->type); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + + AST_LIST_UNLOCK(&channels); +} + +const struct ast_channel_tech *ast_get_channel_tech(const char *name) +{ + struct chanlist *chanls; + const struct ast_channel_tech *ret = NULL; + + if (AST_LIST_LOCK(&channels)) { + ast_log(LOG_WARNING, "Unable to lock channel tech list\n"); + return NULL; + } + + AST_LIST_TRAVERSE(&backends, chanls, list) { + if (!strcasecmp(name, chanls->tech->type)) { + ret = chanls->tech; + break; + } + } + + AST_LIST_UNLOCK(&channels); + + return ret; +} + +/*! \brief Gives the string form of a given hangup cause */ +const char *ast_cause2str(int cause) +{ + int x; + + for (x=0; x < sizeof(causes) / sizeof(causes[0]); x++) { + if (causes[x].cause == cause) + return causes[x].desc; + } + + return "Unknown"; +} + +/*! \brief Convert a symbolic hangup cause to number */ +int ast_str2cause(const char *name) +{ + int x; + + for (x = 0; x < sizeof(causes) / sizeof(causes[0]); x++) + if (strncasecmp(causes[x].name, name, strlen(causes[x].name)) == 0) + return causes[x].cause; + + return -1; +} + +/*! \brief Gives the string form of a given channel state */ +char *ast_state2str(enum ast_channel_state state) +{ + char *buf; + + switch(state) { + case AST_STATE_DOWN: + return "Down"; + case AST_STATE_RESERVED: + return "Rsrvd"; + case AST_STATE_OFFHOOK: + return "OffHook"; + case AST_STATE_DIALING: + return "Dialing"; + case AST_STATE_RING: + return "Ring"; + case AST_STATE_RINGING: + return "Ringing"; + case AST_STATE_UP: + return "Up"; + case AST_STATE_BUSY: + return "Busy"; + case AST_STATE_DIALING_OFFHOOK: + return "Dialing Offhook"; + case AST_STATE_PRERING: + return "Pre-ring"; + default: + if (!(buf = ast_threadstorage_get(&state2str_threadbuf, STATE2STR_BUFSIZE))) + return "Unknown"; + snprintf(buf, STATE2STR_BUFSIZE, "Unknown (%d)", state); + return buf; + } +} + +/*! \brief Gives the string form of a given transfer capability */ +char *ast_transfercapability2str(int transfercapability) +{ + switch(transfercapability) { + case AST_TRANS_CAP_SPEECH: + return "SPEECH"; + case AST_TRANS_CAP_DIGITAL: + return "DIGITAL"; + case AST_TRANS_CAP_RESTRICTED_DIGITAL: + return "RESTRICTED_DIGITAL"; + case AST_TRANS_CAP_3_1K_AUDIO: + return "3K1AUDIO"; + case AST_TRANS_CAP_DIGITAL_W_TONES: + return "DIGITAL_W_TONES"; + case AST_TRANS_CAP_VIDEO: + return "VIDEO"; + default: + return "UNKNOWN"; + } +} + +/*! \brief Pick the best audio codec */ +int ast_best_codec(int fmts) +{ + /* This just our opinion, expressed in code. We are asked to choose + the best codec to use, given no information */ + int x; + static int prefs[] = + { + /*! Okay, ulaw is used by all telephony equipment, so start with it */ + AST_FORMAT_ULAW, + /*! Unless of course, you're a silly European, so then prefer ALAW */ + AST_FORMAT_ALAW, + /*! Okay, well, signed linear is easy to translate into other stuff */ + AST_FORMAT_SLINEAR, + /*! G.726 is standard ADPCM, in RFC3551 packing order */ + AST_FORMAT_G726, + /*! G.726 is standard ADPCM, in AAL2 packing order */ + AST_FORMAT_G726_AAL2, + /*! ADPCM has great sound quality and is still pretty easy to translate */ + AST_FORMAT_ADPCM, + /*! Okay, we're down to vocoders now, so pick GSM because it's small and easier to + translate and sounds pretty good */ + AST_FORMAT_GSM, + /*! iLBC is not too bad */ + AST_FORMAT_ILBC, + /*! Speex is free, but computationally more expensive than GSM */ + AST_FORMAT_SPEEX, + /*! Ick, LPC10 sounds terrible, but at least we have code for it, if you're tacky enough + to use it */ + AST_FORMAT_LPC10, + /*! G.729a is faster than 723 and slightly less expensive */ + AST_FORMAT_G729A, + /*! Down to G.723.1 which is proprietary but at least designed for voice */ + AST_FORMAT_G723_1, + }; + + /* Strip out video */ + fmts &= AST_FORMAT_AUDIO_MASK; + + /* Find the first preferred codec in the format given */ + for (x=0; x < (sizeof(prefs) / sizeof(prefs[0]) ); x++) + if (fmts & prefs[x]) + return prefs[x]; + ast_log(LOG_WARNING, "Don't know any of 0x%x formats\n", fmts); + return 0; +} + +static const struct ast_channel_tech null_tech = { + .type = "NULL", + .description = "Null channel (should not see this)", +}; + +/*! \brief Create a new channel structure */ +struct ast_channel *ast_channel_alloc(int needqueue) +{ + struct ast_channel *tmp; + int x; + int flags; + struct varshead *headp; + + /* If shutting down, don't allocate any new channels */ + if (shutting_down) { + ast_log(LOG_WARNING, "Channel allocation failed: Refusing due to active shutdown\n"); + return NULL; + } + + if (!(tmp = ast_calloc(1, sizeof(*tmp)))) + return NULL; + + if (!(tmp->sched = sched_context_create())) { + ast_log(LOG_WARNING, "Channel allocation failed: Unable to create schedule context\n"); + free(tmp); + return NULL; + } + + ast_string_field_init(tmp, 128); + + /* Don't bother initializing the last two FD here, because they + will *always* be set just a few lines down (AST_TIMING_FD, + AST_ALERT_FD). */ + for (x=0; xfds[x] = -1; + +#ifdef HAVE_ZAPTEL + tmp->timingfd = open("/dev/zap/timer", O_RDWR); + if (tmp->timingfd > -1) { + /* Check if timing interface supports new + ping/pong scheme */ + flags = 1; + if (!ioctl(tmp->timingfd, ZT_TIMERPONG, &flags)) + needqueue = 0; + } +#else + tmp->timingfd = -1; +#endif + + if (needqueue) { + if (pipe(tmp->alertpipe)) { + ast_log(LOG_WARNING, "Channel allocation failed: Can't create alert pipe!\n"); + free(tmp); + return NULL; + } else { + flags = fcntl(tmp->alertpipe[0], F_GETFL); + fcntl(tmp->alertpipe[0], F_SETFL, flags | O_NONBLOCK); + flags = fcntl(tmp->alertpipe[1], F_GETFL); + fcntl(tmp->alertpipe[1], F_SETFL, flags | O_NONBLOCK); + } + } else /* Make sure we've got it done right if they don't */ + tmp->alertpipe[0] = tmp->alertpipe[1] = -1; + + /* Always watch the alertpipe */ + tmp->fds[AST_ALERT_FD] = tmp->alertpipe[0]; + /* And timing pipe */ + tmp->fds[AST_TIMING_FD] = tmp->timingfd; + ast_string_field_set(tmp, name, "**Unknown**"); + /* Initial state */ + tmp->_state = AST_STATE_DOWN; + tmp->streamid = -1; + tmp->appl = NULL; + tmp->data = NULL; + tmp->fin = global_fin; + tmp->fout = global_fout; + ast_mutex_lock(&uniquelock); + if (ast_strlen_zero(ast_config_AST_SYSTEM_NAME)) + ast_string_field_build(tmp, uniqueid, "%li.%d", (long) time(NULL), uniqueint++); + else + ast_string_field_build(tmp, uniqueid, "%s-%li.%d", ast_config_AST_SYSTEM_NAME, (long) time(NULL), uniqueint++); + ast_mutex_unlock(&uniquelock); + headp = &tmp->varshead; + ast_mutex_init(&tmp->lock); + AST_LIST_HEAD_INIT_NOLOCK(headp); + AST_LIST_HEAD_INIT_NOLOCK(&tmp->datastores); + strcpy(tmp->context, "default"); + ast_string_field_set(tmp, language, defaultlanguage); + strcpy(tmp->exten, "s"); + tmp->priority = 1; + tmp->amaflags = ast_default_amaflags; + ast_string_field_set(tmp, accountcode, ast_default_accountcode); + + tmp->tech = &null_tech; + + AST_LIST_LOCK(&channels); + AST_LIST_INSERT_HEAD(&channels, tmp, chan_list); + AST_LIST_UNLOCK(&channels); + return tmp; +} + +/*! \brief Queue an outgoing media frame */ +int ast_queue_frame(struct ast_channel *chan, struct ast_frame *fin) +{ + struct ast_frame *f; + struct ast_frame *prev, *cur; + int blah = 1; + int qlen = 0; + + /* Build us a copy and free the original one */ + if (!(f = ast_frdup(fin))) { + ast_log(LOG_WARNING, "Unable to duplicate frame\n"); + return -1; + } + ast_channel_lock(chan); + prev = NULL; + for (cur = chan->readq; cur; cur = cur->next) { + if ((cur->frametype == AST_FRAME_CONTROL) && (cur->subclass == AST_CONTROL_HANGUP)) { + /* Don't bother actually queueing anything after a hangup */ + ast_frfree(f); + ast_channel_unlock(chan); + return 0; + } + prev = cur; + qlen++; + } + /* Allow up to 96 voice frames outstanding, and up to 128 total frames */ + if (((fin->frametype == AST_FRAME_VOICE) && (qlen > 96)) || (qlen > 128)) { + if (fin->frametype != AST_FRAME_VOICE) { + ast_log(LOG_WARNING, "Exceptionally long queue length queuing to %s\n", chan->name); + CRASH; + } else { + ast_log(LOG_DEBUG, "Dropping voice to exceptionally long queue on %s\n", chan->name); + ast_frfree(f); + ast_channel_unlock(chan); + return 0; + } + } + if (prev) + prev->next = f; + else + chan->readq = f; + if (chan->alertpipe[1] > -1) { + if (write(chan->alertpipe[1], &blah, sizeof(blah)) != sizeof(blah)) + ast_log(LOG_WARNING, "Unable to write to alert pipe on %s, frametype/subclass %d/%d (qlen = %d): %s!\n", + chan->name, f->frametype, f->subclass, qlen, strerror(errno)); +#ifdef HAVE_ZAPTEL + } else if (chan->timingfd > -1) { + ioctl(chan->timingfd, ZT_TIMERPING, &blah); +#endif + } else if (ast_test_flag(chan, AST_FLAG_BLOCKING)) { + pthread_kill(chan->blocker, SIGURG); + } + ast_channel_unlock(chan); + return 0; +} + +/*! \brief Queue a hangup frame for channel */ +int ast_queue_hangup(struct ast_channel *chan) +{ + struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_HANGUP }; + /* Yeah, let's not change a lock-critical value without locking */ + if (!ast_channel_trylock(chan)) { + chan->_softhangup |= AST_SOFTHANGUP_DEV; + ast_channel_unlock(chan); + } + return ast_queue_frame(chan, &f); +} + +/*! \brief Queue a control frame */ +int ast_queue_control(struct ast_channel *chan, enum ast_control_frame_type control) +{ + struct ast_frame f = { AST_FRAME_CONTROL, }; + + f.subclass = control; + + return ast_queue_frame(chan, &f); +} + +/*! \brief Queue a control frame with payload */ +int ast_queue_control_data(struct ast_channel *chan, enum ast_control_frame_type control, + const void *data, size_t datalen) +{ + struct ast_frame f = { AST_FRAME_CONTROL, }; + + f.subclass = control; + f.data = (void *) data; + f.datalen = datalen; + + return ast_queue_frame(chan, &f); +} + +/*! \brief Set defer DTMF flag on channel */ +int ast_channel_defer_dtmf(struct ast_channel *chan) +{ + int pre = 0; + + if (chan) { + pre = ast_test_flag(chan, AST_FLAG_DEFER_DTMF); + ast_set_flag(chan, AST_FLAG_DEFER_DTMF); + } + return pre; +} + +/*! \brief Unset defer DTMF flag on channel */ +void ast_channel_undefer_dtmf(struct ast_channel *chan) +{ + if (chan) + ast_clear_flag(chan, AST_FLAG_DEFER_DTMF); +} + +/*! + * \brief Helper function to find channels. + * + * It supports these modes: + * + * prev != NULL : get channel next in list after prev + * name != NULL : get channel with matching name + * name != NULL && namelen != 0 : get channel whose name starts with prefix + * exten != NULL : get channel whose exten or macroexten matches + * context != NULL && exten != NULL : get channel whose context or macrocontext + * + * It returns with the channel's lock held. If getting the individual lock fails, + * unlock and retry quickly up to 10 times, then give up. + * + * \note XXX Note that this code has cost O(N) because of the need to verify + * that the object is still on the global list. + * + * \note XXX also note that accessing fields (e.g. c->name in ast_log()) + * can only be done with the lock held or someone could delete the + * object while we work on it. This causes some ugliness in the code. + * Note that removing the first ast_log() may be harmful, as it would + * shorten the retry period and possibly cause failures. + * We should definitely go for a better scheme that is deadlock-free. + */ +static struct ast_channel *channel_find_locked(const struct ast_channel *prev, + const char *name, const int namelen, + const char *context, const char *exten) +{ + const char *msg = prev ? "deadlock" : "initial deadlock"; + int retries; + struct ast_channel *c; + + for (retries = 0; retries < 10; retries++) { + int done; + AST_LIST_LOCK(&channels); + AST_LIST_TRAVERSE(&channels, c, chan_list) { + if (prev) { /* look for next item */ + if (c != prev) /* not this one */ + continue; + /* found, prepare to return c->next */ + c = AST_LIST_NEXT(c, chan_list); + } + if (name) { /* want match by name */ + if ((!namelen && strcasecmp(c->name, name)) || + (namelen && strncasecmp(c->name, name, namelen))) + continue; /* name match failed */ + } else if (exten) { + if (context && strcasecmp(c->context, context) && + strcasecmp(c->macrocontext, context)) + continue; /* context match failed */ + if (strcasecmp(c->exten, exten) && + strcasecmp(c->macroexten, exten)) + continue; /* exten match failed */ + } + /* if we get here, c points to the desired record */ + break; + } + /* exit if chan not found or mutex acquired successfully */ + /* this is slightly unsafe, as we _should_ hold the lock to access c->name */ + done = c == NULL || ast_channel_trylock(c) == 0; + if (!done) + ast_log(LOG_DEBUG, "Avoiding %s for channel '%p'\n", msg, c); + AST_LIST_UNLOCK(&channels); + if (done) + return c; + usleep(1); /* give other threads a chance before retrying */ + } + /* + * c is surely not null, but we don't have the lock so cannot + * access c->name + */ + ast_log(LOG_DEBUG, "Failure, could not lock '%p' after %d retries!\n", + c, retries); + + return NULL; +} + +/*! \brief Browse channels in use */ +struct ast_channel *ast_channel_walk_locked(const struct ast_channel *prev) +{ + return channel_find_locked(prev, NULL, 0, NULL, NULL); +} + +/*! \brief Get channel by name and lock it */ +struct ast_channel *ast_get_channel_by_name_locked(const char *name) +{ + return channel_find_locked(NULL, name, 0, NULL, NULL); +} + +/*! \brief Get channel by name prefix and lock it */ +struct ast_channel *ast_get_channel_by_name_prefix_locked(const char *name, const int namelen) +{ + return channel_find_locked(NULL, name, namelen, NULL, NULL); +} + +/*! \brief Get next channel by name prefix and lock it */ +struct ast_channel *ast_walk_channel_by_name_prefix_locked(const struct ast_channel *chan, const char *name, + const int namelen) +{ + return channel_find_locked(chan, name, namelen, NULL, NULL); +} + +/*! \brief Get channel by exten (and optionally context) and lock it */ +struct ast_channel *ast_get_channel_by_exten_locked(const char *exten, const char *context) +{ + return channel_find_locked(NULL, NULL, 0, context, exten); +} + +/*! \brief Get next channel by exten (and optionally context) and lock it */ +struct ast_channel *ast_walk_channel_by_exten_locked(const struct ast_channel *chan, const char *exten, + const char *context) +{ + return channel_find_locked(chan, NULL, 0, context, exten); +} + +/*! \brief Wait, look for hangups and condition arg */ +int ast_safe_sleep_conditional(struct ast_channel *chan, int ms, int (*cond)(void*), void *data) +{ + struct ast_frame *f; + + while (ms > 0) { + if (cond && ((*cond)(data) == 0)) + return 0; + ms = ast_waitfor(chan, ms); + if (ms < 0) + return -1; + if (ms > 0) { + f = ast_read(chan); + if (!f) + return -1; + ast_frfree(f); + } + } + return 0; +} + +/*! \brief Wait, look for hangups */ +int ast_safe_sleep(struct ast_channel *chan, int ms) +{ + return ast_safe_sleep_conditional(chan, ms, NULL, NULL); +} + +static void free_cid(struct ast_callerid *cid) +{ + if (cid->cid_dnid) + free(cid->cid_dnid); + if (cid->cid_num) + free(cid->cid_num); + if (cid->cid_name) + free(cid->cid_name); + if (cid->cid_ani) + free(cid->cid_ani); + if (cid->cid_rdnis) + free(cid->cid_rdnis); +} + +/*! \brief Free a channel structure */ +void ast_channel_free(struct ast_channel *chan) +{ + int fd; + struct ast_var_t *vardata; + struct ast_frame *f, *fp; + struct varshead *headp; + struct ast_datastore *datastore = NULL; + char name[AST_CHANNEL_NAME]; + + headp=&chan->varshead; + + AST_LIST_LOCK(&channels); + AST_LIST_REMOVE(&channels, chan, chan_list); + /* Lock and unlock the channel just to be sure nobody + has it locked still */ + ast_channel_lock(chan); + ast_channel_unlock(chan); + if (chan->tech_pvt) { + ast_log(LOG_WARNING, "Channel '%s' may not have been hung up properly\n", chan->name); + free(chan->tech_pvt); + } + + if (chan->sched) + sched_context_destroy(chan->sched); + + ast_copy_string(name, chan->name, sizeof(name)); + + /* Stop monitoring */ + if (chan->monitor) + chan->monitor->stop( chan, 0 ); + + /* If there is native format music-on-hold state, free it */ + if (chan->music_state) + ast_moh_cleanup(chan); + + /* if someone is whispering on the channel, stop them */ + if (chan->whisper) + ast_channel_whisper_stop(chan); + + /* Free translators */ + if (chan->readtrans) + ast_translator_free_path(chan->readtrans); + if (chan->writetrans) + ast_translator_free_path(chan->writetrans); + if (chan->pbx) + ast_log(LOG_WARNING, "PBX may not have been terminated properly on '%s'\n", chan->name); + free_cid(&chan->cid); + ast_mutex_destroy(&chan->lock); + /* Close pipes if appropriate */ + if ((fd = chan->alertpipe[0]) > -1) + close(fd); + if ((fd = chan->alertpipe[1]) > -1) + close(fd); + if ((fd = chan->timingfd) > -1) + close(fd); + f = chan->readq; + chan->readq = NULL; + while(f) { + fp = f; + f = f->next; + ast_frfree(fp); + } + + /* Get rid of each of the data stores on the channel */ + while ((datastore = AST_LIST_REMOVE_HEAD(&chan->datastores, entry))) + /* Free the data store */ + ast_channel_datastore_free(datastore); + AST_LIST_HEAD_INIT_NOLOCK(&chan->datastores); + + /* loop over the variables list, freeing all data and deleting list items */ + /* no need to lock the list, as the channel is already locked */ + + while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries))) + ast_var_delete(vardata); + + /* Destroy the jitterbuffer */ + ast_jb_destroy(chan); + + ast_string_field_free_all(chan); + free(chan); + AST_LIST_UNLOCK(&channels); + + ast_device_state_changed_literal(name); +} + +struct ast_datastore *ast_channel_datastore_alloc(const struct ast_datastore_info *info, char *uid) +{ + struct ast_datastore *datastore = NULL; + + /* Make sure we at least have type so we can identify this */ + if (info == NULL) { + return NULL; + } + + /* Allocate memory for datastore and clear it */ + datastore = ast_calloc(1, sizeof(*datastore)); + if (datastore == NULL) { + return NULL; + } + + datastore->info = info; + + datastore->uid = ast_strdup(uid); + + return datastore; +} + +int ast_channel_datastore_free(struct ast_datastore *datastore) +{ + int res = 0; + + /* Using the destroy function (if present) destroy the data */ + if (datastore->info->destroy != NULL && datastore->data != NULL) { + datastore->info->destroy(datastore->data); + datastore->data = NULL; + } + + /* Free allocated UID memory */ + if (datastore->uid != NULL) { + free(datastore->uid); + datastore->uid = NULL; + } + + /* Finally free memory used by ourselves */ + free(datastore); + + return res; +} + +int ast_channel_datastore_add(struct ast_channel *chan, struct ast_datastore *datastore) +{ + int res = 0; + + AST_LIST_INSERT_HEAD(&chan->datastores, datastore, entry); + + return res; +} + +int ast_channel_datastore_remove(struct ast_channel *chan, struct ast_datastore *datastore) +{ + struct ast_datastore *datastore2 = NULL; + int res = -1; + + /* Find our position and remove ourselves */ + AST_LIST_TRAVERSE_SAFE_BEGIN(&chan->datastores, datastore2, entry) { + if (datastore2 == datastore) { + AST_LIST_REMOVE_CURRENT(&chan->datastores, entry); + res = 0; + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + + return res; +} + +struct ast_datastore *ast_channel_datastore_find(struct ast_channel *chan, const struct ast_datastore_info *info, char *uid) +{ + struct ast_datastore *datastore = NULL; + + if (info == NULL) + return NULL; + + AST_LIST_TRAVERSE_SAFE_BEGIN(&chan->datastores, datastore, entry) { + if (datastore->info == info) { + if (uid != NULL && datastore->uid != NULL) { + if (!strcasecmp(uid, datastore->uid)) { + /* Matched by type AND uid */ + break; + } + } else { + /* Matched by type at least */ + break; + } + } + } + AST_LIST_TRAVERSE_SAFE_END + + return datastore; +} + +int ast_channel_spy_add(struct ast_channel *chan, struct ast_channel_spy *spy) +{ + /* Link the owner channel to the spy */ + spy->chan = chan; + + if (!ast_test_flag(spy, CHANSPY_FORMAT_AUDIO)) { + ast_log(LOG_WARNING, "Could not add channel spy '%s' to channel '%s', only audio format spies are supported.\n", + spy->type, chan->name); + return -1; + } + + if (ast_test_flag(spy, CHANSPY_READ_VOLADJUST) && (spy->read_queue.format != AST_FORMAT_SLINEAR)) { + ast_log(LOG_WARNING, "Cannot provide volume adjustment on '%s' format spies\n", + ast_getformatname(spy->read_queue.format)); + return -1; + } + + if (ast_test_flag(spy, CHANSPY_WRITE_VOLADJUST) && (spy->write_queue.format != AST_FORMAT_SLINEAR)) { + ast_log(LOG_WARNING, "Cannot provide volume adjustment on '%s' format spies\n", + ast_getformatname(spy->write_queue.format)); + return -1; + } + + if (ast_test_flag(spy, CHANSPY_MIXAUDIO) && + ((spy->read_queue.format != AST_FORMAT_SLINEAR) || + (spy->write_queue.format != AST_FORMAT_SLINEAR))) { + ast_log(LOG_WARNING, "Cannot provide audio mixing on '%s'-'%s' format spies\n", + ast_getformatname(spy->read_queue.format), ast_getformatname(spy->write_queue.format)); + return -1; + } + + if (!chan->spies) { + if (!(chan->spies = ast_calloc(1, sizeof(*chan->spies)))) { + return -1; + } + + AST_LIST_HEAD_INIT_NOLOCK(&chan->spies->list); + AST_LIST_INSERT_HEAD(&chan->spies->list, spy, list); + } else { + AST_LIST_INSERT_TAIL(&chan->spies->list, spy, list); + } + + if (ast_test_flag(spy, CHANSPY_TRIGGER_MODE) != CHANSPY_TRIGGER_NONE) { + ast_cond_init(&spy->trigger, NULL); + ast_set_flag(spy, CHANSPY_TRIGGER_READ); + ast_clear_flag(spy, CHANSPY_TRIGGER_WRITE); + } + + ast_log(LOG_DEBUG, "Spy %s added to channel %s\n", + spy->type, chan->name); + + return 0; +} + +void ast_channel_spy_stop_by_type(struct ast_channel *chan, const char *type) +{ + struct ast_channel_spy *spy; + + if (!chan->spies) + return; + + AST_LIST_TRAVERSE(&chan->spies->list, spy, list) { + ast_mutex_lock(&spy->lock); + if ((spy->type == type) && (spy->status == CHANSPY_RUNNING)) { + spy->status = CHANSPY_STOP; + if (ast_test_flag(spy, CHANSPY_TRIGGER_MODE) != CHANSPY_TRIGGER_NONE) + ast_cond_signal(&spy->trigger); + } + ast_mutex_unlock(&spy->lock); + } +} + +void ast_channel_spy_trigger_wait(struct ast_channel_spy *spy) +{ + struct timeval tv; + struct timespec ts; + + tv = ast_tvadd(ast_tvnow(), ast_samp2tv(50000, 1000)); + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = tv.tv_usec * 1000; + + ast_cond_timedwait(&spy->trigger, &spy->lock, &ts); +} + +void ast_channel_spy_remove(struct ast_channel *chan, struct ast_channel_spy *spy) +{ + struct ast_frame *f; + + if (!chan->spies) + return; + + AST_LIST_REMOVE(&chan->spies->list, spy, list); + + ast_mutex_lock(&spy->lock); + + spy->chan = NULL; + + for (f = spy->read_queue.head; f; f = spy->read_queue.head) { + spy->read_queue.head = f->next; + ast_frfree(f); + } + for (f = spy->write_queue.head; f; f = spy->write_queue.head) { + spy->write_queue.head = f->next; + ast_frfree(f); + } + + if (ast_test_flag(spy, CHANSPY_TRIGGER_MODE) != CHANSPY_TRIGGER_NONE) + ast_cond_destroy(&spy->trigger); + + ast_mutex_unlock(&spy->lock); + + ast_log(LOG_DEBUG, "Spy %s removed from channel %s\n", + spy->type, chan->name); + + if (AST_LIST_EMPTY(&chan->spies->list)) { + if (chan->spies->read_translator.path) + ast_translator_free_path(chan->spies->read_translator.path); + if (chan->spies->write_translator.path) + ast_translator_free_path(chan->spies->write_translator.path); + free(chan->spies); + chan->spies = NULL; + } +} + +static void detach_spies(struct ast_channel *chan) +{ + struct ast_channel_spy *spy; + + if (!chan->spies) + return; + + /* Marking the spies as done is sufficient. Chanspy or spy users will get the picture. */ + AST_LIST_TRAVERSE(&chan->spies->list, spy, list) { + ast_mutex_lock(&spy->lock); + spy->chan = NULL; + if (spy->status == CHANSPY_RUNNING) + spy->status = CHANSPY_DONE; + if (ast_test_flag(spy, CHANSPY_TRIGGER_MODE) != CHANSPY_TRIGGER_NONE) + ast_cond_signal(&spy->trigger); + ast_mutex_unlock(&spy->lock); + } + + AST_LIST_TRAVERSE_SAFE_BEGIN(&chan->spies->list, spy, list) + ast_channel_spy_remove(chan, spy); + AST_LIST_TRAVERSE_SAFE_END; +} + +/*! \brief Softly hangup a channel, don't lock */ +int ast_softhangup_nolock(struct ast_channel *chan, int cause) +{ + if (option_debug) + ast_log(LOG_DEBUG, "Soft-Hanging up channel '%s'\n", chan->name); + /* Inform channel driver that we need to be hung up, if it cares */ + chan->_softhangup |= cause; + ast_queue_frame(chan, &ast_null_frame); + /* Interrupt any poll call or such */ + if (ast_test_flag(chan, AST_FLAG_BLOCKING)) + pthread_kill(chan->blocker, SIGURG); + return 0; +} + +/*! \brief Softly hangup a channel, lock */ +int ast_softhangup(struct ast_channel *chan, int cause) +{ + int res; + ast_channel_lock(chan); + res = ast_softhangup_nolock(chan, cause); + ast_channel_unlock(chan); + return res; +} + +enum spy_direction { + SPY_READ, + SPY_WRITE, +}; + +#define SPY_QUEUE_SAMPLE_LIMIT 4000 /* half of one second */ + +static void queue_frame_to_spies(struct ast_channel *chan, struct ast_frame *f, enum spy_direction dir) +{ + struct ast_frame *translated_frame = NULL; + struct ast_channel_spy *spy; + struct channel_spy_trans *trans; + + trans = (dir == SPY_READ) ? &chan->spies->read_translator : &chan->spies->write_translator; + + AST_LIST_TRAVERSE(&chan->spies->list, spy, list) { + struct ast_frame *last; + struct ast_frame *f1; /* the frame to append */ + struct ast_channel_spy_queue *queue; + + ast_mutex_lock(&spy->lock); + + queue = (dir == SPY_READ) ? &spy->read_queue : &spy->write_queue; + + if ((queue->format == AST_FORMAT_SLINEAR) && (f->subclass != AST_FORMAT_SLINEAR)) { + if (!translated_frame) { + if (trans->path && (trans->last_format != f->subclass)) { + ast_translator_free_path(trans->path); + trans->path = NULL; + } + if (!trans->path) { + ast_log(LOG_DEBUG, "Building translator from %s to SLINEAR for spies on channel %s\n", + ast_getformatname(f->subclass), chan->name); + if ((trans->path = ast_translator_build_path(AST_FORMAT_SLINEAR, f->subclass)) == NULL) { + ast_log(LOG_WARNING, "Cannot build a path from %s to %s\n", + ast_getformatname(f->subclass), ast_getformatname(AST_FORMAT_SLINEAR)); + ast_mutex_unlock(&spy->lock); + continue; + } else { + trans->last_format = f->subclass; + } + } + if (!(translated_frame = ast_translate(trans->path, f, 0))) { + ast_log(LOG_ERROR, "Translation to %s failed, dropping frame for spies\n", + ast_getformatname(AST_FORMAT_SLINEAR)); + ast_mutex_unlock(&spy->lock); + break; + } + } + f1 = translated_frame; + } else { + if (f->subclass != queue->format) { + ast_log(LOG_WARNING, "Spy '%s' on channel '%s' wants format '%s', but frame is '%s', dropping\n", + spy->type, chan->name, + ast_getformatname(queue->format), ast_getformatname(f->subclass)); + ast_mutex_unlock(&spy->lock); + continue; + } + f1 = f; + } + /* duplicate and append f1 to the tail */ + f1 = ast_frdup(f1); + + for (last = queue->head; last && last->next; last = last->next) + ; + if (last) + last->next = f1; + else + queue->head = f1; + + queue->samples += f->samples; + + if (queue->samples > SPY_QUEUE_SAMPLE_LIMIT) { + if (ast_test_flag(spy, CHANSPY_TRIGGER_MODE) != CHANSPY_TRIGGER_NONE) { + switch (ast_test_flag(spy, CHANSPY_TRIGGER_MODE)) { + case CHANSPY_TRIGGER_READ: + if (dir == SPY_WRITE) { + ast_set_flag(spy, CHANSPY_TRIGGER_WRITE); + ast_clear_flag(spy, CHANSPY_TRIGGER_READ); + if (option_debug) + ast_log(LOG_DEBUG, "Switching spy '%s' on '%s' to write-trigger mode\n", + spy->type, chan->name); + } + break; + case CHANSPY_TRIGGER_WRITE: + if (dir == SPY_READ) { + ast_set_flag(spy, CHANSPY_TRIGGER_READ); + ast_clear_flag(spy, CHANSPY_TRIGGER_WRITE); + if (option_debug) + ast_log(LOG_DEBUG, "Switching spy '%s' on '%s' to read-trigger mode\n", + spy->type, chan->name); + } + break; + } + if (option_debug) + ast_log(LOG_DEBUG, "Triggering queue flush for spy '%s' on '%s'\n", + spy->type, chan->name); + ast_set_flag(spy, CHANSPY_TRIGGER_FLUSH); + ast_cond_signal(&spy->trigger); + } else { + if (option_debug) + ast_log(LOG_DEBUG, "Spy '%s' on channel '%s' %s queue too long, dropping frames\n", + spy->type, chan->name, (dir == SPY_READ) ? "read" : "write"); + while (queue->samples > SPY_QUEUE_SAMPLE_LIMIT) { + struct ast_frame *drop = queue->head; + + queue->samples -= drop->samples; + queue->head = drop->next; + ast_frfree(drop); + } + } + } else { + switch (ast_test_flag(spy, CHANSPY_TRIGGER_MODE)) { + case CHANSPY_TRIGGER_READ: + if (dir == SPY_READ) + ast_cond_signal(&spy->trigger); + break; + case CHANSPY_TRIGGER_WRITE: + if (dir == SPY_WRITE) + ast_cond_signal(&spy->trigger); + break; + } + } + + ast_mutex_unlock(&spy->lock); + } + + if (translated_frame) + ast_frfree(translated_frame); +} + +static void free_translation(struct ast_channel *clone) +{ + if (clone->writetrans) + ast_translator_free_path(clone->writetrans); + if (clone->readtrans) + ast_translator_free_path(clone->readtrans); + clone->writetrans = NULL; + clone->readtrans = NULL; + clone->rawwriteformat = clone->nativeformats; + clone->rawreadformat = clone->nativeformats; +} + +/*! \brief Hangup a channel */ +int ast_hangup(struct ast_channel *chan) +{ + int res = 0; + + /* Don't actually hang up a channel that will masquerade as someone else, or + if someone is going to masquerade as us */ + ast_channel_lock(chan); + + detach_spies(chan); /* get rid of spies */ + + if (chan->masq) { + if (ast_do_masquerade(chan)) + ast_log(LOG_WARNING, "Failed to perform masquerade\n"); + } + + if (chan->masq) { + ast_log(LOG_WARNING, "%s getting hung up, but someone is trying to masq into us?!?\n", chan->name); + ast_channel_unlock(chan); + return 0; + } + /* If this channel is one which will be masqueraded into something, + mark it as a zombie already, so we know to free it later */ + if (chan->masqr) { + ast_set_flag(chan, AST_FLAG_ZOMBIE); + ast_channel_unlock(chan); + return 0; + } + free_translation(chan); + /* Close audio stream */ + if (chan->stream) { + ast_closestream(chan->stream); + chan->stream = NULL; + } + /* Close video stream */ + if (chan->vstream) { + ast_closestream(chan->vstream); + chan->vstream = NULL; + } + if (chan->sched) { + sched_context_destroy(chan->sched); + chan->sched = NULL; + } + + if (chan->generatordata) /* Clear any tone stuff remaining */ + chan->generator->release(chan, chan->generatordata); + chan->generatordata = NULL; + chan->generator = NULL; + if (chan->cdr) { /* End the CDR if it hasn't already */ + ast_cdr_end(chan->cdr); + ast_cdr_detach(chan->cdr); /* Post and Free the CDR */ + chan->cdr = NULL; + } + if (ast_test_flag(chan, AST_FLAG_BLOCKING)) { + ast_log(LOG_WARNING, "Hard hangup called by thread %ld on %s, while fd " + "is blocked by thread %ld in procedure %s! Expect a failure\n", + (long)pthread_self(), chan->name, (long)chan->blocker, chan->blockproc); + CRASH; + } + if (!ast_test_flag(chan, AST_FLAG_ZOMBIE)) { + if (option_debug) + ast_log(LOG_DEBUG, "Hanging up channel '%s'\n", chan->name); + if (chan->tech->hangup) + res = chan->tech->hangup(chan); + } else { + if (option_debug) + ast_log(LOG_DEBUG, "Hanging up zombie '%s'\n", chan->name); + } + + ast_channel_unlock(chan); + manager_event(EVENT_FLAG_CALL, "Hangup", + "Channel: %s\r\n" + "Uniqueid: %s\r\n" + "Cause: %d\r\n" + "Cause-txt: %s\r\n", + chan->name, + chan->uniqueid, + chan->hangupcause, + ast_cause2str(chan->hangupcause) + ); + ast_channel_free(chan); + return res; +} + +int ast_answer(struct ast_channel *chan) +{ + int res = 0; + ast_channel_lock(chan); + /* You can't answer an outbound call */ + if (ast_test_flag(chan, AST_FLAG_OUTGOING)) { + ast_channel_unlock(chan); + return 0; + } + /* Stop if we're a zombie or need a soft hangup */ + if (ast_test_flag(chan, AST_FLAG_ZOMBIE) || ast_check_hangup(chan)) { + ast_channel_unlock(chan); + return -1; + } + switch(chan->_state) { + case AST_STATE_RINGING: + case AST_STATE_RING: + if (chan->tech->answer) + res = chan->tech->answer(chan); + ast_setstate(chan, AST_STATE_UP); + ast_cdr_answer(chan->cdr); + break; + case AST_STATE_UP: + ast_cdr_answer(chan->cdr); + break; + default: + break; + } + ast_channel_unlock(chan); + return res; +} + +void ast_deactivate_generator(struct ast_channel *chan) +{ + ast_channel_lock(chan); + if (chan->generatordata) { + if (chan->generator && chan->generator->release) + chan->generator->release(chan, chan->generatordata); + chan->generatordata = NULL; + chan->generator = NULL; + chan->fds[AST_GENERATOR_FD] = -1; + ast_clear_flag(chan, AST_FLAG_WRITE_INT); + ast_settimeout(chan, 0, NULL, NULL); + } + ast_channel_unlock(chan); +} + +static int generator_force(void *data) +{ + /* Called if generator doesn't have data */ + void *tmp; + int res; + int (*generate)(struct ast_channel *chan, void *tmp, int datalen, int samples); + struct ast_channel *chan = data; + tmp = chan->generatordata; + chan->generatordata = NULL; + generate = chan->generator->generate; + res = generate(chan, tmp, 0, 160); + chan->generatordata = tmp; + if (res) { + ast_log(LOG_DEBUG, "Auto-deactivating generator\n"); + ast_deactivate_generator(chan); + } + return 0; +} + +int ast_activate_generator(struct ast_channel *chan, struct ast_generator *gen, void *params) +{ + int res = 0; + + ast_channel_lock(chan); + + if (chan->generatordata) { + if (chan->generator && chan->generator->release) + chan->generator->release(chan, chan->generatordata); + chan->generatordata = NULL; + } + + ast_prod(chan); + if (gen->alloc && !(chan->generatordata = gen->alloc(chan, params))) { + res = -1; + } + + if (!res) { + ast_settimeout(chan, 160, generator_force, chan); + chan->generator = gen; + } + + ast_channel_unlock(chan); + + return res; +} + +/*! \brief Wait for x amount of time on a file descriptor to have input. */ +int ast_waitfor_n_fd(int *fds, int n, int *ms, int *exception) +{ + int winner = -1; + ast_waitfor_nandfds(NULL, 0, fds, n, exception, &winner, ms); + return winner; +} + +/*! \brief Wait for x amount of time on a file descriptor to have input. */ +struct ast_channel *ast_waitfor_nandfds(struct ast_channel **c, int n, int *fds, int nfds, + int *exception, int *outfd, int *ms) +{ + struct timeval start = { 0 , 0 }; + struct pollfd *pfds; + int res; + long rms; + int x, y, max; + int sz; + time_t now = 0; + long whentohangup = 0, diff; + struct ast_channel *winner = NULL; + struct fdmap { + int chan; + int fdno; + } *fdmap; + + sz = n * AST_MAX_FDS + nfds; + pfds = alloca(sizeof(*pfds) * sz); + fdmap = alloca(sizeof(*fdmap) * sz); + + if (outfd) + *outfd = -99999; + if (exception) + *exception = 0; + + /* Perform any pending masquerades */ + for (x=0; x < n; x++) { + ast_channel_lock(c[x]); + if (c[x]->masq) { + if (ast_do_masquerade(c[x])) { + ast_log(LOG_WARNING, "Masquerade failed\n"); + *ms = -1; + ast_channel_unlock(c[x]); + return NULL; + } + } + if (c[x]->whentohangup) { + if (!whentohangup) + time(&now); + diff = c[x]->whentohangup - now; + if (diff < 1) { + /* Should already be hungup */ + c[x]->_softhangup |= AST_SOFTHANGUP_TIMEOUT; + ast_channel_unlock(c[x]); + return c[x]; + } + if (!whentohangup || (diff < whentohangup)) + whentohangup = diff; + } + ast_channel_unlock(c[x]); + } + /* Wait full interval */ + rms = *ms; + if (whentohangup) { + rms = (whentohangup - now) * 1000; /* timeout in milliseconds */ + if (*ms >= 0 && *ms < rms) /* original *ms still smaller */ + rms = *ms; + } + /* + * Build the pollfd array, putting the channels' fds first, + * followed by individual fds. Order is important because + * individual fd's must have priority over channel fds. + */ + max = 0; + for (x=0; xfds[y]); + } + CHECK_BLOCKING(c[x]); + } + /* Add the individual fds */ + for (x=0; x 0) + start = ast_tvnow(); + + if (sizeof(int) == 4) { /* XXX fix timeout > 600000 on linux x86-32 */ + do { + int kbrms = rms; + if (kbrms > 600000) + kbrms = 600000; + res = poll(pfds, max, kbrms); + if (!res) + rms -= kbrms; + } while (!res && (rms > 0)); + } else { + res = poll(pfds, max, rms); + } + for (x=0; xwhentohangup && now >= c[x]->whentohangup) { + c[x]->_softhangup |= AST_SOFTHANGUP_TIMEOUT; + if (winner == NULL) + winner = c[x]; + } + } + } + if (res == 0) { /* no fd ready, reset timeout and done */ + *ms = 0; /* XXX use 0 since we may not have an exact timeout. */ + return winner; + } + /* + * Then check if any channel or fd has a pending event. + * Remember to check channels first and fds last, as they + * must have priority on setting 'winner' + */ + for (x = 0; x < max; x++) { + res = pfds[x].revents; + if (res == 0) + continue; + if (fdmap[x].chan >= 0) { /* this is a channel */ + winner = c[fdmap[x].chan]; /* override previous winners */ + if (res & POLLPRI) + ast_set_flag(winner, AST_FLAG_EXCEPTION); + else + ast_clear_flag(winner, AST_FLAG_EXCEPTION); + winner->fdno = fdmap[x].fdno; + } else { /* this is an fd */ + if (outfd) + *outfd = pfds[x].fd; + if (exception) + *exception = (res & POLLPRI) ? -1 : 0; + winner = NULL; + } + } + if (*ms > 0) { + *ms -= ast_tvdiff_ms(ast_tvnow(), start); + if (*ms < 0) + *ms = 0; + } + return winner; +} + +struct ast_channel *ast_waitfor_n(struct ast_channel **c, int n, int *ms) +{ + return ast_waitfor_nandfds(c, n, NULL, 0, NULL, NULL, ms); +} + +int ast_waitfor(struct ast_channel *c, int ms) +{ + int oldms = ms; /* -1 if no timeout */ + + ast_waitfor_nandfds(&c, 1, NULL, 0, NULL, NULL, &ms); + if ((ms < 0) && (oldms < 0)) + ms = 0; + return ms; +} + +/* XXX never to be called with ms = -1 */ +int ast_waitfordigit(struct ast_channel *c, int ms) +{ + return ast_waitfordigit_full(c, ms, -1, -1); +} + +int ast_settimeout(struct ast_channel *c, int samples, int (*func)(void *data), void *data) +{ + int res = -1; +#ifdef HAVE_ZAPTEL + if (c->timingfd > -1) { + if (!func) { + samples = 0; + data = 0; + } + ast_log(LOG_DEBUG, "Scheduling timer at %d sample intervals\n", samples); + res = ioctl(c->timingfd, ZT_TIMERCONFIG, &samples); + c->timingfunc = func; + c->timingdata = data; + } +#endif + return res; +} + +int ast_waitfordigit_full(struct ast_channel *c, int ms, int audiofd, int cmdfd) +{ + + /* Stop if we're a zombie or need a soft hangup */ + if (ast_test_flag(c, AST_FLAG_ZOMBIE) || ast_check_hangup(c)) + return -1; + /* Wait for a digit, no more than ms milliseconds total. */ + while (ms) { + struct ast_channel *rchan; + int outfd; + + errno = 0; + rchan = ast_waitfor_nandfds(&c, 1, &cmdfd, (cmdfd > -1) ? 1 : 0, NULL, &outfd, &ms); + if (!rchan && outfd < 0 && ms) { + if (errno == 0 || errno == EINTR) + continue; + ast_log(LOG_WARNING, "Wait failed (%s)\n", strerror(errno)); + return -1; + } else if (outfd > -1) { + /* The FD we were watching has something waiting */ + return 1; + } else if (rchan) { + int res; + struct ast_frame *f = ast_read(c); + if (!f) + return -1; + + switch(f->frametype) { + case AST_FRAME_DTMF: + res = f->subclass; + ast_frfree(f); + return res; + case AST_FRAME_CONTROL: + switch(f->subclass) { + case AST_CONTROL_HANGUP: + ast_frfree(f); + return -1; + case AST_CONTROL_RINGING: + case AST_CONTROL_ANSWER: + /* Unimportant */ + break; + default: + ast_log(LOG_WARNING, "Unexpected control subclass '%d'\n", f->subclass); + } + case AST_FRAME_VOICE: + /* Write audio if appropriate */ + if (audiofd > -1) + write(audiofd, f->data, f->datalen); + } + /* Ignore */ + ast_frfree(f); + } + } + return 0; /* Time is up */ +} + +static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio) +{ + struct ast_frame *f = NULL; /* the return value */ + int blah; + int prestate; + + /* this function is very long so make sure there is only one return + * point at the end (there is only one exception to this). + */ + ast_channel_lock(chan); + if (chan->masq) { + if (ast_do_masquerade(chan)) { + ast_log(LOG_WARNING, "Failed to perform masquerade\n"); + } else { + f = &ast_null_frame; + } + goto done; + } + + /* Stop if we're a zombie or need a soft hangup */ + if (ast_test_flag(chan, AST_FLAG_ZOMBIE) || ast_check_hangup(chan)) { + if (chan->generator) + ast_deactivate_generator(chan); + goto done; + } + prestate = chan->_state; + + if (!ast_test_flag(chan, AST_FLAG_DEFER_DTMF) && !ast_strlen_zero(chan->dtmfq)) { + /* We have DTMF that has been deferred. Return it now */ + chan->dtmff.frametype = AST_FRAME_DTMF; + chan->dtmff.subclass = chan->dtmfq[0]; + /* Drop first digit from the buffer */ + memmove(chan->dtmfq, chan->dtmfq + 1, sizeof(chan->dtmfq) - 1); + f = &chan->dtmff; + goto done; + } + + /* Read and ignore anything on the alertpipe, but read only + one sizeof(blah) per frame that we send from it */ + if (chan->alertpipe[0] > -1) + read(chan->alertpipe[0], &blah, sizeof(blah)); + +#ifdef HAVE_ZAPTEL + if (chan->timingfd > -1 && chan->fdno == AST_TIMING_FD && ast_test_flag(chan, AST_FLAG_EXCEPTION)) { + int res; + + ast_clear_flag(chan, AST_FLAG_EXCEPTION); + blah = -1; + /* IF we can't get event, assume it's an expired as-per the old interface */ + res = ioctl(chan->timingfd, ZT_GETEVENT, &blah); + if (res) + blah = ZT_EVENT_TIMER_EXPIRED; + + if (blah == ZT_EVENT_TIMER_PING) { + if (!chan->readq || !chan->readq->next) { + /* Acknowledge PONG unless we need it again */ + if (ioctl(chan->timingfd, ZT_TIMERPONG, &blah)) { + ast_log(LOG_WARNING, "Failed to pong timer on '%s': %s\n", chan->name, strerror(errno)); + } + } + } else if (blah == ZT_EVENT_TIMER_EXPIRED) { + ioctl(chan->timingfd, ZT_TIMERACK, &blah); + if (chan->timingfunc) { + /* save a copy of func/data before unlocking the channel */ + int (*func)(void *) = chan->timingfunc; + void *data = chan->timingdata; + ast_channel_unlock(chan); + func(data); + } else { + blah = 0; + ioctl(chan->timingfd, ZT_TIMERCONFIG, &blah); + chan->timingdata = NULL; + ast_channel_unlock(chan); + } + /* cannot 'goto done' because the channel is already unlocked */ + return &ast_null_frame; + } else + ast_log(LOG_NOTICE, "No/unknown event '%d' on timer for '%s'?\n", blah, chan->name); + } else +#endif + if (chan->fds[AST_GENERATOR_FD] > -1 && chan->fdno == AST_GENERATOR_FD) { + /* if the AST_GENERATOR_FD is set, call the generator with args + * set to -1 so it can do whatever it needs to. + */ + void *tmp = chan->generatordata; + chan->generatordata = NULL; /* reset to let ast_write get through */ + chan->generator->generate(chan, tmp, -1, -1); + chan->generatordata = tmp; + f = &ast_null_frame; + goto done; + } + + /* Check for pending read queue */ + if (chan->readq) { + f = chan->readq; + chan->readq = f->next; + f->next = NULL; + /* Interpret hangup and return NULL */ + /* XXX why not the same for frames from the channel ? */ + if (f->frametype == AST_FRAME_CONTROL && f->subclass == AST_CONTROL_HANGUP) { + ast_frfree(f); + f = NULL; + } + } else { + chan->blocker = pthread_self(); + if (ast_test_flag(chan, AST_FLAG_EXCEPTION)) { + if (chan->tech->exception) + f = chan->tech->exception(chan); + else { + ast_log(LOG_WARNING, "Exception flag set on '%s', but no exception handler\n", chan->name); + f = &ast_null_frame; + } + /* Clear the exception flag */ + ast_clear_flag(chan, AST_FLAG_EXCEPTION); + } else if (chan->tech->read) + f = chan->tech->read(chan); + else + ast_log(LOG_WARNING, "No read routine on channel %s\n", chan->name); + } + + if (f) { + /* if the channel driver returned more than one frame, stuff the excess + into the readq for the next ast_read call + */ + if (f->next) { + chan->readq = f->next; + f->next = NULL; + } + + switch (f->frametype) { + case AST_FRAME_CONTROL: + if (f->subclass == AST_CONTROL_ANSWER) { + if (!ast_test_flag(chan, AST_FLAG_OUTGOING)) { + ast_log(LOG_DEBUG, "Ignoring answer on an inbound call!\n"); + ast_frfree(f); + f = &ast_null_frame; + } else if (prestate == AST_STATE_UP) { + ast_log(LOG_DEBUG, "Dropping duplicate answer!\n"); + ast_frfree(f); + f = &ast_null_frame; + } else { + /* Answer the CDR */ + ast_setstate(chan, AST_STATE_UP); + ast_cdr_answer(chan->cdr); + } + } + break; + case AST_FRAME_DTMF: + ast_log(LOG_DTMF, "DTMF '%c' received on %s\n", f->subclass, chan->name); + if (ast_test_flag(chan, AST_FLAG_DEFER_DTMF)) { + if (strlen(chan->dtmfq) < sizeof(chan->dtmfq) - 2) + chan->dtmfq[strlen(chan->dtmfq)] = f->subclass; + else + ast_log(LOG_WARNING, "Dropping deferred DTMF digits on %s\n", chan->name); + ast_frfree(f); + f = &ast_null_frame; + } + break; + case AST_FRAME_DTMF_BEGIN: + ast_log(LOG_DTMF, "DTMF begin '%c' received on %s\n", f->subclass, chan->name); + break; + case AST_FRAME_DTMF_END: + ast_log(LOG_DTMF, "DTMF end '%c' received on %s\n", f->subclass, chan->name); + break; + case AST_FRAME_VOICE: + if (dropaudio) { + ast_frfree(f); + f = &ast_null_frame; + } else if (!(f->subclass & chan->nativeformats)) { + /* This frame can't be from the current native formats -- drop it on the + floor */ + ast_log(LOG_NOTICE, "Dropping incompatible voice frame on %s of format %s since our native format has changed to %s\n", + chan->name, ast_getformatname(f->subclass), ast_getformatname(chan->nativeformats)); + ast_frfree(f); + f = &ast_null_frame; + } else { + if (chan->spies) + queue_frame_to_spies(chan, f, SPY_READ); + + if (chan->monitor && chan->monitor->read_stream ) { + /* XXX what does this do ? */ +#ifndef MONITOR_CONSTANT_DELAY + int jump = chan->outsmpl - chan->insmpl - 4 * f->samples; + if (jump >= 0) { + if (ast_seekstream(chan->monitor->read_stream, jump + f->samples, SEEK_FORCECUR) == -1) + ast_log(LOG_WARNING, "Failed to perform seek in monitoring read stream, synchronization between the files may be broken\n"); + chan->insmpl += jump + 4 * f->samples; + } else + chan->insmpl+= f->samples; +#else + int jump = chan->outsmpl - chan->insmpl; + if (jump - MONITOR_DELAY >= 0) { + if (ast_seekstream(chan->monitor->read_stream, jump - f->samples, SEEK_FORCECUR) == -1) + ast_log(LOG_WARNING, "Failed to perform seek in monitoring read stream, synchronization between the files may be broken\n"); + chan->insmpl += jump; + } else + chan->insmpl += f->samples; +#endif + if (chan->monitor->state == AST_MONITOR_RUNNING) { + if (ast_writestream(chan->monitor->read_stream, f) < 0) + ast_log(LOG_WARNING, "Failed to write data to channel monitor read stream\n"); + } + } + + if (chan->readtrans && (f = ast_translate(chan->readtrans, f, 1)) == NULL) + f = &ast_null_frame; + + /* Run generator sitting on the line if timing device not available + * and synchronous generation of outgoing frames is necessary */ + if (chan->generatordata && !ast_internal_timing_enabled(chan)) { + void *tmp = chan->generatordata; + int res; + + if (chan->timingfunc) { + if (option_debug > 1) + ast_log(LOG_DEBUG, "Generator got voice, switching to phase locked mode\n"); + ast_settimeout(chan, 0, NULL, NULL); + } + + chan->generatordata = NULL; /* reset, to let writes go through */ + res = chan->generator->generate(chan, tmp, f->datalen, f->samples); + chan->generatordata = tmp; + if (res) { + if (option_debug > 1) + ast_log(LOG_DEBUG, "Auto-deactivating generator\n"); + ast_deactivate_generator(chan); + } + + } else if (f->frametype == AST_FRAME_CNG) { + if (chan->generator && !chan->timingfunc && (chan->timingfd > -1)) { + if (option_debug > 1) + ast_log(LOG_DEBUG, "Generator got CNG, switching to timed mode\n"); + ast_settimeout(chan, 160, generator_force, chan); + } + } + } + } + } else { + /* Make sure we always return NULL in the future */ + chan->_softhangup |= AST_SOFTHANGUP_DEV; + if (chan->generator) + ast_deactivate_generator(chan); + /* End the CDR if appropriate */ + if (chan->cdr) + ast_cdr_end(chan->cdr); + } + + /* High bit prints debugging */ + if (chan->fin & DEBUGCHAN_FLAG) + ast_frame_dump(chan->name, f, "<<"); + chan->fin = FRAMECOUNT_INC(chan->fin); + +done: + ast_channel_unlock(chan); + return f; +} + +int ast_internal_timing_enabled(struct ast_channel *chan) +{ + int ret = ast_opt_internal_timing && chan->timingfd > -1; + if (option_debug > 4) + ast_log(LOG_DEBUG, "Internal timing is %s (option_internal_timing=%d chan->timingfd=%d)\n", ret? "enabled": "disabled", ast_opt_internal_timing, chan->timingfd); + return ret; +} + +struct ast_frame *ast_read(struct ast_channel *chan) +{ + return __ast_read(chan, 0); +} + +struct ast_frame *ast_read_noaudio(struct ast_channel *chan) +{ + return __ast_read(chan, 1); +} + +int ast_indicate(struct ast_channel *chan, int condition) +{ + return ast_indicate_data(chan, condition, NULL, 0); +} + +int ast_indicate_data(struct ast_channel *chan, int condition, const void *data, size_t datalen) +{ + int res = -1; + + ast_channel_lock(chan); + /* Stop if we're a zombie or need a soft hangup */ + if (ast_test_flag(chan, AST_FLAG_ZOMBIE) || ast_check_hangup(chan)) { + ast_channel_unlock(chan); + return -1; + } + if (chan->tech->indicate) + res = chan->tech->indicate(chan, condition, data, datalen); + ast_channel_unlock(chan); + if (!chan->tech->indicate || res) { + /* + * Device does not support (that) indication, lets fake + * it by doing our own tone generation. (PM2002) + */ + if (condition < 0) + ast_playtones_stop(chan); + else { + const struct tone_zone_sound *ts = NULL; + switch (condition) { + case AST_CONTROL_RINGING: + ts = ast_get_indication_tone(chan->zone, "ring"); + break; + case AST_CONTROL_BUSY: + ts = ast_get_indication_tone(chan->zone, "busy"); + break; + case AST_CONTROL_CONGESTION: + ts = ast_get_indication_tone(chan->zone, "congestion"); + break; + } + if (ts && ts->data[0]) { + ast_log(LOG_DEBUG, "Driver for channel '%s' does not support indication %d, emulating it\n", chan->name, condition); + ast_playtones_start(chan,0,ts->data, 1); + res = 0; + } else if (condition == AST_CONTROL_PROGRESS) { + /* ast_playtones_stop(chan); */ + } else if (condition == AST_CONTROL_PROCEEDING) { + /* Do nothing, really */ + } else if (condition == AST_CONTROL_HOLD) { + /* Do nothing.... */ + } else if (condition == AST_CONTROL_UNHOLD) { + /* Do nothing.... */ + } else if (condition == AST_CONTROL_VIDUPDATE) { + /* Do nothing.... */ + } else { + /* not handled */ + ast_log(LOG_WARNING, "Unable to handle indication %d for '%s'\n", condition, chan->name); + res = -1; + } + } + } + return res; +} + +int ast_recvchar(struct ast_channel *chan, int timeout) +{ + int c; + char *buf = ast_recvtext(chan, timeout); + if (buf == NULL) + return -1; /* error or timeout */ + c = *(unsigned char *)buf; + free(buf); + return c; +} + +char *ast_recvtext(struct ast_channel *chan, int timeout) +{ + int res, done = 0; + char *buf = NULL; + + while (!done) { + struct ast_frame *f; + if (ast_check_hangup(chan)) + break; + res = ast_waitfor(chan, timeout); + if (res <= 0) /* timeout or error */ + break; + timeout = res; /* update timeout */ + f = ast_read(chan); + if (f == NULL) + break; /* no frame */ + if (f->frametype == AST_FRAME_CONTROL && f->subclass == AST_CONTROL_HANGUP) + done = 1; /* force a break */ + else if (f->frametype == AST_FRAME_TEXT) { /* what we want */ + buf = ast_strndup((char *) f->data, f->datalen); /* dup and break */ + done = 1; + } + ast_frfree(f); + } + return buf; +} + +int ast_sendtext(struct ast_channel *chan, const char *text) +{ + int res = 0; + /* Stop if we're a zombie or need a soft hangup */ + if (ast_test_flag(chan, AST_FLAG_ZOMBIE) || ast_check_hangup(chan)) + return -1; + CHECK_BLOCKING(chan); + if (chan->tech->send_text) + res = chan->tech->send_text(chan, text); + ast_clear_flag(chan, AST_FLAG_BLOCKING); + return res; +} + +static int do_senddigit(struct ast_channel *chan, char digit) +{ + int res = -1; + + if (chan->tech->send_digit) + res = chan->tech->send_digit(chan, digit); + if (res) { + /* + * Device does not support DTMF tones, lets fake + * it by doing our own generation. (PM2002) + */ + static const char* dtmf_tones[] = { + "!941+1336/100,!0/100", /* 0 */ + "!697+1209/100,!0/100", /* 1 */ + "!697+1336/100,!0/100", /* 2 */ + "!697+1477/100,!0/100", /* 3 */ + "!770+1209/100,!0/100", /* 4 */ + "!770+1336/100,!0/100", /* 5 */ + "!770+1477/100,!0/100", /* 6 */ + "!852+1209/100,!0/100", /* 7 */ + "!852+1336/100,!0/100", /* 8 */ + "!852+1477/100,!0/100", /* 9 */ + "!697+1633/100,!0/100", /* A */ + "!770+1633/100,!0/100", /* B */ + "!852+1633/100,!0/100", /* C */ + "!941+1633/100,!0/100", /* D */ + "!941+1209/100,!0/100", /* * */ + "!941+1477/100,!0/100" }; /* # */ + if (digit >= '0' && digit <='9') + ast_playtones_start(chan, 0, dtmf_tones[digit-'0'], 0); + else if (digit >= 'A' && digit <= 'D') + ast_playtones_start(chan, 0, dtmf_tones[digit-'A'+10], 0); + else if (digit == '*') + ast_playtones_start(chan, 0, dtmf_tones[14], 0); + else if (digit == '#') + ast_playtones_start(chan, 0, dtmf_tones[15], 0); + else { + /* not handled */ + ast_log(LOG_DEBUG, "Unable to generate DTMF tone '%c' for '%s'\n", digit, chan->name); + } + } + return 0; +} + +int ast_senddigit(struct ast_channel *chan, char digit) +{ + return do_senddigit(chan, digit); +} + +int ast_prod(struct ast_channel *chan) +{ + struct ast_frame a = { AST_FRAME_VOICE }; + char nothing[128]; + + /* Send an empty audio frame to get things moving */ + if (chan->_state != AST_STATE_UP) { + ast_log(LOG_DEBUG, "Prodding channel '%s'\n", chan->name); + a.subclass = chan->rawwriteformat; + a.data = nothing + AST_FRIENDLY_OFFSET; + a.src = "ast_prod"; + if (ast_write(chan, &a)) + ast_log(LOG_WARNING, "Prodding channel '%s' failed\n", chan->name); + } + return 0; +} + +int ast_write_video(struct ast_channel *chan, struct ast_frame *fr) +{ + int res; + if (!chan->tech->write_video) + return 0; + res = ast_write(chan, fr); + if (!res) + res = 1; + return res; +} + +int ast_write(struct ast_channel *chan, struct ast_frame *fr) +{ + int res = -1; + struct ast_frame *f = NULL; + + /* Stop if we're a zombie or need a soft hangup */ + ast_channel_lock(chan); + if (ast_test_flag(chan, AST_FLAG_ZOMBIE) || ast_check_hangup(chan)) + goto done; + + /* Handle any pending masquerades */ + if (chan->masq && ast_do_masquerade(chan)) { + ast_log(LOG_WARNING, "Failed to perform masquerade\n"); + goto done; + } + if (chan->masqr) { + res = 0; /* XXX explain, why 0 ? */ + goto done; + } + if (chan->generatordata) { + if (ast_test_flag(chan, AST_FLAG_WRITE_INT)) + ast_deactivate_generator(chan); + else { + res = 0; /* XXX explain, why 0 ? */ + goto done; + } + } + /* High bit prints debugging */ + if (chan->fout & DEBUGCHAN_FLAG) + ast_frame_dump(chan->name, fr, ">>"); + CHECK_BLOCKING(chan); + switch(fr->frametype) { + case AST_FRAME_CONTROL: + res = (chan->tech->indicate == NULL) ? 0 : + chan->tech->indicate(chan, fr->subclass, fr->data, fr->datalen); + break; + case AST_FRAME_DTMF_BEGIN: + res = (chan->tech->send_digit_begin == NULL) ? 0 : + chan->tech->send_digit_begin(chan, fr->subclass); + break; + case AST_FRAME_DTMF_END: + res = (chan->tech->send_digit_end == NULL) ? 0 : + chan->tech->send_digit_end(chan); + break; + case AST_FRAME_DTMF: + ast_clear_flag(chan, AST_FLAG_BLOCKING); + ast_channel_unlock(chan); + res = do_senddigit(chan,fr->subclass); + ast_channel_lock(chan); + CHECK_BLOCKING(chan); + break; + case AST_FRAME_TEXT: + res = (chan->tech->send_text == NULL) ? 0 : + chan->tech->send_text(chan, (char *) fr->data); + break; + case AST_FRAME_HTML: + res = (chan->tech->send_html == NULL) ? 0 : + chan->tech->send_html(chan, fr->subclass, (char *) fr->data, fr->datalen); + break; + case AST_FRAME_VIDEO: + /* XXX Handle translation of video codecs one day XXX */ + res = (chan->tech->write_video == NULL) ? 0 : + chan->tech->write_video(chan, fr); + break; + case AST_FRAME_MODEM: + res = (chan->tech->write == NULL) ? 0 : + chan->tech->write(chan, fr); + break; + case AST_FRAME_VOICE: + if (chan->tech->write == NULL) + break; /*! \todo XXX should return 0 maybe ? */ + + /* Bypass translator if we're writing format in the raw write format. This + allows mixing of native / non-native formats */ + if (fr->subclass == chan->rawwriteformat) + f = fr; + else + f = (chan->writetrans) ? ast_translate(chan->writetrans, fr, 0) : fr; + if (f == NULL) { + res = 0; + } else { + if (chan->spies) + queue_frame_to_spies(chan, f, SPY_WRITE); + + if (chan->monitor && chan->monitor->write_stream) { + /* XXX must explain this code */ +#ifndef MONITOR_CONSTANT_DELAY + int jump = chan->insmpl - chan->outsmpl - 4 * f->samples; + if (jump >= 0) { + if (ast_seekstream(chan->monitor->write_stream, jump + f->samples, SEEK_FORCECUR) == -1) + ast_log(LOG_WARNING, "Failed to perform seek in monitoring write stream, synchronization between the files may be broken\n"); + chan->outsmpl += jump + 4 * f->samples; + } else + chan->outsmpl += f->samples; +#else + int jump = chan->insmpl - chan->outsmpl; + if (jump - MONITOR_DELAY >= 0) { + if (ast_seekstream(chan->monitor->write_stream, jump - f->samples, SEEK_FORCECUR) == -1) + ast_log(LOG_WARNING, "Failed to perform seek in monitoring write stream, synchronization between the files may be broken\n"); + chan->outsmpl += jump; + } else + chan->outsmpl += f->samples; +#endif + if (chan->monitor->state == AST_MONITOR_RUNNING) { + if (ast_writestream(chan->monitor->write_stream, f) < 0) + ast_log(LOG_WARNING, "Failed to write data to channel monitor write stream\n"); + } + } + + if (ast_test_flag(chan, AST_FLAG_WHISPER)) { + /* frame is assumed to be in SLINEAR, since that is + required for whisper mode */ + ast_frame_adjust_volume(f, -2); + if (ast_slinfactory_available(&chan->whisper->sf) >= f->samples) { + short buf[f->samples]; + struct ast_frame whisper = { + .frametype = AST_FRAME_VOICE, + .subclass = AST_FORMAT_SLINEAR, + .data = buf, + .datalen = sizeof(buf), + .samples = f->samples, + }; + + ast_mutex_lock(&chan->whisper->lock); + if (ast_slinfactory_read(&chan->whisper->sf, buf, f->samples)) + ast_frame_slinear_sum(f, &whisper); + ast_mutex_unlock(&chan->whisper->lock); + } + } + + res = chan->tech->write(chan, f); + } + break; + } + + if (f && f != fr) + ast_frfree(f); + ast_clear_flag(chan, AST_FLAG_BLOCKING); + /* Consider a write failure to force a soft hangup */ + if (res < 0) + chan->_softhangup |= AST_SOFTHANGUP_DEV; + else { + chan->fout = FRAMECOUNT_INC(chan->fout); + } +done: + ast_channel_unlock(chan); + return res; +} + +static int set_format(struct ast_channel *chan, int fmt, int *rawformat, int *format, + struct ast_trans_pvt **trans, const int direction) +{ + int native; + int res; + + /* Make sure we only consider audio */ + fmt &= AST_FORMAT_AUDIO_MASK; + + native = chan->nativeformats; + /* Find a translation path from the native format to one of the desired formats */ + if (!direction) + /* reading */ + res = ast_translator_best_choice(&fmt, &native); + else + /* writing */ + res = ast_translator_best_choice(&native, &fmt); + + if (res < 0) { + ast_log(LOG_WARNING, "Unable to find a codec translation path from %s to %s\n", + ast_getformatname(native), ast_getformatname(fmt)); + return -1; + } + + /* Now we have a good choice for both. */ + ast_channel_lock(chan); + + if ((*rawformat == native) && (*format == fmt)) { + /* the channel is already in these formats, so nothing to do */ + ast_channel_unlock(chan); + return 0; + } + + *rawformat = native; + /* User perspective is fmt */ + *format = fmt; + /* Free any read translation we have right now */ + if (*trans) + ast_translator_free_path(*trans); + /* Build a translation path from the raw format to the desired format */ + if (!direction) + /* reading */ + *trans = ast_translator_build_path(*format, *rawformat); + else + /* writing */ + *trans = ast_translator_build_path(*rawformat, *format); + ast_channel_unlock(chan); + if (option_debug) + ast_log(LOG_DEBUG, "Set channel %s to %s format %s\n", chan->name, + direction ? "write" : "read", ast_getformatname(fmt)); + return 0; +} + +int ast_set_read_format(struct ast_channel *chan, int fmt) +{ + return set_format(chan, fmt, &chan->rawreadformat, &chan->readformat, + &chan->readtrans, 0); +} + +int ast_set_write_format(struct ast_channel *chan, int fmt) +{ + return set_format(chan, fmt, &chan->rawwriteformat, &chan->writeformat, + &chan->writetrans, 1); +} + +struct ast_channel *__ast_request_and_dial(const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name, struct outgoing_helper *oh) +{ + int dummy_outstate; + int cause = 0; + struct ast_channel *chan; + int res = 0; + + if (outstate) + *outstate = 0; + else + outstate = &dummy_outstate; /* make outstate always a valid pointer */ + + chan = ast_request(type, format, data, &cause); + if (!chan) { + ast_log(LOG_NOTICE, "Unable to request channel %s/%s\n", type, (char *)data); + /* compute error and return */ + if (cause == AST_CAUSE_BUSY) + *outstate = AST_CONTROL_BUSY; + else if (cause == AST_CAUSE_CONGESTION) + *outstate = AST_CONTROL_CONGESTION; + return NULL; + } + + if (oh) { + if (oh->vars) + ast_set_variables(chan, oh->vars); + /* XXX why is this necessary, for the parent_channel perhaps ? */ + if (!ast_strlen_zero(oh->cid_num) && !ast_strlen_zero(oh->cid_name)) + ast_set_callerid(chan, oh->cid_num, oh->cid_name, oh->cid_num); + if (oh->parent_channel) + ast_channel_inherit_variables(oh->parent_channel, chan); + if (oh->account) + ast_cdr_setaccount(chan, oh->account); + } + ast_set_callerid(chan, cid_num, cid_name, cid_num); + + if (ast_call(chan, data, 0)) { /* ast_call failed... */ + ast_log(LOG_NOTICE, "Unable to call channel %s/%s\n", type, (char *)data); + } else { + res = 1; /* mark success in case chan->_state is already AST_STATE_UP */ + while (timeout && chan->_state != AST_STATE_UP) { + struct ast_frame *f; + res = ast_waitfor(chan, timeout); + if (res <= 0) /* error, timeout, or done */ + break; + if (timeout > -1) + timeout = res; + f = ast_read(chan); + if (!f) { + *outstate = AST_CONTROL_HANGUP; + res = 0; + break; + } + if (f->frametype == AST_FRAME_CONTROL) { + switch (f->subclass) { + case AST_CONTROL_RINGING: /* record but keep going */ + *outstate = f->subclass; + break; + + case AST_CONTROL_BUSY: + case AST_CONTROL_CONGESTION: + case AST_CONTROL_ANSWER: + *outstate = f->subclass; + timeout = 0; /* trick to force exit from the while() */ + break; + + /* Ignore these */ + case AST_CONTROL_PROGRESS: + case AST_CONTROL_PROCEEDING: + case AST_CONTROL_HOLD: + case AST_CONTROL_UNHOLD: + case AST_CONTROL_VIDUPDATE: + case -1: /* Ignore -- just stopping indications */ + break; + + default: + ast_log(LOG_NOTICE, "Don't know what to do with control frame %d\n", f->subclass); + } + } + ast_frfree(f); + } + } + + /* Final fixups */ + if (oh) { + if (!ast_strlen_zero(oh->context)) + ast_copy_string(chan->context, oh->context, sizeof(chan->context)); + if (!ast_strlen_zero(oh->exten)) + ast_copy_string(chan->exten, oh->exten, sizeof(chan->exten)); + if (oh->priority) + chan->priority = oh->priority; + } + if (chan->_state == AST_STATE_UP) + *outstate = AST_CONTROL_ANSWER; + + if (res <= 0) { + if (!chan->cdr && (chan->cdr = ast_cdr_alloc())) + ast_cdr_init(chan->cdr, chan); + if (chan->cdr) { + char tmp[256]; + snprintf(tmp, sizeof(tmp), "%s/%s", type, (char *)data); + ast_cdr_setapp(chan->cdr,"Dial",tmp); + ast_cdr_update(chan); + ast_cdr_start(chan->cdr); + ast_cdr_end(chan->cdr); + /* If the cause wasn't handled properly */ + if (ast_cdr_disposition(chan->cdr,chan->hangupcause)) + ast_cdr_failed(chan->cdr); + } + ast_hangup(chan); + chan = NULL; + } + return chan; +} + +struct ast_channel *ast_request_and_dial(const char *type, int format, void *data, int timeout, int *outstate, const char *cidnum, const char *cidname) +{ + return __ast_request_and_dial(type, format, data, timeout, outstate, cidnum, cidname, NULL); +} + +struct ast_channel *ast_request(const char *type, int format, void *data, int *cause) +{ + struct chanlist *chan; + struct ast_channel *c; + int capabilities; + int fmt; + int res; + int foo; + int videoformat = format & AST_FORMAT_VIDEO_MASK; + + if (!cause) + cause = &foo; + *cause = AST_CAUSE_NOTDEFINED; + + if (AST_LIST_LOCK(&channels)) { + ast_log(LOG_WARNING, "Unable to lock channel list\n"); + return NULL; + } + + AST_LIST_TRAVERSE(&backends, chan, list) { + if (strcasecmp(type, chan->tech->type)) + continue; + + capabilities = chan->tech->capabilities; + fmt = format & AST_FORMAT_AUDIO_MASK; + res = ast_translator_best_choice(&fmt, &capabilities); + if (res < 0) { + ast_log(LOG_WARNING, "No translator path exists for channel type %s (native %d) to %d\n", type, chan->tech->capabilities, format); + AST_LIST_UNLOCK(&channels); + return NULL; + } + AST_LIST_UNLOCK(&channels); + if (!chan->tech->requester) + return NULL; + + if (!(c = chan->tech->requester(type, capabilities | videoformat, data, cause))) + return NULL; + + if (c->_state == AST_STATE_DOWN) { + manager_event(EVENT_FLAG_CALL, "Newchannel", + "Channel: %s\r\n" + "State: %s\r\n" + "CallerID: %s\r\n" + "CallerIDName: %s\r\n" + "Uniqueid: %s\r\n", + c->name, ast_state2str(c->_state), + S_OR(c->cid.cid_num, ""), + S_OR(c->cid.cid_name, ""), + c->uniqueid); + } + return c; + } + + ast_log(LOG_WARNING, "No channel type registered for '%s'\n", type); + *cause = AST_CAUSE_NOSUCHDRIVER; + AST_LIST_UNLOCK(&channels); + + return NULL; +} + +int ast_call(struct ast_channel *chan, char *addr, int timeout) +{ + /* Place an outgoing call, but don't wait any longer than timeout ms before returning. + If the remote end does not answer within the timeout, then do NOT hang up, but + return anyway. */ + int res = -1; + /* Stop if we're a zombie or need a soft hangup */ + ast_channel_lock(chan); + if (!ast_test_flag(chan, AST_FLAG_ZOMBIE) && !ast_check_hangup(chan)) { + if (chan->tech->call) + res = chan->tech->call(chan, addr, timeout); + ast_set_flag(chan, AST_FLAG_OUTGOING); + } + ast_channel_unlock(chan); + return res; +} + +/*! + \brief Transfer a call to dest, if the channel supports transfer + + Called by: + \arg app_transfer + \arg the manager interface +*/ +int ast_transfer(struct ast_channel *chan, char *dest) +{ + int res = -1; + + /* Stop if we're a zombie or need a soft hangup */ + ast_channel_lock(chan); + if (!ast_test_flag(chan, AST_FLAG_ZOMBIE) && !ast_check_hangup(chan)) { + if (chan->tech->transfer) { + res = chan->tech->transfer(chan, dest); + if (!res) + res = 1; + } else + res = 0; + } + ast_channel_unlock(chan); + return res; +} + +int ast_readstring(struct ast_channel *c, char *s, int len, int timeout, int ftimeout, char *enders) +{ + return ast_readstring_full(c, s, len, timeout, ftimeout, enders, -1, -1); +} + +int ast_readstring_full(struct ast_channel *c, char *s, int len, int timeout, int ftimeout, char *enders, int audiofd, int ctrlfd) +{ + int pos = 0; /* index in the buffer where we accumulate digits */ + int to = ftimeout; + + /* Stop if we're a zombie or need a soft hangup */ + if (ast_test_flag(c, AST_FLAG_ZOMBIE) || ast_check_hangup(c)) + return -1; + if (!len) + return -1; + for (;;) { + int d; + if (c->stream) { + d = ast_waitstream_full(c, AST_DIGIT_ANY, audiofd, ctrlfd); + ast_stopstream(c); + usleep(1000); + if (!d) + d = ast_waitfordigit_full(c, to, audiofd, ctrlfd); + } else { + d = ast_waitfordigit_full(c, to, audiofd, ctrlfd); + } + if (d < 0) + return -1; + if (d == 0) { + s[pos]='\0'; + return 1; + } + if (d == 1) { + s[pos]='\0'; + return 2; + } + if (!strchr(enders, d)) + s[pos++] = d; + if (strchr(enders, d) || (pos >= len)) { + s[pos]='\0'; + return 0; + } + to = timeout; + } + /* Never reached */ + return 0; +} + +int ast_channel_supports_html(struct ast_channel *chan) +{ + return (chan->tech->send_html) ? 1 : 0; +} + +int ast_channel_sendhtml(struct ast_channel *chan, int subclass, const char *data, int datalen) +{ + if (chan->tech->send_html) + return chan->tech->send_html(chan, subclass, data, datalen); + return -1; +} + +int ast_channel_sendurl(struct ast_channel *chan, const char *url) +{ + return ast_channel_sendhtml(chan, AST_HTML_URL, url, strlen(url) + 1); +} + +int ast_channel_make_compatible(struct ast_channel *chan, struct ast_channel *peer) +{ + int src; + int dst; + + /* Set up translation from the chan to the peer */ + src = chan->nativeformats; + dst = peer->nativeformats; + if (ast_translator_best_choice(&dst, &src) < 0) { + ast_log(LOG_WARNING, "No path to translate from %s(%d) to %s(%d)\n", chan->name, src, peer->name, dst); + return -1; + } + + /* if the best path is not 'pass through', then + transcoding is needed; if desired, force transcode path + to use SLINEAR between channels, but only if there is + no direct conversion available */ + if ((src != dst) && ast_opt_transcode_via_slin && + (ast_translate_path_steps(dst, src) != 1)) + dst = AST_FORMAT_SLINEAR; + if (ast_set_read_format(chan, dst) < 0) { + ast_log(LOG_WARNING, "Unable to set read format on channel %s to %d\n", chan->name, dst); + return -1; + } + if (ast_set_write_format(peer, dst) < 0) { + ast_log(LOG_WARNING, "Unable to set write format on channel %s to %d\n", peer->name, dst); + return -1; + } + + /* Set up translation from the peer to the chan */ + src = peer->nativeformats; + dst = chan->nativeformats; + if (ast_translator_best_choice(&dst, &src) < 0) { + ast_log(LOG_WARNING, "No path to translate from %s(%d) to %s(%d)\n", peer->name, src, chan->name, dst); + return -1; + } + + /* if the best path is not 'pass through', then + transcoding is needed; if desired, force transcode path + to use SLINEAR between channels, but only if there is + no direct conversion available */ + if ((src != dst) && ast_opt_transcode_via_slin && + (ast_translate_path_steps(dst, src) != 1)) + dst = AST_FORMAT_SLINEAR; + if (ast_set_read_format(peer, dst) < 0) { + ast_log(LOG_WARNING, "Unable to set read format on channel %s to %d\n", peer->name, dst); + return -1; + } + if (ast_set_write_format(chan, dst) < 0) { + ast_log(LOG_WARNING, "Unable to set write format on channel %s to %d\n", chan->name, dst); + return -1; + } + return 0; +} + +int ast_channel_masquerade(struct ast_channel *original, struct ast_channel *clone) +{ + int res = -1; + struct ast_channel *final_orig = original, *final_clone = clone; + + ast_channel_lock(original); + while (ast_channel_trylock(clone)) { + ast_channel_unlock(original); + usleep(1); + ast_channel_lock(original); + } + + /* each of these channels may be sitting behind a channel proxy (i.e. chan_agent) + and if so, we don't really want to masquerade it, but its proxy */ + if (original->_bridge && (original->_bridge != ast_bridged_channel(original))) + final_orig = original->_bridge; + + if (clone->_bridge && (clone->_bridge != ast_bridged_channel(clone))) + final_clone = clone->_bridge; + + if ((final_orig != original) || (final_clone != clone)) { + ast_channel_lock(final_orig); + while (ast_channel_trylock(final_clone)) { + ast_channel_unlock(final_orig); + usleep(1); + ast_channel_lock(final_orig); + } + ast_channel_unlock(clone); + ast_channel_unlock(original); + original = final_orig; + clone = final_clone; + } + + if (original == clone) { + ast_log(LOG_WARNING, "Can't masquerade channel '%s' into itself!\n", original->name); + ast_channel_unlock(clone); + ast_channel_unlock(original); + return -1; + } + + ast_log(LOG_DEBUG, "Planning to masquerade channel %s into the structure of %s\n", + clone->name, original->name); + if (original->masq) { + ast_log(LOG_WARNING, "%s is already going to masquerade as %s\n", + original->masq->name, original->name); + } else if (clone->masqr) { + ast_log(LOG_WARNING, "%s is already going to masquerade as %s\n", + clone->name, clone->masqr->name); + } else { + original->masq = clone; + clone->masqr = original; + ast_queue_frame(original, &ast_null_frame); + ast_queue_frame(clone, &ast_null_frame); + ast_log(LOG_DEBUG, "Done planning to masquerade channel %s into the structure of %s\n", clone->name, original->name); + res = 0; + } + + ast_channel_unlock(clone); + ast_channel_unlock(original); + + return res; +} + +void ast_change_name(struct ast_channel *chan, char *newname) +{ + manager_event(EVENT_FLAG_CALL, "Rename", "Oldname: %s\r\nNewname: %s\r\nUniqueid: %s\r\n", chan->name, newname, chan->uniqueid); + ast_string_field_set(chan, name, newname); +} + +void ast_channel_inherit_variables(const struct ast_channel *parent, struct ast_channel *child) +{ + struct ast_var_t *current, *newvar; + const char *varname; + + AST_LIST_TRAVERSE(&parent->varshead, current, entries) { + int vartype = 0; + + varname = ast_var_full_name(current); + if (!varname) + continue; + + if (varname[0] == '_') { + vartype = 1; + if (varname[1] == '_') + vartype = 2; + } + + switch (vartype) { + case 1: + newvar = ast_var_assign(&varname[1], ast_var_value(current)); + if (newvar) { + AST_LIST_INSERT_TAIL(&child->varshead, newvar, entries); + if (option_debug) + ast_log(LOG_DEBUG, "Copying soft-transferable variable %s.\n", ast_var_name(newvar)); + } + break; + case 2: + newvar = ast_var_assign(ast_var_full_name(current), ast_var_value(current)); + if (newvar) { + AST_LIST_INSERT_TAIL(&child->varshead, newvar, entries); + if (option_debug) + ast_log(LOG_DEBUG, "Copying hard-transferable variable %s.\n", ast_var_name(newvar)); + } + break; + default: + if (option_debug) + ast_log(LOG_DEBUG, "Not copying variable %s.\n", ast_var_name(current)); + break; + } + } +} + +/*! + \brief Clone channel variables from 'clone' channel into 'original' channel + + All variables except those related to app_groupcount are cloned. + Variables are actually _removed_ from 'clone' channel, presumably + because it will subsequently be destroyed. + + \note Assumes locks will be in place on both channels when called. +*/ +static void clone_variables(struct ast_channel *original, struct ast_channel *clone) +{ + struct ast_var_t *varptr; + + /* we need to remove all app_groupcount related variables from the original + channel before merging in the clone's variables; any groups assigned to the + original channel should be released, only those assigned to the clone + should remain + */ + + AST_LIST_TRAVERSE_SAFE_BEGIN(&original->varshead, varptr, entries) { + if (!strncmp(ast_var_name(varptr), GROUP_CATEGORY_PREFIX, strlen(GROUP_CATEGORY_PREFIX))) { + AST_LIST_REMOVE(&original->varshead, varptr, entries); + ast_var_delete(varptr); + } + } + AST_LIST_TRAVERSE_SAFE_END; + + /* Append variables from clone channel into original channel */ + /* XXX Is this always correct? We have to in order to keep MACROS working XXX */ + if (AST_LIST_FIRST(&clone->varshead)) + AST_LIST_INSERT_TAIL(&original->varshead, AST_LIST_FIRST(&clone->varshead), entries); +} + +/*! + \brief Masquerade a channel + + \note Assumes channel will be locked when called +*/ +int ast_do_masquerade(struct ast_channel *original) +{ + int x,i; + int res=0; + int origstate; + struct ast_frame *cur, *prev; + const struct ast_channel_tech *t; + void *t_pvt; + struct ast_callerid tmpcid; + struct ast_channel *clone = original->masq; + int rformat = original->readformat; + int wformat = original->writeformat; + char newn[100]; + char orig[100]; + char masqn[100]; + char zombn[100]; + + if (option_debug > 3) + ast_log(LOG_DEBUG, "Actually Masquerading %s(%d) into the structure of %s(%d)\n", + clone->name, clone->_state, original->name, original->_state); + + /* XXX This is a seriously wacked out operation. We're essentially putting the guts of + the clone channel into the original channel. Start by killing off the original + channel's backend. I'm not sure we're going to keep this function, because + while the features are nice, the cost is very high in terms of pure nastiness. XXX */ + + /* We need the clone's lock, too */ + ast_channel_lock(clone); + + if (option_debug > 1) + ast_log(LOG_DEBUG, "Got clone lock for masquerade on '%s' at %p\n", clone->name, &clone->lock); + + /* Having remembered the original read/write formats, we turn off any translation on either + one */ + free_translation(clone); + free_translation(original); + + + /* Unlink the masquerade */ + original->masq = NULL; + clone->masqr = NULL; + + /* Save the original name */ + ast_copy_string(orig, original->name, sizeof(orig)); + /* Save the new name */ + ast_copy_string(newn, clone->name, sizeof(newn)); + /* Create the masq name */ + snprintf(masqn, sizeof(masqn), "%s", newn); + + /* Copy the name from the clone channel */ + ast_string_field_set(original, name, newn); + + /* Mangle the name of the clone channel */ + ast_string_field_set(clone, name, masqn); + + /* Notify any managers of the change, first the masq then the other */ + manager_event(EVENT_FLAG_CALL, "Rename", "Oldname: %s\r\nNewname: %s\r\nUniqueid: %s\r\n", newn, masqn, clone->uniqueid); + manager_event(EVENT_FLAG_CALL, "Rename", "Oldname: %s\r\nNewname: %s\r\nUniqueid: %s\r\n", orig, newn, original->uniqueid); + + /* Swap the technologies */ + t = original->tech; + original->tech = clone->tech; + clone->tech = t; + + t_pvt = original->tech_pvt; + original->tech_pvt = clone->tech_pvt; + clone->tech_pvt = t_pvt; + + /* Swap the readq's */ + cur = original->readq; + original->readq = clone->readq; + clone->readq = cur; + + /* Swap the alertpipes */ + for (i = 0; i < 2; i++) { + x = original->alertpipe[i]; + original->alertpipe[i] = clone->alertpipe[i]; + clone->alertpipe[i] = x; + } + + /* Swap the raw formats */ + x = original->rawreadformat; + original->rawreadformat = clone->rawreadformat; + clone->rawreadformat = x; + x = original->rawwriteformat; + original->rawwriteformat = clone->rawwriteformat; + clone->rawwriteformat = x; + + /* Save any pending frames on both sides. Start by counting + * how many we're going to need... */ + prev = NULL; + x = 0; + for (cur = clone->readq; cur; cur = cur->next) { + x++; + prev = cur; + } + /* If we had any, prepend them to the ones already in the queue, and + * load up the alertpipe */ + if (prev) { + prev->next = original->readq; + original->readq = clone->readq; + clone->readq = NULL; + if (original->alertpipe[1] > -1) { + for (i = 0; i < x; i++) + write(original->alertpipe[1], &x, sizeof(x)); + } + } + clone->_softhangup = AST_SOFTHANGUP_DEV; + + + /* And of course, so does our current state. Note we need not + call ast_setstate since the event manager doesn't really consider + these separate. We do this early so that the clone has the proper + state of the original channel. */ + origstate = original->_state; + original->_state = clone->_state; + clone->_state = origstate; + + if (clone->tech->fixup){ + res = clone->tech->fixup(original, clone); + if (res) + ast_log(LOG_WARNING, "Fixup failed on channel %s, strange things may happen.\n", clone->name); + } + + /* Start by disconnecting the original's physical side */ + if (clone->tech->hangup) + res = clone->tech->hangup(clone); + if (res) { + ast_log(LOG_WARNING, "Hangup failed! Strange things may happen!\n"); + ast_channel_unlock(clone); + return -1; + } + + snprintf(zombn, sizeof(zombn), "%s", orig); + /* Mangle the name of the clone channel */ + ast_string_field_set(clone, name, zombn); + manager_event(EVENT_FLAG_CALL, "Rename", "Oldname: %s\r\nNewname: %s\r\nUniqueid: %s\r\n", masqn, zombn, clone->uniqueid); + + /* Update the type. */ + t_pvt = original->monitor; + original->monitor = clone->monitor; + clone->monitor = t_pvt; + + /* Keep the same language. */ + ast_string_field_set(original, language, clone->language); + /* Copy the FD's other than the generator fd */ + for (x = 0; x < AST_MAX_FDS; x++) { + if (x != AST_GENERATOR_FD) + original->fds[x] = clone->fds[x]; + } + + /* move any whisperer over */ + ast_channel_whisper_stop(original); + if (ast_test_flag(clone, AST_FLAG_WHISPER)) { + original->whisper = clone->whisper; + ast_set_flag(original, AST_FLAG_WHISPER); + clone->whisper = NULL; + ast_clear_flag(clone, AST_FLAG_WHISPER); + } + + /* Move data stores over */ + if (AST_LIST_FIRST(&clone->datastores)) + AST_LIST_INSERT_TAIL(&original->datastores, AST_LIST_FIRST(&clone->datastores), entry); + AST_LIST_HEAD_INIT_NOLOCK(&clone->datastores); + + clone_variables(original, clone); + AST_LIST_HEAD_INIT_NOLOCK(&clone->varshead); + /* Presense of ADSI capable CPE follows clone */ + original->adsicpe = clone->adsicpe; + /* Bridge remains the same */ + /* CDR fields remain the same */ + /* XXX What about blocking, softhangup, blocker, and lock and blockproc? XXX */ + /* Application and data remain the same */ + /* Clone exception becomes real one, as with fdno */ + ast_copy_flags(original, clone, AST_FLAG_EXCEPTION); + original->fdno = clone->fdno; + /* Schedule context remains the same */ + /* Stream stuff stays the same */ + /* Keep the original state. The fixup code will need to work with it most likely */ + + /* Just swap the whole structures, nevermind the allocations, they'll work themselves + out. */ + tmpcid = original->cid; + original->cid = clone->cid; + clone->cid = tmpcid; + + /* Restore original timing file descriptor */ + original->fds[AST_TIMING_FD] = original->timingfd; + + /* Our native formats are different now */ + original->nativeformats = clone->nativeformats; + + /* Context, extension, priority, app data, jump table, remain the same */ + /* pvt switches. pbx stays the same, as does next */ + + /* Set the write format */ + ast_set_write_format(original, wformat); + + /* Set the read format */ + ast_set_read_format(original, rformat); + + /* Copy the music class */ + ast_string_field_set(original, musicclass, clone->musicclass); + + if (option_debug) + ast_log(LOG_DEBUG, "Putting channel %s in %d/%d formats\n", original->name, wformat, rformat); + + /* Okay. Last thing is to let the channel driver know about all this mess, so he + can fix up everything as best as possible */ + if (original->tech->fixup) { + res = original->tech->fixup(clone, original); + if (res) { + ast_log(LOG_WARNING, "Channel for type '%s' could not fixup channel %s\n", + original->tech->type, original->name); + ast_channel_unlock(clone); + return -1; + } + } else + ast_log(LOG_WARNING, "Channel type '%s' does not have a fixup routine (for %s)! Bad things may happen.\n", + original->tech->type, original->name); + + /* Now, at this point, the "clone" channel is totally F'd up. We mark it as + a zombie so nothing tries to touch it. If it's already been marked as a + zombie, then free it now (since it already is considered invalid). */ + if (ast_test_flag(clone, AST_FLAG_ZOMBIE)) { + if (option_debug) + ast_log(LOG_DEBUG, "Destroying channel clone '%s'\n", clone->name); + ast_channel_unlock(clone); + manager_event(EVENT_FLAG_CALL, "Hangup", + "Channel: %s\r\n" + "Uniqueid: %s\r\n" + "Cause: %d\r\n" + "Cause-txt: %s\r\n", + clone->name, + clone->uniqueid, + clone->hangupcause, + ast_cause2str(clone->hangupcause) + ); + ast_channel_free(clone); + } else { + ast_log(LOG_DEBUG, "Released clone lock on '%s'\n", clone->name); + ast_set_flag(clone, AST_FLAG_ZOMBIE); + ast_queue_frame(clone, &ast_null_frame); + ast_channel_unlock(clone); + } + + /* Signal any blocker */ + if (ast_test_flag(original, AST_FLAG_BLOCKING)) + pthread_kill(original->blocker, SIGURG); + if (option_debug) + ast_log(LOG_DEBUG, "Done Masquerading %s (%d)\n", original->name, original->_state); + return 0; +} + +void ast_set_callerid(struct ast_channel *chan, const char *callerid, const char *calleridname, const char *ani) +{ + if (callerid) { + if (chan->cid.cid_num) + free(chan->cid.cid_num); + chan->cid.cid_num = ast_strdup(callerid); + } + if (calleridname) { + if (chan->cid.cid_name) + free(chan->cid.cid_name); + chan->cid.cid_name = ast_strdup(calleridname); + } + if (ani) { + if (chan->cid.cid_ani) + free(chan->cid.cid_ani); + chan->cid.cid_ani = ast_strdup(ani); + } + if (chan->cdr) + ast_cdr_setcid(chan->cdr, chan); + manager_event(EVENT_FLAG_CALL, "Newcallerid", + "Channel: %s\r\n" + "CallerID: %s\r\n" + "CallerIDName: %s\r\n" + "Uniqueid: %s\r\n" + "CID-CallingPres: %d (%s)\r\n", + chan->name, + S_OR(chan->cid.cid_num, ""), + S_OR(chan->cid.cid_name, ""), + chan->uniqueid, + chan->cid.cid_pres, + ast_describe_caller_presentation(chan->cid.cid_pres) + ); +} + +int ast_setstate(struct ast_channel *chan, enum ast_channel_state state) +{ + int oldstate = chan->_state; + + if (oldstate == state) + return 0; + + chan->_state = state; + ast_device_state_changed_literal(chan->name); + manager_event(EVENT_FLAG_CALL, + (oldstate == AST_STATE_DOWN) ? "Newchannel" : "Newstate", + "Channel: %s\r\n" + "State: %s\r\n" + "CallerID: %s\r\n" + "CallerIDName: %s\r\n" + "Uniqueid: %s\r\n", + chan->name, ast_state2str(chan->_state), + S_OR(chan->cid.cid_num, ""), + S_OR(chan->cid.cid_name, ""), + chan->uniqueid); + + return 0; +} + +/*! \brief Find bridged channel */ +struct ast_channel *ast_bridged_channel(struct ast_channel *chan) +{ + struct ast_channel *bridged; + bridged = chan->_bridge; + if (bridged && bridged->tech->bridged_channel) + bridged = bridged->tech->bridged_channel(chan, bridged); + return bridged; +} + +static void bridge_playfile(struct ast_channel *chan, struct ast_channel *peer, const char *sound, int remain) +{ + int min = 0, sec = 0, check; + + check = ast_autoservice_start(peer); + if (check) + return; + + if (remain > 0) { + if (remain / 60 > 1) { + min = remain / 60; + sec = remain % 60; + } else { + sec = remain; + } + } + + if (!strcmp(sound,"timeleft")) { /* Queue support */ + ast_stream_and_wait(chan, "vm-youhave", chan->language, ""); + if (min) { + ast_say_number(chan, min, AST_DIGIT_ANY, chan->language, NULL); + ast_stream_and_wait(chan, "queue-minutes", chan->language, ""); + } + if (sec) { + ast_say_number(chan, sec, AST_DIGIT_ANY, chan->language, NULL); + ast_stream_and_wait(chan, "queue-seconds", chan->language, ""); + } + } else { + ast_stream_and_wait(chan, sound, chan->language, ""); + } + + ast_autoservice_stop(peer); +} + +static enum ast_bridge_result ast_generic_bridge(struct ast_channel *c0, struct ast_channel *c1, + struct ast_bridge_config *config, struct ast_frame **fo, + struct ast_channel **rc, struct timeval bridge_end) +{ + /* Copy voice back and forth between the two channels. */ + struct ast_channel *cs[3]; + struct ast_frame *f; + enum ast_bridge_result res = AST_BRIDGE_COMPLETE; + int o0nativeformats; + int o1nativeformats; + int watch_c0_dtmf; + int watch_c1_dtmf; + void *pvt0, *pvt1; + /* Indicates whether a frame was queued into a jitterbuffer */ + int frame_put_in_jb = 0; + int jb_in_use; + int to; + + cs[0] = c0; + cs[1] = c1; + pvt0 = c0->tech_pvt; + pvt1 = c1->tech_pvt; + o0nativeformats = c0->nativeformats; + o1nativeformats = c1->nativeformats; + watch_c0_dtmf = config->flags & AST_BRIDGE_DTMF_CHANNEL_0; + watch_c1_dtmf = config->flags & AST_BRIDGE_DTMF_CHANNEL_1; + + /* Check the need of a jitterbuffer for each channel */ + jb_in_use = ast_jb_do_usecheck(c0, c1); + + for (;;) { + struct ast_channel *who, *other; + + if ((c0->tech_pvt != pvt0) || (c1->tech_pvt != pvt1) || + (o0nativeformats != c0->nativeformats) || + (o1nativeformats != c1->nativeformats)) { + /* Check for Masquerade, codec changes, etc */ + res = AST_BRIDGE_RETRY; + break; + } + if (bridge_end.tv_sec) { + to = ast_tvdiff_ms(bridge_end, ast_tvnow()); + if (to <= 0) { + res = AST_BRIDGE_RETRY; + break; + } + } else + to = -1; + /* Calculate the appropriate max sleep interval - in general, this is the time, + left to the closest jb delivery moment */ + if (jb_in_use) + to = ast_jb_get_when_to_wakeup(c0, c1, to); + who = ast_waitfor_n(cs, 2, &to); + if (!who) { + /* No frame received within the specified timeout - check if we have to deliver now */ + if (jb_in_use) + ast_jb_get_and_deliver(c0, c1); + if (c0->_softhangup == AST_SOFTHANGUP_UNBRIDGE || c1->_softhangup == AST_SOFTHANGUP_UNBRIDGE) { + if (c0->_softhangup == AST_SOFTHANGUP_UNBRIDGE) + c0->_softhangup = 0; + if (c1->_softhangup == AST_SOFTHANGUP_UNBRIDGE) + c1->_softhangup = 0; + c0->_bridge = c1; + c1->_bridge = c0; + } + continue; + } + f = ast_read(who); + if (!f) { + *fo = NULL; + *rc = who; + ast_log(LOG_DEBUG, "Didn't get a frame from channel: %s\n",who->name); + break; + } + + other = (who == c0) ? c1 : c0; /* the 'other' channel */ + /* Try add the frame info the who's bridged channel jitterbuff */ + if (jb_in_use) + frame_put_in_jb = !ast_jb_put(other, f); + + if ((f->frametype == AST_FRAME_CONTROL) && !(config->flags & AST_BRIDGE_IGNORE_SIGS)) { + int bridge_exit = 0; + + switch (f->subclass) { + case AST_CONTROL_HOLD: + case AST_CONTROL_UNHOLD: + case AST_CONTROL_VIDUPDATE: + ast_indicate_data(other, f->subclass, f->data, f->datalen); + break; + default: + *fo = f; + *rc = who; + bridge_exit = 1; + ast_log(LOG_DEBUG, "Got a FRAME_CONTROL (%d) frame on channel %s\n", f->subclass, who->name); + break; + } + if (bridge_exit) + break; + } + if ((f->frametype == AST_FRAME_VOICE) || + (f->frametype == AST_FRAME_DTMF) || + (f->frametype == AST_FRAME_VIDEO) || + (f->frametype == AST_FRAME_IMAGE) || + (f->frametype == AST_FRAME_HTML) || + (f->frametype == AST_FRAME_MODEM) || + (f->frametype == AST_FRAME_TEXT)) { + /* monitored dtmf causes exit from bridge */ + int monitored_source = (who == c0) ? watch_c0_dtmf : watch_c1_dtmf; + + if (f->frametype == AST_FRAME_DTMF && monitored_source) { + *fo = f; + *rc = who; + ast_log(LOG_DEBUG, "Got DTMF on channel (%s)\n", who->name); + break; + } + /* Write immediately frames, not passed through jb */ + if (!frame_put_in_jb) + ast_write(other, f); + + /* Check if we have to deliver now */ + if (jb_in_use) + ast_jb_get_and_deliver(c0, c1); + } + /* XXX do we want to pass on also frames not matched above ? */ + ast_frfree(f); + + /* Swap who gets priority */ + cs[2] = cs[0]; + cs[0] = cs[1]; + cs[1] = cs[2]; + } + return res; +} + +/*! \brief Bridge two channels together */ +enum ast_bridge_result ast_channel_bridge(struct ast_channel *c0, struct ast_channel *c1, + struct ast_bridge_config *config, struct ast_frame **fo, struct ast_channel **rc) +{ + struct ast_channel *who = NULL; + enum ast_bridge_result res = AST_BRIDGE_COMPLETE; + int nativefailed=0; + int firstpass; + int o0nativeformats; + int o1nativeformats; + long time_left_ms=0; + struct timeval nexteventts = { 0, }; + char caller_warning = 0; + char callee_warning = 0; + + if (c0->_bridge) { + ast_log(LOG_WARNING, "%s is already in a bridge with %s\n", + c0->name, c0->_bridge->name); + return -1; + } + if (c1->_bridge) { + ast_log(LOG_WARNING, "%s is already in a bridge with %s\n", + c1->name, c1->_bridge->name); + return -1; + } + + /* Stop if we're a zombie or need a soft hangup */ + if (ast_test_flag(c0, AST_FLAG_ZOMBIE) || ast_check_hangup_locked(c0) || + ast_test_flag(c1, AST_FLAG_ZOMBIE) || ast_check_hangup_locked(c1)) + return -1; + + *fo = NULL; + firstpass = config->firstpass; + config->firstpass = 0; + + if (ast_tvzero(config->start_time)) + config->start_time = ast_tvnow(); + time_left_ms = config->timelimit; + + caller_warning = ast_test_flag(&config->features_caller, AST_FEATURE_PLAY_WARNING); + callee_warning = ast_test_flag(&config->features_callee, AST_FEATURE_PLAY_WARNING); + + if (config->start_sound && firstpass) { + if (caller_warning) + bridge_playfile(c0, c1, config->start_sound, time_left_ms / 1000); + if (callee_warning) + bridge_playfile(c1, c0, config->start_sound, time_left_ms / 1000); + } + + /* Keep track of bridge */ + c0->_bridge = c1; + c1->_bridge = c0; + + /* \todo XXX here should check that cid_num is not NULL */ + manager_event(EVENT_FLAG_CALL, "Link", + "Channel1: %s\r\n" + "Channel2: %s\r\n" + "Uniqueid1: %s\r\n" + "Uniqueid2: %s\r\n" + "CallerID1: %s\r\n" + "CallerID2: %s\r\n", + c0->name, c1->name, c0->uniqueid, c1->uniqueid, c0->cid.cid_num, c1->cid.cid_num); + + o0nativeformats = c0->nativeformats; + o1nativeformats = c1->nativeformats; + + if (config->feature_timer) { + nexteventts = ast_tvadd(config->start_time, ast_samp2tv(config->feature_timer, 1000)); + } else if (config->timelimit) { + nexteventts = ast_tvadd(config->start_time, ast_samp2tv(config->timelimit, 1000)); + if (caller_warning || callee_warning) + nexteventts = ast_tvsub(nexteventts, ast_samp2tv(config->play_warning, 1000)); + } + + for (/* ever */;;) { + struct timeval now = { 0, }; + int to; + + to = -1; + + if (!ast_tvzero(nexteventts)) { + now = ast_tvnow(); + to = ast_tvdiff_ms(nexteventts, now); + if (to < 0) + to = 0; + } + + if (config->timelimit) { + time_left_ms = config->timelimit - ast_tvdiff_ms(now, config->start_time); + if (time_left_ms < to) + to = time_left_ms; + + if (time_left_ms <= 0) { + if (caller_warning && config->end_sound) + bridge_playfile(c0, c1, config->end_sound, 0); + if (callee_warning && config->end_sound) + bridge_playfile(c1, c0, config->end_sound, 0); + *fo = NULL; + if (who) + *rc = who; + res = 0; + break; + } + + if (!to) { + if (time_left_ms >= 5000 && config->warning_sound && config->play_warning) { + int t = (time_left_ms + 500) / 1000; /* round to nearest second */ + if (caller_warning) + bridge_playfile(c0, c1, config->warning_sound, t); + if (callee_warning) + bridge_playfile(c1, c0, config->warning_sound, t); + } + if (config->warning_freq) { + nexteventts = ast_tvadd(nexteventts, ast_samp2tv(config->warning_freq, 1000)); + } else + nexteventts = ast_tvadd(config->start_time, ast_samp2tv(config->timelimit, 1000)); + } + } + + if (c0->_softhangup == AST_SOFTHANGUP_UNBRIDGE || c1->_softhangup == AST_SOFTHANGUP_UNBRIDGE) { + if (c0->_softhangup == AST_SOFTHANGUP_UNBRIDGE) + c0->_softhangup = 0; + if (c1->_softhangup == AST_SOFTHANGUP_UNBRIDGE) + c1->_softhangup = 0; + c0->_bridge = c1; + c1->_bridge = c0; + ast_log(LOG_DEBUG, "Unbridge signal received. Ending native bridge.\n"); + continue; + } + + /* Stop if we're a zombie or need a soft hangup */ + if (ast_test_flag(c0, AST_FLAG_ZOMBIE) || ast_check_hangup_locked(c0) || + ast_test_flag(c1, AST_FLAG_ZOMBIE) || ast_check_hangup_locked(c1)) { + *fo = NULL; + if (who) + *rc = who; + res = 0; + ast_log(LOG_DEBUG, "Bridge stops because we're zombie or need a soft hangup: c0=%s, c1=%s, flags: %s,%s,%s,%s\n", + c0->name, c1->name, + ast_test_flag(c0, AST_FLAG_ZOMBIE) ? "Yes" : "No", + ast_check_hangup(c0) ? "Yes" : "No", + ast_test_flag(c1, AST_FLAG_ZOMBIE) ? "Yes" : "No", + ast_check_hangup(c1) ? "Yes" : "No"); + break; + } + + if (c0->tech->bridge && + (config->timelimit == 0) && + (c0->tech->bridge == c1->tech->bridge) && + !nativefailed && !c0->monitor && !c1->monitor && + !c0->spies && !c1->spies && !ast_test_flag(&(config->features_callee),AST_FEATURE_REDIRECT) && + !ast_test_flag(&(config->features_caller),AST_FEATURE_REDIRECT) ) { + /* Looks like they share a bridge method and nothing else is in the way */ + ast_set_flag(c0, AST_FLAG_NBRIDGE); + ast_set_flag(c1, AST_FLAG_NBRIDGE); + if ((res = c0->tech->bridge(c0, c1, config->flags, fo, rc, to)) == AST_BRIDGE_COMPLETE) { + /* \todo XXX here should check that cid_num is not NULL */ + manager_event(EVENT_FLAG_CALL, "Unlink", + "Channel1: %s\r\n" + "Channel2: %s\r\n" + "Uniqueid1: %s\r\n" + "Uniqueid2: %s\r\n" + "CallerID1: %s\r\n" + "CallerID2: %s\r\n", + c0->name, c1->name, c0->uniqueid, c1->uniqueid, c0->cid.cid_num, c1->cid.cid_num); + ast_log(LOG_DEBUG, "Returning from native bridge, channels: %s, %s\n", c0->name, c1->name); + + ast_clear_flag(c0, AST_FLAG_NBRIDGE); + ast_clear_flag(c1, AST_FLAG_NBRIDGE); + + if (c0->_softhangup == AST_SOFTHANGUP_UNBRIDGE || c1->_softhangup == AST_SOFTHANGUP_UNBRIDGE) + continue; + + c0->_bridge = NULL; + c1->_bridge = NULL; + + return res; + } else { + ast_clear_flag(c0, AST_FLAG_NBRIDGE); + ast_clear_flag(c1, AST_FLAG_NBRIDGE); + } + switch (res) { + case AST_BRIDGE_RETRY: + continue; + default: + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Native bridging %s and %s ended\n", + c0->name, c1->name); + /* fallthrough */ + case AST_BRIDGE_FAILED_NOWARN: + nativefailed++; + break; + } + } + + if (((c0->writeformat != c1->readformat) || (c0->readformat != c1->writeformat) || + (c0->nativeformats != o0nativeformats) || (c1->nativeformats != o1nativeformats)) && + !(c0->generator || c1->generator)) { + if (ast_channel_make_compatible(c0, c1)) { + ast_log(LOG_WARNING, "Can't make %s and %s compatible\n", c0->name, c1->name); + /* \todo XXX here should check that cid_num is not NULL */ + manager_event(EVENT_FLAG_CALL, "Unlink", + "Channel1: %s\r\n" + "Channel2: %s\r\n" + "Uniqueid1: %s\r\n" + "Uniqueid2: %s\r\n" + "CallerID1: %s\r\n" + "CallerID2: %s\r\n", + c0->name, c1->name, c0->uniqueid, c1->uniqueid, c0->cid.cid_num, c1->cid.cid_num); + return AST_BRIDGE_FAILED; + } + o0nativeformats = c0->nativeformats; + o1nativeformats = c1->nativeformats; + } + res = ast_generic_bridge(c0, c1, config, fo, rc, nexteventts); + if (res != AST_BRIDGE_RETRY) + break; + } + + c0->_bridge = NULL; + c1->_bridge = NULL; + + /* \todo XXX here should check that cid_num is not NULL */ + manager_event(EVENT_FLAG_CALL, "Unlink", + "Channel1: %s\r\n" + "Channel2: %s\r\n" + "Uniqueid1: %s\r\n" + "Uniqueid2: %s\r\n" + "CallerID1: %s\r\n" + "CallerID2: %s\r\n", + c0->name, c1->name, c0->uniqueid, c1->uniqueid, c0->cid.cid_num, c1->cid.cid_num); + ast_log(LOG_DEBUG, "Bridge stops bridging channels %s and %s\n", c0->name, c1->name); + + return res; +} + +/*! \brief Sets an option on a channel */ +int ast_channel_setoption(struct ast_channel *chan, int option, void *data, int datalen, int block) +{ + int res; + + if (chan->tech->setoption) { + res = chan->tech->setoption(chan, option, data, datalen); + if (res < 0) + return res; + } else { + errno = ENOSYS; + return -1; + } + if (block) { + /* XXX Implement blocking -- just wait for our option frame reply, discarding + intermediate packets. XXX */ + ast_log(LOG_ERROR, "XXX Blocking not implemented yet XXX\n"); + return -1; + } + return 0; +} + +struct tonepair_def { + int freq1; + int freq2; + int duration; + int vol; +}; + +struct tonepair_state { + int fac1; + int fac2; + int v1_1; + int v2_1; + int v3_1; + int v1_2; + int v2_2; + int v3_2; + int origwfmt; + int pos; + int duration; + int modulate; + struct ast_frame f; + unsigned char offset[AST_FRIENDLY_OFFSET]; + short data[4000]; +}; + +static void tonepair_release(struct ast_channel *chan, void *params) +{ + struct tonepair_state *ts = params; + + if (chan) + ast_set_write_format(chan, ts->origwfmt); + free(ts); +} + +static void *tonepair_alloc(struct ast_channel *chan, void *params) +{ + struct tonepair_state *ts; + struct tonepair_def *td = params; + + if (!(ts = ast_calloc(1, sizeof(*ts)))) + return NULL; + ts->origwfmt = chan->writeformat; + if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) { + ast_log(LOG_WARNING, "Unable to set '%s' to signed linear format (write)\n", chan->name); + tonepair_release(NULL, ts); + ts = NULL; + } else { + ts->fac1 = 2.0 * cos(2.0 * M_PI * (td->freq1 / 8000.0)) * 32768.0; + ts->v1_1 = 0; + ts->v2_1 = sin(-4.0 * M_PI * (td->freq1 / 8000.0)) * td->vol; + ts->v3_1 = sin(-2.0 * M_PI * (td->freq1 / 8000.0)) * td->vol; + ts->v2_1 = 0; + ts->fac2 = 2.0 * cos(2.0 * M_PI * (td->freq2 / 8000.0)) * 32768.0; + ts->v2_2 = sin(-4.0 * M_PI * (td->freq2 / 8000.0)) * td->vol; + ts->v3_2 = sin(-2.0 * M_PI * (td->freq2 / 8000.0)) * td->vol; + ts->duration = td->duration; + ts->modulate = 0; + } + /* Let interrupts interrupt :) */ + ast_set_flag(chan, AST_FLAG_WRITE_INT); + return ts; +} + +static int tonepair_generator(struct ast_channel *chan, void *data, int len, int samples) +{ + struct tonepair_state *ts = data; + int x; + + /* we need to prepare a frame with 16 * timelen samples as we're + * generating SLIN audio + */ + len = samples * 2; + + if (len > sizeof(ts->data) / 2 - 1) { + ast_log(LOG_WARNING, "Can't generate that much data!\n"); + return -1; + } + memset(&ts->f, 0, sizeof(ts->f)); + for (x=0;xv1_1 = ts->v2_1; + ts->v2_1 = ts->v3_1; + ts->v3_1 = (ts->fac1 * ts->v2_1 >> 15) - ts->v1_1; + + ts->v1_2 = ts->v2_2; + ts->v2_2 = ts->v3_2; + ts->v3_2 = (ts->fac2 * ts->v2_2 >> 15) - ts->v1_2; + if (ts->modulate) { + int p; + p = ts->v3_2 - 32768; + if (p < 0) p = -p; + p = ((p * 9) / 10) + 1; + ts->data[x] = (ts->v3_1 * p) >> 15; + } else + ts->data[x] = ts->v3_1 + ts->v3_2; + } + ts->f.frametype = AST_FRAME_VOICE; + ts->f.subclass = AST_FORMAT_SLINEAR; + ts->f.datalen = len; + ts->f.samples = samples; + ts->f.offset = AST_FRIENDLY_OFFSET; + ts->f.data = ts->data; + ast_write(chan, &ts->f); + ts->pos += x; + if (ts->duration > 0) { + if (ts->pos >= ts->duration * 8) + return -1; + } + return 0; +} + +static struct ast_generator tonepair = { + alloc: tonepair_alloc, + release: tonepair_release, + generate: tonepair_generator, +}; + +int ast_tonepair_start(struct ast_channel *chan, int freq1, int freq2, int duration, int vol) +{ + struct tonepair_def d = { 0, }; + + d.freq1 = freq1; + d.freq2 = freq2; + d.duration = duration; + d.vol = (vol < 1) ? 8192 : vol; /* force invalid to 8192 */ + if (ast_activate_generator(chan, &tonepair, &d)) + return -1; + return 0; +} + +void ast_tonepair_stop(struct ast_channel *chan) +{ + ast_deactivate_generator(chan); +} + +int ast_tonepair(struct ast_channel *chan, int freq1, int freq2, int duration, int vol) +{ + int res; + + if ((res = ast_tonepair_start(chan, freq1, freq2, duration, vol))) + return res; + + /* Give us some wiggle room */ + while (chan->generatordata && ast_waitfor(chan, 100) >= 0) { + struct ast_frame *f = ast_read(chan); + if (f) + ast_frfree(f); + else + return -1; + } + return 0; +} + +ast_group_t ast_get_group(char *s) +{ + char *piece; + char *c; + int start=0, finish=0, x; + ast_group_t group = 0; + + c = ast_strdupa(s); + + while ((piece = strsep(&c, ","))) { + if (sscanf(piece, "%d-%d", &start, &finish) == 2) { + /* Range */ + } else if (sscanf(piece, "%d", &start)) { + /* Just one */ + finish = start; + } else { + ast_log(LOG_ERROR, "Syntax error parsing group configuration '%s' at '%s'. Ignoring.\n", s, piece); + continue; + } + for (x = start; x <= finish; x++) { + if ((x > 63) || (x < 0)) { + ast_log(LOG_WARNING, "Ignoring invalid group %d (maximum group is 63)\n", x); + } else + group |= ((ast_group_t) 1 << x); + } + } + return group; +} + +static int (*ast_moh_start_ptr)(struct ast_channel *, const char *, const char *) = NULL; +static void (*ast_moh_stop_ptr)(struct ast_channel *) = NULL; +static void (*ast_moh_cleanup_ptr)(struct ast_channel *) = NULL; + +void ast_install_music_functions(int (*start_ptr)(struct ast_channel *, const char *, const char *), + void (*stop_ptr)(struct ast_channel *), + void (*cleanup_ptr)(struct ast_channel *)) +{ + ast_moh_start_ptr = start_ptr; + ast_moh_stop_ptr = stop_ptr; + ast_moh_cleanup_ptr = cleanup_ptr; +} + +void ast_uninstall_music_functions(void) +{ + ast_moh_start_ptr = NULL; + ast_moh_stop_ptr = NULL; + ast_moh_cleanup_ptr = NULL; +} + +/*! \brief Turn on music on hold on a given channel */ +int ast_moh_start(struct ast_channel *chan, const char *mclass, const char *interpclass) +{ + if (ast_moh_start_ptr) + return ast_moh_start_ptr(chan, mclass, interpclass); + + if (option_verbose > 2) { + ast_verbose(VERBOSE_PREFIX_3 "Music class %s requested but no musiconhold loaded.\n", + mclass ? mclass : (interpclass ? interpclass : "default")); + } + + return 0; +} + +/*! \brief Turn off music on hold on a given channel */ +void ast_moh_stop(struct ast_channel *chan) +{ + if (ast_moh_stop_ptr) + ast_moh_stop_ptr(chan); +} + +void ast_moh_cleanup(struct ast_channel *chan) +{ + if (ast_moh_cleanup_ptr) + ast_moh_cleanup_ptr(chan); +} + +void ast_channels_init(void) +{ + ast_cli_register(&cli_show_channeltypes); + ast_cli_register(&cli_show_channeltype); +} + +/*! \brief Print call group and pickup group ---*/ +char *ast_print_group(char *buf, int buflen, ast_group_t group) +{ + unsigned int i; + int first=1; + char num[3]; + + buf[0] = '\0'; + + if (!group) /* Return empty string if no group */ + return buf; + + for (i = 0; i <= 63; i++) { /* Max group is 63 */ + if (group & ((ast_group_t) 1 << i)) { + if (!first) { + strncat(buf, ", ", buflen); + } else { + first=0; + } + snprintf(num, sizeof(num), "%u", i); + strncat(buf, num, buflen); + } + } + return buf; +} + +void ast_set_variables(struct ast_channel *chan, struct ast_variable *vars) +{ + struct ast_variable *cur; + + for (cur = vars; cur; cur = cur->next) + pbx_builtin_setvar_helper(chan, cur->name, cur->value); +} + +static void copy_data_from_queue(struct ast_channel_spy_queue *queue, short *buf, unsigned int samples) +{ + struct ast_frame *f; + int tocopy; + int bytestocopy; + + while (samples) { + f = queue->head; + + if (!f) { + ast_log(LOG_ERROR, "Ran out of frames before buffer filled!\n"); + break; + } + + tocopy = (f->samples > samples) ? samples : f->samples; + bytestocopy = ast_codec_get_len(queue->format, tocopy); + memcpy(buf, f->data, bytestocopy); + samples -= tocopy; + buf += tocopy; + f->samples -= tocopy; + f->data += bytestocopy; + f->datalen -= bytestocopy; + f->offset += bytestocopy; + queue->samples -= tocopy; + if (!f->samples) { + queue->head = f->next; + ast_frfree(f); + } + } +} + +struct ast_frame *ast_channel_spy_read_frame(struct ast_channel_spy *spy, unsigned int samples) +{ + struct ast_frame *result; + /* buffers are allocated to hold SLINEAR, which is the largest format */ + short read_buf[samples]; + short write_buf[samples]; + struct ast_frame *read_frame; + struct ast_frame *write_frame; + int need_dup; + struct ast_frame stack_read_frame = { .frametype = AST_FRAME_VOICE, + .subclass = spy->read_queue.format, + .data = read_buf, + .samples = samples, + .datalen = ast_codec_get_len(spy->read_queue.format, samples), + }; + struct ast_frame stack_write_frame = { .frametype = AST_FRAME_VOICE, + .subclass = spy->write_queue.format, + .data = write_buf, + .samples = samples, + .datalen = ast_codec_get_len(spy->write_queue.format, samples), + }; + + /* if a flush has been requested, dump everything in whichever queue is larger */ + if (ast_test_flag(spy, CHANSPY_TRIGGER_FLUSH)) { + if (spy->read_queue.samples > spy->write_queue.samples) { + if (ast_test_flag(spy, CHANSPY_READ_VOLADJUST)) { + for (result = spy->read_queue.head; result; result = result->next) + ast_frame_adjust_volume(result, spy->read_vol_adjustment); + } + result = spy->read_queue.head; + spy->read_queue.head = NULL; + spy->read_queue.samples = 0; + } else { + if (ast_test_flag(spy, CHANSPY_WRITE_VOLADJUST)) { + for (result = spy->write_queue.head; result; result = result->next) + ast_frame_adjust_volume(result, spy->write_vol_adjustment); + } + result = spy->write_queue.head; + spy->write_queue.head = NULL; + spy->write_queue.samples = 0; + } + ast_clear_flag(spy, CHANSPY_TRIGGER_FLUSH); + return result; + } + + if ((spy->read_queue.samples < samples) || (spy->write_queue.samples < samples)) + return NULL; + + /* short-circuit if both head frames have exactly what we want */ + if ((spy->read_queue.head->samples == samples) && + (spy->write_queue.head->samples == samples)) { + read_frame = spy->read_queue.head; + spy->read_queue.head = read_frame->next; + read_frame->next = NULL; + + write_frame = spy->write_queue.head; + spy->write_queue.head = write_frame->next; + write_frame->next = NULL; + + spy->read_queue.samples -= samples; + spy->write_queue.samples -= samples; + + need_dup = 0; + } else { + copy_data_from_queue(&spy->read_queue, read_buf, samples); + copy_data_from_queue(&spy->write_queue, write_buf, samples); + + read_frame = &stack_read_frame; + write_frame = &stack_write_frame; + need_dup = 1; + } + + if (ast_test_flag(spy, CHANSPY_READ_VOLADJUST)) + ast_frame_adjust_volume(read_frame, spy->read_vol_adjustment); + + if (ast_test_flag(spy, CHANSPY_WRITE_VOLADJUST)) + ast_frame_adjust_volume(write_frame, spy->write_vol_adjustment); + + if (ast_test_flag(spy, CHANSPY_MIXAUDIO)) { + ast_frame_slinear_sum(read_frame, write_frame); + + if (need_dup) + result = ast_frdup(read_frame); + else { + result = read_frame; + ast_frfree(write_frame); + } + } else { + if (need_dup) { + result = ast_frdup(read_frame); + result->next = ast_frdup(write_frame); + } else { + result = read_frame; + result->next = write_frame; + } + } + + return result; +} + +static void *silence_generator_alloc(struct ast_channel *chan, void *data) +{ + /* just store the data pointer in the channel structure */ + return data; +} + +static void silence_generator_release(struct ast_channel *chan, void *data) +{ + /* nothing to do */ +} + +static int silence_generator_generate(struct ast_channel *chan, void *data, int len, int samples) +{ + short buf[samples]; + struct ast_frame frame = { + .frametype = AST_FRAME_VOICE, + .subclass = AST_FORMAT_SLINEAR, + .data = buf, + .samples = samples, + .datalen = sizeof(buf), + }; + memset(buf, 0, sizeof(buf)); + if (ast_write(chan, &frame)) + return -1; + return 0; +} + +static struct ast_generator silence_generator = { + .alloc = silence_generator_alloc, + .release = silence_generator_release, + .generate = silence_generator_generate, +}; + +struct ast_silence_generator { + int old_write_format; +}; + +struct ast_silence_generator *ast_channel_start_silence_generator(struct ast_channel *chan) +{ + struct ast_silence_generator *state; + + if (!(state = ast_calloc(1, sizeof(*state)))) { + return NULL; + } + + state->old_write_format = chan->writeformat; + + if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) { + ast_log(LOG_ERROR, "Could not set write format to SLINEAR\n"); + free(state); + return NULL; + } + + ast_activate_generator(chan, &silence_generator, state); + + if (option_debug) + ast_log(LOG_DEBUG, "Started silence generator on '%s'\n", chan->name); + + return state; +} + +void ast_channel_stop_silence_generator(struct ast_channel *chan, struct ast_silence_generator *state) +{ + if (!state) + return; + + ast_deactivate_generator(chan); + + if (option_debug) + ast_log(LOG_DEBUG, "Stopped silence generator on '%s'\n", chan->name); + + if (ast_set_write_format(chan, state->old_write_format) < 0) + ast_log(LOG_ERROR, "Could not return write format to its original state\n"); + + free(state); +} + + +/*! \ brief Convert channel reloadreason (ENUM) to text string for manager event */ +const char *channelreloadreason2txt(enum channelreloadreason reason) +{ + switch (reason) { + case CHANNEL_MODULE_LOAD: + return "LOAD (Channel module load)"; + + case CHANNEL_MODULE_RELOAD: + return "RELOAD (Channel module reload)"; + + case CHANNEL_CLI_RELOAD: + return "CLIRELOAD (Channel module reload by CLI command)"; + + default: + return "MANAGERRELOAD (Channel module reload by manager)"; + } +}; + +#ifdef DEBUG_CHANNEL_LOCKS + +/*! \brief Unlock AST channel (and print debugging output) +\note You need to enable DEBUG_CHANNEL_LOCKS for this function +*/ +int ast_channel_unlock(struct ast_channel *chan) +{ + int res = 0; + if (option_debug > 2) + ast_log(LOG_DEBUG, "::::==== Unlocking AST channel %s\n", chan->name); + + if (!chan) { + ast_log(LOG_DEBUG, "::::==== Unlocking non-existing channel \n"); + return 0; + } + + res = ast_mutex_unlock(&chan->lock); + + if (option_debug > 2) { + /* Try to find counter if possible on your platform + I've only found out how to do this on Linux + DEBUG_THREADS changes the lock structure + */ +#ifdef __linux__ + int count = 0; +#ifdef DEBUG_THREADS + if ((count = chan->lock.mutex.__m_count)) +#else + if ((count = chan->lock.__m_count)) +#endif + ast_log(LOG_DEBUG, ":::=== Still have %d locks (recursive)\n", count); +#endif + if (!res) + ast_log(LOG_DEBUG, "::::==== Channel %s was unlocked\n", chan->name); + if (res == EINVAL) { + ast_log(LOG_DEBUG, "::::==== Channel %s had no lock by this thread. Failed unlocking\n", chan->name); + } + } + if (res == EPERM) { + /* We had no lock, so okay any way*/ + if (option_debug > 3) + ast_log(LOG_DEBUG, "::::==== Channel %s was not locked at all \n", chan->name); + res = 0; + } + return res; +} + +/*! \brief Lock AST channel (and print debugging output) +\note You need to enable DEBUG_CHANNEL_LOCKS for this function */ +int ast_channel_lock(struct ast_channel *chan) +{ + int res; + + if (option_debug > 3) + ast_log(LOG_DEBUG, "====:::: Locking AST channel %s\n", chan->name); + + res = ast_mutex_lock(&chan->lock); + + if (option_debug > 3) { +#ifdef __linux__ + int count = 0; +#ifdef DEBUG_THREADS + if ((count = chan->lock.mutex.__m_count)) +#else + if ((count = chan->lock.__m_count)) +#endif + ast_log(LOG_DEBUG, ":::=== Now have %d locks (recursive)\n", count); +#endif + if (!res) + ast_log(LOG_DEBUG, "::::==== Channel %s was locked\n", chan->name); + if (res == EDEADLK) { + /* We had no lock, so okey any way */ + if (option_debug > 3) + ast_log(LOG_DEBUG, "::::==== Channel %s was not locked by us. Lock would cause deadlock.\n", chan->name); + } + if (res == EINVAL) { + if (option_debug > 3) + ast_log(LOG_DEBUG, "::::==== Channel %s lock failed. No mutex.\n", chan->name); + } + } + return res; +} + +/*! \brief Lock AST channel (and print debugging output) +\note You need to enable DEBUG_CHANNEL_LOCKS for this function */ +int ast_channel_trylock(struct ast_channel *chan) +{ + int res; + + if (option_debug > 2) + ast_log(LOG_DEBUG, "====:::: Trying to lock AST channel %s\n", chan->name); + + res = ast_mutex_trylock(&chan->lock); + + if (option_debug > 2) { +#ifdef __linux__ + int count = 0; +#ifdef DEBUG_THREADS + if ((count = chan->lock.mutex.__m_count)) +#else + if ((count = chan->lock.__m_count)) +#endif + ast_log(LOG_DEBUG, ":::=== Now have %d locks (recursive)\n", count); +#endif + if (!res) + ast_log(LOG_DEBUG, "::::==== Channel %s was locked\n", chan->name); + if (res == EBUSY) { + /* We failed to lock */ + if (option_debug > 2) + ast_log(LOG_DEBUG, "::::==== Channel %s failed to lock. Not waiting around...\n", chan->name); + } + if (res == EDEADLK) { + /* We had no lock, so okey any way*/ + if (option_debug > 2) + ast_log(LOG_DEBUG, "::::==== Channel %s was not locked. Lock would cause deadlock.\n", chan->name); + } + if (res == EINVAL && option_debug > 2) + ast_log(LOG_DEBUG, "::::==== Channel %s lock failed. No mutex.\n", chan->name); + } + return res; +} + +#endif + +/* + * Wrappers for various ast_say_*() functions that call the full version + * of the same functions. + * The proper place would be say.c, but that file is optional and one + * must be able to build asterisk even without it (using a loadable 'say' + * implementation that only supplies the 'full' version of the functions. + */ + +int ast_say_number(struct ast_channel *chan, int num, + const char *ints, const char *language, const char *options) +{ + return ast_say_number_full(chan, num, ints, language, options, -1, -1); +} + +int ast_say_enumeration(struct ast_channel *chan, int num, + const char *ints, const char *language, const char *options) +{ + return ast_say_enumeration_full(chan, num, ints, language, options, -1, -1); +} + +int ast_say_digits(struct ast_channel *chan, int num, + const char *ints, const char *lang) +{ + return ast_say_digits_full(chan, num, ints, lang, -1, -1); +} + +int ast_say_digit_str(struct ast_channel *chan, const char *str, + const char *ints, const char *lang) +{ + return ast_say_digit_str_full(chan, str, ints, lang, -1, -1); +} + +int ast_say_character_str(struct ast_channel *chan, const char *str, + const char *ints, const char *lang) +{ + return ast_say_character_str_full(chan, str, ints, lang, -1, -1); +} + +int ast_say_phonetic_str(struct ast_channel *chan, const char *str, + const char *ints, const char *lang) +{ + return ast_say_phonetic_str_full(chan, str, ints, lang, -1, -1); +} + +int ast_say_digits_full(struct ast_channel *chan, int num, + const char *ints, const char *lang, int audiofd, int ctrlfd) +{ + char buf[256]; + + snprintf(buf, sizeof(buf), "%d", num); + return ast_say_digit_str_full(chan, buf, ints, lang, audiofd, ctrlfd); +} + +int ast_channel_whisper_start(struct ast_channel *chan) +{ + if (chan->whisper) + return -1; + + if (!(chan->whisper = ast_calloc(1, sizeof(*chan->whisper)))) + return -1; + + ast_mutex_init(&chan->whisper->lock); + ast_slinfactory_init(&chan->whisper->sf); + chan->whisper->original_format = ast_set_write_format(chan, AST_FORMAT_SLINEAR); + ast_set_flag(chan, AST_FLAG_WHISPER); + + return 0; +} + +int ast_channel_whisper_feed(struct ast_channel *chan, struct ast_frame *f) +{ + if (!chan->whisper) + return -1; + + ast_mutex_lock(&chan->whisper->lock); + ast_slinfactory_feed(&chan->whisper->sf, f); + ast_mutex_unlock(&chan->whisper->lock); + + return 0; +} + +void ast_channel_whisper_stop(struct ast_channel *chan) +{ + if (!chan->whisper) + return; + + ast_clear_flag(chan, AST_FLAG_WHISPER); + ast_set_write_format(chan, chan->whisper->original_format); + ast_slinfactory_destroy(&chan->whisper->sf); + ast_mutex_destroy(&chan->whisper->lock); + free(chan->whisper); + chan->whisper = NULL; +} diff --git a/main/chanvars.c b/main/chanvars.c new file mode 100644 index 000000000..e21b3fc57 --- /dev/null +++ b/main/chanvars.c @@ -0,0 +1,86 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Channel Variables + * + * \author Mark Spencer + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include + +#include "asterisk/chanvars.h" +#include "asterisk/logger.h" +#include "asterisk/strings.h" +#include "asterisk/utils.h" + +struct ast_var_t *ast_var_assign(const char *name, const char *value) +{ + struct ast_var_t *var; + int name_len = strlen(name) + 1; + int value_len = strlen(value) + 1; + + if (!(var = ast_calloc(sizeof(*var) + name_len + value_len, sizeof(char)))) { + return NULL; + } + + ast_copy_string(var->name, name, name_len); + var->value = var->name + name_len; + ast_copy_string(var->value, value, value_len); + + return var; +} + +void ast_var_delete(struct ast_var_t *var) +{ + if (var) + free(var); +} + +const char *ast_var_name(const struct ast_var_t *var) +{ + const char *name; + + if (var == NULL || (name = var->name) == NULL) + return NULL; + /* Return the name without the initial underscores */ + if (name[0] == '_') { + name++; + if (name[0] == '_') + name++; + } + return name; +} + +const char *ast_var_full_name(const struct ast_var_t *var) +{ + return (var ? var->name : NULL); +} + +const char *ast_var_value(const struct ast_var_t *var) +{ + return (var ? var->value : NULL); +} + + diff --git a/main/cli.c b/main/cli.c new file mode 100644 index 000000000..896ef2848 --- /dev/null +++ b/main/cli.c @@ -0,0 +1,1390 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Standard Command Line Interface + * + * \author Mark Spencer + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/logger.h" +#include "asterisk/options.h" +#include "asterisk/cli.h" +#include "asterisk/linkedlists.h" +#include "asterisk/module.h" +#include "asterisk/pbx.h" +#include "asterisk/channel.h" +#include "asterisk/utils.h" +#include "asterisk/app.h" +#include "asterisk/lock.h" +#include "editline/readline/readline.h" +#include "asterisk/threadstorage.h" + +extern unsigned long global_fin, global_fout; + +AST_THREADSTORAGE(ast_cli_buf, ast_cli_buf_init); + +/*! \brief Initial buffer size for resulting strings in ast_cli() */ +#define AST_CLI_INITLEN 256 + +void ast_cli(int fd, char *fmt, ...) +{ + int res; + struct ast_dynamic_str *buf; + va_list ap; + + if (!(buf = ast_dynamic_str_thread_get(&ast_cli_buf, AST_CLI_INITLEN))) + return; + + va_start(ap, fmt); + res = ast_dynamic_str_thread_set_va(&buf, 0, &ast_cli_buf, fmt, ap); + va_end(ap); + + if (res != AST_DYNSTR_BUILD_FAILED) + ast_carefulwrite(fd, buf->str, strlen(buf->str), 100); +} + +static AST_LIST_HEAD_STATIC(helpers, ast_cli_entry); + +static char load_help[] = +"Usage: load \n" +" Loads the specified module into Asterisk.\n"; + +static char unload_help[] = +"Usage: unload [-f|-h] \n" +" Unloads the specified module from Asterisk. The -f\n" +" option causes the module to be unloaded even if it is\n" +" in use (may cause a crash) and the -h module causes the\n" +" module to be unloaded even if the module says it cannot, \n" +" which almost always will cause a crash.\n"; + +static char help_help[] = +"Usage: help [topic]\n" +" When called with a topic as an argument, displays usage\n" +" information on the given command. If called without a\n" +" topic, it provides a list of commands.\n"; + +static char chanlist_help[] = +"Usage: show channels [concise|verbose]\n" +" Lists currently defined channels and some information about them. If\n" +" 'concise' is specified, the format is abridged and in a more easily\n" +" machine parsable format. If 'verbose' is specified, the output includes\n" +" more and longer fields.\n"; + +static char reload_help[] = +"Usage: reload [module ...]\n" +" Reloads configuration files for all listed modules which support\n" +" reloading, or for all supported modules if none are listed.\n"; + +static char set_verbose_help[] = +"Usage: set verbose \n" +" Sets level of verbose messages to be displayed. 0 means\n" +" no messages should be displayed. Equivalent to -v[v[v...]]\n" +" on startup\n"; + +static char set_debug_help[] = +"Usage: set debug \n" +" Sets level of core debug messages to be displayed. 0 means\n" +" no messages should be displayed. Equivalent to -d[d[d...]]\n" +" on startup.\n"; + +static char logger_mute_help[] = +"Usage: logger mute\n" +" Disables logging output to the current console, making it possible to\n" +" gather information without being disturbed by scrolling lines.\n"; + +static char softhangup_help[] = +"Usage: soft hangup \n" +" Request that a channel be hung up. The hangup takes effect\n" +" the next time the driver reads or writes from the channel\n"; + +static char group_show_channels_help[] = +"Usage: group show channels [pattern]\n" +" Lists all currently active channels with channel group(s) specified.\n" +" Optional regular expression pattern is matched to group names for each\n" +" channel.\n"; + +static int handle_load(int fd, int argc, char *argv[]) +{ + if (argc != 2) + return RESULT_SHOWUSAGE; + if (ast_load_resource(argv[1])) { + ast_cli(fd, "Unable to load module %s\n", argv[1]); + return RESULT_FAILURE; + } + return RESULT_SUCCESS; +} + +static int handle_reload(int fd, int argc, char *argv[]) +{ + int x; + int res; + if (argc < 1) + return RESULT_SHOWUSAGE; + if (argc > 1) { + for (x=1;x option_verbose) + option_verbose = val; + } else + return RESULT_SHOWUSAGE; + if (oldval != option_verbose && option_verbose > 0) + ast_cli(fd, "Verbosity was %d and is now %d\n", oldval, option_verbose); + else if (oldval > 0 && option_verbose > 0) + ast_cli(fd, "Verbosity is at least %d\n", option_verbose); + else if (oldval > 0 && option_verbose == 0) + ast_cli(fd, "Verbosity is now OFF\n"); + return RESULT_SUCCESS; +} + +static int handle_set_debug(int fd, int argc, char *argv[]) +{ + int val = 0; + int oldval = option_debug; + + /* "set debug [atleast] N" */ + if (argc == 3) + option_debug = atoi(argv[2]); + else if (argc == 4) { + if (strcasecmp(argv[2], "atleast")) + return RESULT_SHOWUSAGE; + val = atoi(argv[3]); + if (val > option_debug) + option_debug = val; + } else + return RESULT_SHOWUSAGE; + if (oldval != option_debug && option_debug > 0) + ast_cli(fd, "Core debug was %d and is now %d\n", oldval, option_debug); + else if (oldval > 0 && option_debug > 0) + ast_cli(fd, "Core debug is at least %d\n", option_debug); + else if (oldval > 0 && option_debug == 0) + ast_cli(fd, "Core debug is now OFF\n"); + return RESULT_SUCCESS; +} + +static int handle_logger_mute(int fd, int argc, char *argv[]) +{ + if (argc != 2) + return RESULT_SHOWUSAGE; + ast_console_toggle_mute(fd); + return RESULT_SUCCESS; +} + +static int handle_unload(int fd, int argc, char *argv[]) +{ + int x; + int force=AST_FORCE_SOFT; + if (argc < 2) + return RESULT_SHOWUSAGE; + for (x=1;x YEAR) { + x = (timeval / YEAR); + timeval -= (x * YEAR); + ast_build_string(&s, &maxbytes, "%d year%s%s ", x, ESS(x),NEEDCOMMA(timeval)); + } + if (timeval > WEEK) { + x = (timeval / WEEK); + timeval -= (x * WEEK); + ast_build_string(&s, &maxbytes, "%d week%s%s ", x, ESS(x),NEEDCOMMA(timeval)); + } + if (timeval > DAY) { + x = (timeval / DAY); + timeval -= (x * DAY); + ast_build_string(&s, &maxbytes, "%d day%s%s ", x, ESS(x),NEEDCOMMA(timeval)); + } + if (timeval > HOUR) { + x = (timeval / HOUR); + timeval -= (x * HOUR); + ast_build_string(&s, &maxbytes, "%d hour%s%s ", x, ESS(x),NEEDCOMMA(timeval)); + } + if (timeval > MINUTE) { + x = (timeval / MINUTE); + timeval -= (x * MINUTE); + ast_build_string(&s, &maxbytes, "%d minute%s%s ", x, ESS(x),NEEDCOMMA(timeval)); + } + x = timeval; + if (x > 0) + ast_build_string(&s, &maxbytes, "%d second%s ", x, ESS(x)); + if (timestr[0] != '\0') + ast_cli(fd, "%s: %s\n", prefix, timestr); +} + +static int handle_showuptime(int fd, int argc, char *argv[]) +{ + /* 'show uptime [seconds]' */ + time_t curtime = time(NULL); + int printsec = (argc == 3 && !strcasecmp(argv[2],"seconds")); + + if (argc != 2 && !printsec) + return RESULT_SHOWUSAGE; + if (ast_startuptime) + print_uptimestr(fd, curtime - ast_startuptime, "System uptime", printsec); + if (ast_lastreloadtime) + print_uptimestr(fd, curtime - ast_lastreloadtime, "Last reload", printsec); + return RESULT_SUCCESS; +} + +static int handle_modlist(int fd, int argc, char *argv[]) +{ + char *like = ""; + if (argc == 3) + return RESULT_SHOWUSAGE; + else if (argc >= 4) { + if (strcmp(argv[2],"like")) + return RESULT_SHOWUSAGE; + like = argv[3]; + } + + ast_mutex_lock(&climodentrylock); + climodentryfd = fd; /* global, protected by climodentrylock */ + ast_cli(fd, MODLIST_FORMAT2, "Module", "Description", "Use Count"); + ast_cli(fd,"%d modules loaded\n", ast_update_module_list(modlist_modentry, like)); + climodentryfd = -1; + ast_mutex_unlock(&climodentrylock); + return RESULT_SUCCESS; +} +#undef MODLIST_FORMAT +#undef MODLIST_FORMAT2 + +static int handle_chanlist(int fd, int argc, char *argv[]) +{ +#define FORMAT_STRING "%-20.20s %-20.20s %-7.7s %-30.30s\n" +#define FORMAT_STRING2 "%-20.20s %-20.20s %-7.7s %-30.30s\n" +#define CONCISE_FORMAT_STRING "%s!%s!%s!%d!%s!%s!%s!%s!%s!%d!%s!%s\n" +#define VERBOSE_FORMAT_STRING "%-20.20s %-20.20s %-16.16s %4d %-7.7s %-12.12s %-25.25s %-15.15s %8.8s %-11.11s %-20.20s\n" +#define VERBOSE_FORMAT_STRING2 "%-20.20s %-20.20s %-16.16s %-4.4s %-7.7s %-12.12s %-25.25s %-15.15s %8.8s %-11.11s %-20.20s\n" + + struct ast_channel *c = NULL; + char durbuf[10] = "-"; + char locbuf[40]; + char appdata[40]; + int duration; + int durh, durm, durs; + int numchans = 0, concise = 0, verbose = 0; + + concise = (argc == 3 && (!strcasecmp(argv[2],"concise"))); + verbose = (argc == 3 && (!strcasecmp(argv[2],"verbose"))); + + if (argc < 2 || argc > 3 || (argc == 3 && !concise && !verbose)) + return RESULT_SHOWUSAGE; + + if (!concise && !verbose) + ast_cli(fd, FORMAT_STRING2, "Channel", "Location", "State", "Application(Data)"); + else if (verbose) + ast_cli(fd, VERBOSE_FORMAT_STRING2, "Channel", "Context", "Extension", "Priority", "State", "Application", "Data", + "CallerID", "Duration", "Accountcode", "BridgedTo"); + + while ((c = ast_channel_walk_locked(c)) != NULL) { + struct ast_channel *bc = ast_bridged_channel(c); + if ((concise || verbose) && c->cdr && !ast_tvzero(c->cdr->start)) { + duration = (int)(ast_tvdiff_ms(ast_tvnow(), c->cdr->start) / 1000); + if (verbose) { + durh = duration / 3600; + durm = (duration % 3600) / 60; + durs = duration % 60; + snprintf(durbuf, sizeof(durbuf), "%02d:%02d:%02d", durh, durm, durs); + } else { + snprintf(durbuf, sizeof(durbuf), "%d", duration); + } + } else { + durbuf[0] = '\0'; + } + if (concise) { + ast_cli(fd, CONCISE_FORMAT_STRING, c->name, c->context, c->exten, c->priority, ast_state2str(c->_state), + c->appl ? c->appl : "(None)", + S_OR(c->data, ""), /* XXX different from verbose ? */ + S_OR(c->cid.cid_num, ""), + S_OR(c->accountcode, ""), + c->amaflags, + durbuf, + bc ? bc->name : "(None)"); + } else if (verbose) { + ast_cli(fd, VERBOSE_FORMAT_STRING, c->name, c->context, c->exten, c->priority, ast_state2str(c->_state), + c->appl ? c->appl : "(None)", + c->data ? S_OR(c->data, "(Empty)" ): "(None)", + S_OR(c->cid.cid_num, ""), + durbuf, + S_OR(c->accountcode, ""), + bc ? bc->name : "(None)"); + } else { + if (!ast_strlen_zero(c->context) && !ast_strlen_zero(c->exten)) + snprintf(locbuf, sizeof(locbuf), "%s@%s:%d", c->exten, c->context, c->priority); + else + strcpy(locbuf, "(None)"); + if (c->appl) + snprintf(appdata, sizeof(appdata), "%s(%s)", c->appl, c->data ? c->data : ""); + else + strcpy(appdata, "(None)"); + ast_cli(fd, FORMAT_STRING, c->name, locbuf, ast_state2str(c->_state), appdata); + } + numchans++; + ast_channel_unlock(c); + } + if (!concise) { + ast_cli(fd, "%d active channel%s\n", numchans, ESS(numchans)); + if (option_maxcalls) + ast_cli(fd, "%d of %d max active call%s (%5.2f%% of capacity)\n", + ast_active_calls(), option_maxcalls, ESS(ast_active_calls()), + ((double)ast_active_calls() / (double)option_maxcalls) * 100.0); + else + ast_cli(fd, "%d active call%s\n", ast_active_calls(), ESS(ast_active_calls())); + } + return RESULT_SUCCESS; + +#undef FORMAT_STRING +#undef FORMAT_STRING2 +#undef CONCISE_FORMAT_STRING +#undef VERBOSE_FORMAT_STRING +#undef VERBOSE_FORMAT_STRING2 +} + +static char showchan_help[] = +"Usage: show channel \n" +" Shows lots of information about the specified channel.\n"; + +static char debugchan_help[] = +"Usage: debug channel \n" +" Enables debugging on a specific channel.\n"; + +static char debuglevel_help[] = +"Usage: debug level [filename]\n" +" Set debug to specified level (0 to disable). If filename\n" +"is specified, debugging will be limited to just that file.\n"; + +static char nodebugchan_help[] = +"Usage: no debug channel \n" +" Disables debugging on a specific channel.\n"; + +static char commandcomplete_help[] = +"Usage: _command complete \"\" text state\n" +" This function is used internally to help with command completion and should.\n" +" never be called by the user directly.\n"; + +static char commandnummatches_help[] = +"Usage: _command nummatches \"\" text \n" +" This function is used internally to help with command completion and should.\n" +" never be called by the user directly.\n"; + +static char commandmatchesarray_help[] = +"Usage: _command matchesarray \"\" text \n" +" This function is used internally to help with command completion and should.\n" +" never be called by the user directly.\n"; + +static int handle_softhangup(int fd, int argc, char *argv[]) +{ + struct ast_channel *c=NULL; + if (argc != 3) + return RESULT_SHOWUSAGE; + c = ast_get_channel_by_name_locked(argv[2]); + if (c) { + ast_cli(fd, "Requested Hangup on channel '%s'\n", c->name); + ast_softhangup(c, AST_SOFTHANGUP_EXPLICIT); + ast_channel_unlock(c); + } else + ast_cli(fd, "%s is not a known channel\n", argv[2]); + return RESULT_SUCCESS; +} + +static char *__ast_cli_generator(const char *text, const char *word, int state, int lock); + +static int handle_commandmatchesarray(int fd, int argc, char *argv[]) +{ + char *buf, *obuf; + int buflen = 2048; + int len = 0; + char **matches; + int x, matchlen; + + if (argc != 4) + return RESULT_SHOWUSAGE; + if (!(buf = ast_malloc(buflen))) + return RESULT_FAILURE; + buf[len] = '\0'; + matches = ast_cli_completion_matches(argv[2], argv[3]); + if (matches) { + for (x=0; matches[x]; x++) { + matchlen = strlen(matches[x]) + 1; + if (len + matchlen >= buflen) { + buflen += matchlen * 3; + obuf = buf; + if (!(buf = ast_realloc(obuf, buflen))) + /* Memory allocation failure... Just free old buffer and be done */ + free(obuf); + } + if (buf) + len += sprintf( buf + len, "%s ", matches[x]); + free(matches[x]); + matches[x] = NULL; + } + free(matches); + } + + if (buf) { + ast_cli(fd, "%s%s",buf, AST_CLI_COMPLETE_EOF); + free(buf); + } else + ast_cli(fd, "NULL\n"); + + return RESULT_SUCCESS; +} + + + +static int handle_commandnummatches(int fd, int argc, char *argv[]) +{ + int matches = 0; + + if (argc != 4) + return RESULT_SHOWUSAGE; + + matches = ast_cli_generatornummatches(argv[2], argv[3]); + + ast_cli(fd, "%d", matches); + + return RESULT_SUCCESS; +} + +static int handle_commandcomplete(int fd, int argc, char *argv[]) +{ + char *buf; + + if (argc != 5) + return RESULT_SHOWUSAGE; + buf = __ast_cli_generator(argv[2], argv[3], atoi(argv[4]), 0); + if (buf) { + ast_cli(fd, buf); + free(buf); + } else + ast_cli(fd, "NULL\n"); + return RESULT_SUCCESS; +} + +static int handle_debuglevel(int fd, int argc, char *argv[]) +{ + int newlevel; + char *filename = ""; + if ((argc < 3) || (argc > 4)) + return RESULT_SHOWUSAGE; + if (sscanf(argv[2], "%d", &newlevel) != 1) + return RESULT_SHOWUSAGE; + option_debug = newlevel; + if (argc == 4) { + filename = argv[3]; + ast_copy_string(debug_filename, filename, sizeof(debug_filename)); + } else { + debug_filename[0] = '\0'; + } + ast_cli(fd, "Debugging level set to %d, file '%s'\n", newlevel, filename); + return RESULT_SUCCESS; +} + +/* XXX todo: merge next two functions!!! */ +static int handle_debugchan(int fd, int argc, char *argv[]) +{ + struct ast_channel *c=NULL; + int is_all; + + /* 'debug channel {all|chan_id}' */ + if (argc != 3) + return RESULT_SHOWUSAGE; + + is_all = !strcasecmp("all", argv[2]); + if (is_all) { + global_fin |= DEBUGCHAN_FLAG; + global_fout |= DEBUGCHAN_FLAG; + c = ast_channel_walk_locked(NULL); + } else { + c = ast_get_channel_by_name_locked(argv[2]); + if (c == NULL) + ast_cli(fd, "No such channel %s\n", argv[2]); + } + while (c) { + if (!(c->fin & DEBUGCHAN_FLAG) || !(c->fout & DEBUGCHAN_FLAG)) { + c->fin |= DEBUGCHAN_FLAG; + c->fout |= DEBUGCHAN_FLAG; + ast_cli(fd, "Debugging enabled on channel %s\n", c->name); + } + ast_channel_unlock(c); + if (!is_all) + break; + c = ast_channel_walk_locked(c); + } + ast_cli(fd, "Debugging on new channels is enabled\n"); + return RESULT_SUCCESS; +} + +static int handle_nodebugchan(int fd, int argc, char *argv[]) +{ + struct ast_channel *c=NULL; + int is_all; + /* 'no debug channel {all|chan_id}' */ + if (argc != 4) + return RESULT_SHOWUSAGE; + is_all = !strcasecmp("all", argv[3]); + if (is_all) { + global_fin &= ~DEBUGCHAN_FLAG; + global_fout &= ~DEBUGCHAN_FLAG; + c = ast_channel_walk_locked(NULL); + } else { + c = ast_get_channel_by_name_locked(argv[3]); + if (c == NULL) + ast_cli(fd, "No such channel %s\n", argv[3]); + } + while(c) { + if ((c->fin & DEBUGCHAN_FLAG) || (c->fout & DEBUGCHAN_FLAG)) { + c->fin &= ~DEBUGCHAN_FLAG; + c->fout &= ~DEBUGCHAN_FLAG; + ast_cli(fd, "Debugging disabled on channel %s\n", c->name); + } + ast_channel_unlock(c); + if (!is_all) + break; + c = ast_channel_walk_locked(c); + } + ast_cli(fd, "Debugging on new channels is disabled\n"); + return RESULT_SUCCESS; +} + +static int handle_showchan(int fd, int argc, char *argv[]) +{ + struct ast_channel *c=NULL; + struct timeval now; + char buf[2048]; + char cdrtime[256]; + char nf[256], wf[256], rf[256]; + long elapsed_seconds=0; + int hour=0, min=0, sec=0; + + if (argc != 3) + return RESULT_SHOWUSAGE; + now = ast_tvnow(); + c = ast_get_channel_by_name_locked(argv[2]); + if (!c) { + ast_cli(fd, "%s is not a known channel\n", argv[2]); + return RESULT_SUCCESS; + } + if(c->cdr) { + elapsed_seconds = now.tv_sec - c->cdr->start.tv_sec; + hour = elapsed_seconds / 3600; + min = (elapsed_seconds % 3600) / 60; + sec = elapsed_seconds % 60; + snprintf(cdrtime, sizeof(cdrtime), "%dh%dm%ds", hour, min, sec); + } else + strcpy(cdrtime, "N/A"); + ast_cli(fd, + " -- General --\n" + " Name: %s\n" + " Type: %s\n" + " UniqueID: %s\n" + " Caller ID: %s\n" + " Caller ID Name: %s\n" + " DNID Digits: %s\n" + " State: %s (%d)\n" + " Rings: %d\n" + " NativeFormats: %s\n" + " WriteFormat: %s\n" + " ReadFormat: %s\n" + " WriteTranscode: %s\n" + " ReadTranscode: %s\n" + "1st File Descriptor: %d\n" + " Frames in: %d%s\n" + " Frames out: %d%s\n" + " Time to Hangup: %ld\n" + " Elapsed Time: %s\n" + " Direct Bridge: %s\n" + "Indirect Bridge: %s\n" + " -- PBX --\n" + " Context: %s\n" + " Extension: %s\n" + " Priority: %d\n" + " Call Group: %d\n" + " Pickup Group: %d\n" + " Application: %s\n" + " Data: %s\n" + " Blocking in: %s\n", + c->name, c->tech->type, c->uniqueid, + S_OR(c->cid.cid_num, "(N/A)"), + S_OR(c->cid.cid_name, "(N/A)"), + S_OR(c->cid.cid_dnid, "(N/A)"), ast_state2str(c->_state), c->_state, c->rings, + ast_getformatname_multiple(nf, sizeof(nf), c->nativeformats), + ast_getformatname_multiple(wf, sizeof(wf), c->writeformat), + ast_getformatname_multiple(rf, sizeof(rf), c->readformat), + c->writetrans ? "Yes" : "No", + c->readtrans ? "Yes" : "No", + c->fds[0], + c->fin & ~DEBUGCHAN_FLAG, (c->fin & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "", + c->fout & ~DEBUGCHAN_FLAG, (c->fout & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "", + (long)c->whentohangup, + cdrtime, c->_bridge ? c->_bridge->name : "", ast_bridged_channel(c) ? ast_bridged_channel(c)->name : "", + c->context, c->exten, c->priority, c->callgroup, c->pickupgroup, ( c->appl ? c->appl : "(N/A)" ), + ( c-> data ? S_OR(c->data, "(Empty)") : "(None)"), + (ast_test_flag(c, AST_FLAG_BLOCKING) ? c->blockproc : "(Not Blocking)")); + + if(pbx_builtin_serialize_variables(c,buf,sizeof(buf))) + ast_cli(fd," Variables:\n%s\n",buf); + if(c->cdr && ast_cdr_serialize_variables(c->cdr,buf, sizeof(buf), '=', '\n', 1)) + ast_cli(fd," CDR Variables:\n%s\n",buf); + + ast_channel_unlock(c); + return RESULT_SUCCESS; +} + +/* + * helper function to generate CLI matches from a fixed set of values. + * A NULL word is acceptable. + */ +char *ast_cli_complete(const char *word, char *const choices[], int state) +{ + int i, which = 0, len; + len = ast_strlen_zero(word) ? 0 : strlen(word); + + for (i = 0; choices[i]; i++) { + if ((!len || !strncasecmp(word, choices[i], len)) && ++which > state) + return ast_strdup(choices[i]); + } + return NULL; +} + +static char *complete_show_channels(const char *line, const char *word, int pos, int state) +{ + static char *choices[] = { "concise", "verbose", NULL }; + + return (pos != 2) ? NULL : ast_cli_complete(word, choices, state); +} + +char *ast_complete_channels(const char *line, const char *word, int pos, int state, int rpos) +{ + struct ast_channel *c = NULL; + int which = 0; + int wordlen; + char notfound = '\0'; + char *ret = ¬found; /* so NULL can break the loop */ + + if (pos != rpos) + return NULL; + + wordlen = strlen(word); + + while (ret == ¬found && (c = ast_channel_walk_locked(c))) { + if (!strncasecmp(word, c->name, wordlen) && ++which > state) + ret = ast_strdup(c->name); + ast_channel_unlock(c); + } + return ret == ¬found ? NULL : ret; +} + +static char *complete_ch_3(const char *line, const char *word, int pos, int state) +{ + return ast_complete_channels(line, word, pos, state, 2); +} + +static char *complete_ch_4(const char *line, const char *word, int pos, int state) +{ + return ast_complete_channels(line, word, pos, state, 3); +} + +static char *complete_mod_2_nr(const char *line, const char *word, int pos, int state) +{ + return ast_module_helper(line, word, pos, state, 1, 0); +} + +static char *complete_mod_2(const char *line, const char *word, int pos, int state) +{ + return ast_module_helper(line, word, pos, state, 1, 1); +} + +static char *complete_mod_4(const char *line, const char *word, int pos, int state) +{ + return ast_module_helper(line, word, pos, state, 3, 0); +} + +static char *complete_fn(const char *line, const char *word, int pos, int state) +{ + char *c; + char filename[256]; + + if (pos != 1) + return NULL; + + if (word[0] == '/') + ast_copy_string(filename, word, sizeof(filename)); + else + snprintf(filename, sizeof(filename), "%s/%s", ast_config_AST_MODULE_DIR, word); + + c = filename_completion_function(filename, state); + + if (c && word[0] != '/') + c += (strlen(ast_config_AST_MODULE_DIR) + 1); + + return c ? strdup(c) : c; +} + +static int group_show_channels(int fd, int argc, char *argv[]) +{ +#define FORMAT_STRING "%-25s %-20s %-20s\n" + + struct ast_channel *c = NULL; + int numchans = 0; + struct ast_var_t *current; + struct varshead *headp; + regex_t regexbuf; + int havepattern = 0; + + if (argc < 3 || argc > 4) + return RESULT_SHOWUSAGE; + + if (argc == 4) { + if (regcomp(®exbuf, argv[3], REG_EXTENDED | REG_NOSUB)) + return RESULT_SHOWUSAGE; + havepattern = 1; + } + + ast_cli(fd, FORMAT_STRING, "Channel", "Group", "Category"); + while ( (c = ast_channel_walk_locked(c)) != NULL) { + headp=&c->varshead; + AST_LIST_TRAVERSE(headp,current,entries) { + if (!strncmp(ast_var_name(current), GROUP_CATEGORY_PREFIX "_", strlen(GROUP_CATEGORY_PREFIX) + 1)) { + if (!havepattern || !regexec(®exbuf, ast_var_value(current), 0, NULL, 0)) { + ast_cli(fd, FORMAT_STRING, c->name, ast_var_value(current), + (ast_var_name(current) + strlen(GROUP_CATEGORY_PREFIX) + 1)); + numchans++; + } + } else if (!strcmp(ast_var_name(current), GROUP_CATEGORY_PREFIX)) { + if (!havepattern || !regexec(®exbuf, ast_var_value(current), 0, NULL, 0)) { + ast_cli(fd, FORMAT_STRING, c->name, ast_var_value(current), "(default)"); + numchans++; + } + } + } + numchans++; + ast_channel_unlock(c); + } + + if (havepattern) + regfree(®exbuf); + + ast_cli(fd, "%d active channel%s\n", numchans, (numchans != 1) ? "s" : ""); + return RESULT_SUCCESS; +#undef FORMAT_STRING +} + +static int handle_help(int fd, int argc, char *argv[]); + +static char * complete_help(const char *text, const char *word, int pos, int state) +{ + /* skip first 4 or 5 chars, "help "*/ + int l = strlen(text); + + if (l > 5) + l = 5; + text += l; + /* XXX watch out, should stop to the non-generator parts */ + return __ast_cli_generator(text, word, state, 0); +} + +static struct ast_cli_entry builtins[] = { + /* Keep alphabetized, with longer matches first (example: abcd before abc) */ + { { "_command", "complete", NULL }, handle_commandcomplete, "Command complete", commandcomplete_help }, + { { "_command", "nummatches", NULL }, handle_commandnummatches, "Returns number of command matches", commandnummatches_help }, + { { "_command", "matchesarray", NULL }, handle_commandmatchesarray, "Returns command matches array", commandmatchesarray_help }, + { { "debug", "channel", NULL }, handle_debugchan, "Enable debugging on a channel", debugchan_help, complete_ch_3 }, + { { "debug", "level", NULL }, handle_debuglevel, "Set global debug level", debuglevel_help }, + { { "group", "show", "channels", NULL }, group_show_channels, "Show active channels with group(s)", group_show_channels_help}, + { { "help", NULL }, handle_help, "Display help list, or specific help on a command", help_help, complete_help }, + { { "load", NULL }, handle_load, "Load a module by name", load_help, complete_fn }, + { { "logger", "mute", NULL }, handle_logger_mute, "Toggle logging output to a console", logger_mute_help }, + { { "no", "debug", "channel", NULL }, handle_nodebugchan, "Disable debugging on a channel", nodebugchan_help, complete_ch_4 }, + { { "reload", NULL }, handle_reload, "Reload configuration", reload_help, complete_mod_2 }, + { { "set", "debug", NULL }, handle_set_debug, "Set level of debug chattiness", set_debug_help }, + { { "set", "verbose", NULL }, handle_set_verbose, "Set level of verboseness", set_verbose_help }, + { { "show", "channel", NULL }, handle_showchan, "Display information on a specific channel", showchan_help, complete_ch_3 }, + { { "show", "channels", NULL }, handle_chanlist, "Display information on channels", chanlist_help, complete_show_channels }, + { { "show", "modules", NULL }, handle_modlist, "List modules and info", modlist_help }, + { { "show", "modules", "like", NULL }, handle_modlist, "List modules and info", modlist_help, complete_mod_4 }, + { { "show", "uptime", NULL }, handle_showuptime, "Show uptime information", uptime_help }, + { { "soft", "hangup", NULL }, handle_softhangup, "Request a hangup on a given channel", softhangup_help, complete_ch_3 }, + { { "unload", NULL }, handle_unload, "Unload a module by name", unload_help, complete_mod_2_nr }, + { { NULL }, NULL, NULL, NULL } +}; + +/*! \brief initialize the _full_cmd string in * each of the builtins. */ +void ast_builtins_init(void) +{ + struct ast_cli_entry *e; + + for (e = builtins; e->cmda[0] != NULL; e++) { + char buf[80]; + ast_join(buf, sizeof(buf), e->cmda); + e->_full_cmd = strdup(buf); + if (!e->_full_cmd) + ast_log(LOG_WARNING, "-- cannot allocate <%s>\n", buf); + } +} + +/* + * We have two sets of commands: builtins are stored in a + * NULL-terminated array of ast_cli_entry, whereas external + * commands are in a list. + * When navigating, we need to keep two pointers and get + * the next one in lexicographic order. For the purpose, + * we use a structure. + */ + +struct cli_iterator { + struct ast_cli_entry *builtins; + struct ast_cli_entry *helpers; +}; + +static struct ast_cli_entry *cli_next(struct cli_iterator *i) +{ + struct ast_cli_entry *e; + + if (i->builtins == NULL && i->helpers == NULL) { + /* initialize */ + i->builtins = builtins; + i->helpers = AST_LIST_FIRST(&helpers); + } + e = i->builtins; /* temporary */ + if (!e->cmda[0] || (i->helpers && + strcmp(i->helpers->_full_cmd, e->_full_cmd) < 0)) { + /* Use helpers */ + e = i->helpers; + if (e) + i->helpers = AST_LIST_NEXT(e, list); + } else { /* use builtin. e is already set */ + (i->builtins)++; /* move to next */ + } + return e; +} + +/*! + * \brief locate a cli command in the 'helpers' list (which must be locked). + * exact has 3 values: + * 0 returns if the search key is equal or longer than the entry. + * -1 true if the mismatch is on the last word XXX not true! + * 1 true only on complete, exact match. + */ +static struct ast_cli_entry *find_cli(char *const cmds[], int match_type) +{ + int matchlen = -1; /* length of longest match so far */ + struct ast_cli_entry *cand = NULL, *e=NULL; + struct cli_iterator i = { NULL, NULL}; + + while( (e = cli_next(&i)) ) { + int y; + for (y = 0 ; cmds[y] && e->cmda[y]; y++) { + if (strcasecmp(e->cmda[y], cmds[y])) + break; + } + if (e->cmda[y] == NULL) { /* no more words in candidate */ + if (cmds[y] == NULL) /* this is an exact match, cannot do better */ + break; + /* here the search key is longer than the candidate */ + if (match_type != 0) /* but we look for almost exact match... */ + continue; /* so we skip this one. */ + /* otherwise we like it (case 0) */ + } else { /* still words in candidate */ + if (cmds[y] == NULL) /* search key is shorter, not good */ + continue; + /* if we get here, both words exist but there is a mismatch */ + if (match_type == 0) /* not the one we look for */ + continue; + if (match_type == 1) /* not the one we look for */ + continue; + if (cmds[y+1] != NULL || e->cmda[y+1] != NULL) /* not the one we look for */ + continue; + /* we are in case match_type == -1 and mismatch on last word */ + } + if (cand == NULL || y > matchlen) /* remember the candidate */ + cand = e; + } + return e ? e : cand; +} + +static char *find_best(char *argv[]) +{ + static char cmdline[80]; + int x; + /* See how close we get, then print the candidate */ + char *myargv[AST_MAX_CMD_LEN]; + for (x=0;xinuse) { + ast_log(LOG_WARNING, "Can't remove command that is in use\n"); + } else { + AST_LIST_LOCK(&helpers); + AST_LIST_REMOVE(&helpers, e, list); + AST_LIST_UNLOCK(&helpers); + } + return 0; +} + +int ast_cli_register(struct ast_cli_entry *e) +{ + struct ast_cli_entry *cur; + char fulle[80] =""; + int lf, ret = -1; + + ast_join(fulle, sizeof(fulle), e->cmda); + AST_LIST_LOCK(&helpers); + + if (find_cli(e->cmda, 1)) { + AST_LIST_UNLOCK(&helpers); + ast_log(LOG_WARNING, "Command '%s' already registered (or something close enough)\n", fulle); + goto done; + } + e->_full_cmd = ast_strdup(fulle); + if (!e->_full_cmd) + goto done; + lf = strlen(fulle); + AST_LIST_TRAVERSE_SAFE_BEGIN(&helpers, cur, list) { + int len = strlen(cur->_full_cmd); + if (lf < len) + len = lf; + if (strncasecmp(fulle, cur->_full_cmd, len) < 0) { + AST_LIST_INSERT_BEFORE_CURRENT(&helpers, e, list); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + + if (!cur) + AST_LIST_INSERT_TAIL(&helpers, e, list); + ret = 0; /* success */ + +done: + AST_LIST_UNLOCK(&helpers); + + return ret; +} + +/* + * register/unregister an array of entries. + */ +void ast_cli_register_multiple(struct ast_cli_entry *e, int len) +{ + int i; + + for (i = 0; i < len; i++) + ast_cli_register(e + i); +} + +void ast_cli_unregister_multiple(struct ast_cli_entry *e, int len) +{ + int i; + + for (i = 0; i < len; i++) + ast_cli_unregister(e + i); +} + + +/*! \brief helper for help_workhorse and final part of + * handle_help. if locked = 0 it's just help_workhorse, + * otherwise assume the list is already locked and print + * an error message if not found. + */ +static int help1(int fd, char *match[], int locked) +{ + char matchstr[80] = ""; + struct ast_cli_entry *e; + int len = 0; + int found = 0; + struct cli_iterator i = { NULL, NULL}; + + if (match) { + ast_join(matchstr, sizeof(matchstr), match); + len = strlen(matchstr); + } + if (!locked) + AST_LIST_LOCK(&helpers); + while ( (e = cli_next(&i)) ) { + /* Hide commands that start with '_' */ + if (e->_full_cmd[0] == '_') + continue; + if (match && strncasecmp(matchstr, e->_full_cmd, len)) + continue; + ast_cli(fd, "%25.25s %s\n", e->_full_cmd, e->summary); + found++; + } + AST_LIST_UNLOCK(&helpers); + if (!locked && !found && matchstr[0]) + ast_cli(fd, "No such command '%s'.\n", matchstr); + return 0; +} + +static int help_workhorse(int fd, char *match[]) +{ + return help1(fd, match, 0 /* do not print errors */); +} + +static int handle_help(int fd, int argc, char *argv[]) +{ + char fullcmd[80]; + struct ast_cli_entry *e; + + if (argc < 1) + return RESULT_SHOWUSAGE; + if (argc == 1) + return help_workhorse(fd, NULL); + + AST_LIST_LOCK(&helpers); + e = find_cli(argv + 1, 1); /* try exact match first */ + if (!e) + return help1(fd, argv + 1, 1 /* locked */); + if (e->usage) + ast_cli(fd, "%s", e->usage); + else { + ast_join(fullcmd, sizeof(fullcmd), argv+1); + ast_cli(fd, "No help text available for '%s'.\n", fullcmd); + } + AST_LIST_UNLOCK(&helpers); + return RESULT_SUCCESS; +} + +static char *parse_args(const char *s, int *argc, char *argv[], int max, int *trailingwhitespace) +{ + char *dup, *cur; + int x = 0; + int quoted = 0; + int escaped = 0; + int whitespace = 1; + + *trailingwhitespace = 0; + if (s == NULL) /* invalid, though! */ + return NULL; + /* make a copy to store the parsed string */ + if (!(dup = ast_strdup(s))) + return NULL; + + cur = dup; + /* scan the original string copying into cur when needed */ + for (; *s ; s++) { + if (x >= max - 1) { + ast_log(LOG_WARNING, "Too many arguments, truncating at %s\n", s); + break; + } + if (*s == '"' && !escaped) { + quoted = !quoted; + if (quoted && whitespace) { + /* start a quoted string from previous whitespace: new argument */ + argv[x++] = cur; + whitespace = 0; + } + } else if ((*s == ' ' || *s == '\t') && !(quoted || escaped)) { + /* If we are not already in whitespace, and not in a quoted string or + processing an escape sequence, and just entered whitespace, then + finalize the previous argument and remember that we are in whitespace + */ + if (!whitespace) { + *cur++ = '\0'; + whitespace = 1; + } + } else if (*s == '\\' && !escaped) { + escaped = 1; + } else { + if (whitespace) { + /* we leave whitespace, and are not quoted. So it's a new argument */ + argv[x++] = cur; + whitespace = 0; + } + *cur++ = *s; + escaped = 0; + } + } + /* Null terminate */ + *cur++ = '\0'; + /* XXX put a NULL in the last argument, because some functions that take + * the array may want a null-terminated array. + * argc still reflects the number of non-NULL entries. + */ + argv[x] = NULL; + *argc = x; + *trailingwhitespace = whitespace; + return dup; +} + +/*! \brief Return the number of unique matches for the generator */ +int ast_cli_generatornummatches(const char *text, const char *word) +{ + int matches = 0, i = 0; + char *buf = NULL, *oldbuf = NULL; + + while ((buf = ast_cli_generator(text, word, i++))) { + if (!oldbuf || strcmp(buf,oldbuf)) + matches++; + if (oldbuf) + free(oldbuf); + oldbuf = buf; + } + if (oldbuf) + free(oldbuf); + return matches; +} + +char **ast_cli_completion_matches(const char *text, const char *word) +{ + char **match_list = NULL, *retstr, *prevstr; + size_t match_list_len, max_equal, which, i; + int matches = 0; + + /* leave entry 0 free for the longest common substring */ + match_list_len = 1; + while ((retstr = ast_cli_generator(text, word, matches)) != NULL) { + if (matches + 1 >= match_list_len) { + match_list_len <<= 1; + if (!(match_list = ast_realloc(match_list, match_list_len * sizeof(*match_list)))) + return NULL; + } + match_list[++matches] = retstr; + } + + if (!match_list) + return match_list; /* NULL */ + + /* Find the longest substring that is common to all results + * (it is a candidate for completion), and store a copy in entry 0. + */ + prevstr = match_list[1]; + max_equal = strlen(prevstr); + for (which = 2; which <= matches; which++) { + for (i = 0; i < max_equal && toupper(prevstr[i]) == toupper(match_list[which][i]); i++) + continue; + max_equal = i; + } + + if (!(retstr = ast_malloc(max_equal + 1))) + return NULL; + + strncpy(retstr, match_list[1], max_equal); + retstr[max_equal] = '\0'; + match_list[0] = retstr; + + /* ensure that the array is NULL terminated */ + if (matches + 1 >= match_list_len) { + if (!(match_list = ast_realloc(match_list, (match_list_len + 1) * sizeof(*match_list)))) + return NULL; + } + match_list[matches + 1] = NULL; + + return match_list; +} + +static char *__ast_cli_generator(const char *text, const char *word, int state, int lock) +{ + char *argv[AST_MAX_ARGS]; + struct ast_cli_entry *e; + struct cli_iterator i = { NULL, NULL }; + int x = 0, argindex, matchlen; + int matchnum=0; + char *ret = NULL; + char matchstr[80] = ""; + int tws = 0; + char *dup = parse_args(text, &x, argv, sizeof(argv) / sizeof(argv[0]), &tws); + + if (!dup) /* error */ + return NULL; + argindex = (!ast_strlen_zero(word) && x>0) ? x-1 : x; + /* rebuild the command, ignore tws */ + ast_join(matchstr, sizeof(matchstr)-1, argv); + matchlen = strlen(matchstr); + if (tws) { + strcat(matchstr, " "); /* XXX */ + if (matchlen) + matchlen++; + } + if (lock) + AST_LIST_LOCK(&helpers); + while( !ret && (e = cli_next(&i)) ) { + int lc = strlen(e->_full_cmd); + if (e->_full_cmd[0] != '_' && lc > 0 && matchlen <= lc && + !strncasecmp(matchstr, e->_full_cmd, matchlen)) { + /* Found initial part, return a copy of the next word... */ + if (e->cmda[argindex] && ++matchnum > state) + ret = strdup(e->cmda[argindex]); /* we need a malloced string */ + } else if (e->generator && !strncasecmp(matchstr, e->_full_cmd, lc) && matchstr[lc] < 33) { + /* We have a command in its entirity within us -- theoretically only one + command can have this occur */ + ret = e->generator(matchstr, word, argindex, state); + } + } + if (lock) + AST_LIST_UNLOCK(&helpers); + free(dup); + return ret; +} + +char *ast_cli_generator(const char *text, const char *word, int state) +{ + return __ast_cli_generator(text, word, state, 1); +} + +int ast_cli_command(int fd, const char *s) +{ + char *argv[AST_MAX_ARGS]; + struct ast_cli_entry *e; + int x; + char *dup; + int tws; + + if (!(dup = parse_args(s, &x, argv, sizeof(argv) / sizeof(argv[0]), &tws))) + return -1; + + /* We need at least one entry, or ignore */ + if (x > 0) { + AST_LIST_LOCK(&helpers); + e = find_cli(argv, 0); + if (e) + e->inuse++; + AST_LIST_UNLOCK(&helpers); + if (e) { + switch(e->handler(fd, x, argv)) { + case RESULT_SHOWUSAGE: + if (e->usage) + ast_cli(fd, "%s", e->usage); + else + ast_cli(fd, "Invalid usage, but no usage information available.\n"); + break; + } + } else + ast_cli(fd, "No such command '%s' (type 'help' for help)\n", find_best(argv)); + if (e) + ast_atomic_fetchadd_int(&e->inuse, -1); + } + free(dup); + + return 0; +} diff --git a/main/coef_in.h b/main/coef_in.h new file mode 100644 index 000000000..9aba022f6 --- /dev/null +++ b/main/coef_in.h @@ -0,0 +1,13 @@ + { { 1.8229206611e-04,-7.8997325866e-01,2.2401819940e+00,-4.6751353581e+00,5.5080745712e+00,-5.0571565772e+00,2.6215820004e+00,0.0000000000e+00, + }, { 9.8532175289e-02,-5.6297236492e-02,3.3146713415e-01,-9.2239200436e-01,1.4844365184e+00,-2.0183258642e+00,2.0074154497e+00,0.0000000000e+00, + }, }, { { 1.8229206610e-04,-7.8997325866e-01,7.7191410839e-01,-2.8075643964e+00,1.6948618347e+00,-3.0367273700e+00,9.0333559408e-01,0.0000000000e+00, + }, { 9.8531161839e-02,-5.6297236492e-02,1.1421579050e-01,-4.8122536483e-01,4.0121072432e-01,-7.4834487567e-01,6.9170822332e-01,0.0000000000e+00, + }, }, { { 1.8229206611e-04,-7.8997325866e-01,2.9003821430e+00,-6.1082779024e+00,7.7169345751e+00,-6.6075999680e+00,3.3941838836e+00,0.0000000000e+00, + }, { 9.8539686961e-02,-5.6297236492e-02,4.2915323820e-01,-1.2609358633e+00,2.2399213250e+00,-2.9928879142e+00,2.5990173742e+00,0.0000000000e+00, + }, }, { { 1.8229206610e-04,-7.8997325866e-01,-7.7191410839e-01,-2.8075643964e+00,-1.6948618347e+00,-3.0367273700e+00,-9.0333559408e-01,0.0000000000e+00, + }, { 9.8531161839e-02,-5.6297236492e-02,-1.1421579050e-01,-4.8122536483e-01,-4.0121072432e-01,-7.4834487567e-01,-6.9170822332e-01,0.0000000000e+00, + }, }, { { 1.8229206611e-04,-7.8997325866e-01,2.5782298908e+00,-5.3629717478e+00,6.5890882172e+00,-5.8012914776e+00,3.0171839130e+00,0.0000000000e+00, + }, { 9.8534230718e-02,-5.6297236492e-02,3.8148618075e-01,-1.0848760410e+00,1.8441165168e+00,-2.4860666655e+00,2.3103384142e+00,0.0000000000e+00, + }, }, { { 1.8229206610e-04,-7.8997325866e-01,-3.8715051001e-01,-2.6192408538e+00,-8.3977994034e-01,-2.8329897913e+00,-4.5306444352e-01,0.0000000000e+00, + }, { 9.8531160936e-02,-5.6297236492e-02,-5.7284484199e-02,-4.3673866734e-01,-1.9564766257e-01,-6.2028156584e-01,-3.4692356122e-01,0.0000000000e+00, + }, }, diff --git a/main/coef_out.h b/main/coef_out.h new file mode 100644 index 000000000..d80f5c490 --- /dev/null +++ b/main/coef_out.h @@ -0,0 +1,4 @@ + { 1.3868644653e-08,-6.3283665042e-01,4.0895057217e+00,-1.1020074592e+01,1.5850766191e+01,-1.2835109292e+01,5.5477477340e+00,0.0000000000e+00, + }, { 3.1262119724e-03,-7.8390522307e-03,8.5209627801e-02,-4.0804129163e-01,1.1157139955e+00,-1.8767603680e+00,1.8916395224e+00,0.0000000000e+00 + }, + diff --git a/main/config.c b/main/config.c new file mode 100644 index 000000000..ee3922a2a --- /dev/null +++ b/main/config.c @@ -0,0 +1,1235 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Configuration File Parser + * + * \author Mark Spencer + * + * Includes the Asterisk Realtime API - ARA + * See doc/realtime.txt and doc/extconfig.txt + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include +#define AST_INCLUDE_GLOB 1 +#ifdef AST_INCLUDE_GLOB +#if defined(__Darwin__) || defined(__CYGWIN__) +#define GLOB_ABORTED GLOB_ABEND +#endif +# include +#endif + +#include "asterisk/config.h" +#include "asterisk/cli.h" +#include "asterisk/lock.h" +#include "asterisk/options.h" +#include "asterisk/logger.h" +#include "asterisk/utils.h" +#include "asterisk/channel.h" +#include "asterisk/app.h" + +#define MAX_NESTED_COMMENTS 128 +#define COMMENT_START ";--" +#define COMMENT_END "--;" +#define COMMENT_META ';' +#define COMMENT_TAG '-' + +static char *extconfig_conf = "extconfig.conf"; + +static struct ast_config_map { + struct ast_config_map *next; + char *name; + char *driver; + char *database; + char *table; + char stuff[0]; +} *config_maps = NULL; + +AST_MUTEX_DEFINE_STATIC(config_lock); +static struct ast_config_engine *config_engine_list; + +#define MAX_INCLUDE_LEVEL 10 + +struct ast_comment { + struct ast_comment *next; + char cmt[0]; +}; + +struct ast_category { + char name[80]; + int ignored; /* do not let user of the config see this category */ + struct ast_variable *root; + struct ast_variable *last; + struct ast_category *next; +}; + +struct ast_config { + struct ast_category *root; + struct ast_category *last; + struct ast_category *current; + struct ast_category *last_browse; /* used to cache the last category supplied via category_browse */ + int include_level; + int max_include_level; +}; + +struct ast_variable *ast_variable_new(const char *name, const char *value) +{ + struct ast_variable *variable; + int name_len = strlen(name) + 1; + + if ((variable = ast_calloc(1, name_len + strlen(value) + 1 + sizeof(*variable)))) { + variable->name = variable->stuff; + variable->value = variable->stuff + name_len; + strcpy(variable->name,name); + strcpy(variable->value,value); + } + + return variable; +} + +void ast_variable_append(struct ast_category *category, struct ast_variable *variable) +{ + if (!variable) + return; + if (category->last) + category->last->next = variable; + else + category->root = variable; + category->last = variable; +} + +void ast_variables_destroy(struct ast_variable *v) +{ + struct ast_variable *vn; + + while(v) { + vn = v; + v = v->next; + free(vn); + } +} + +struct ast_variable *ast_variable_browse(const struct ast_config *config, const char *category) +{ + struct ast_category *cat = NULL; + + if (category && config->last_browse && (config->last_browse->name == category)) + cat = config->last_browse; + else + cat = ast_category_get(config, category); + + return (cat) ? cat->root : NULL; +} + +char *ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable) +{ + struct ast_variable *v; + + if (category) { + for (v = ast_variable_browse(config, category); v; v = v->next) { + if (!strcasecmp(variable, v->name)) + return v->value; + } + } else { + struct ast_category *cat; + + for (cat = config->root; cat; cat = cat->next) + for (v = cat->root; v; v = v->next) + if (!strcasecmp(variable, v->name)) + return v->value; + } + + return NULL; +} + +static struct ast_variable *variable_clone(const struct ast_variable *old) +{ + struct ast_variable *new = ast_variable_new(old->name, old->value); + + if (new) { + new->lineno = old->lineno; + new->object = old->object; + new->blanklines = old->blanklines; + /* TODO: clone comments? */ + } + + return new; +} + +static void move_variables(struct ast_category *old, struct ast_category *new) +{ + struct ast_variable *var = old->root; + old->root = NULL; +#if 1 + /* we can just move the entire list in a single op */ + ast_variable_append(new, var); +#else + while (var) { + struct ast_variable *next = var->next; + var->next = NULL; + ast_variable_append(new, var); + var = next; + } +#endif +} + +struct ast_category *ast_category_new(const char *name) +{ + struct ast_category *category; + + if ((category = ast_calloc(1, sizeof(*category)))) + ast_copy_string(category->name, name, sizeof(category->name)); + return category; +} + +static struct ast_category *category_get(const struct ast_config *config, const char *category_name, int ignored) +{ + struct ast_category *cat; + + /* try exact match first, then case-insensitive match */ + for (cat = config->root; cat; cat = cat->next) { + if (cat->name == category_name && (ignored || !cat->ignored)) + return cat; + } + + for (cat = config->root; cat; cat = cat->next) { + if (!strcasecmp(cat->name, category_name) && (ignored || !cat->ignored)) + return cat; + } + + return NULL; +} + +struct ast_category *ast_category_get(const struct ast_config *config, const char *category_name) +{ + return category_get(config, category_name, 0); +} + +int ast_category_exist(const struct ast_config *config, const char *category_name) +{ + return !!ast_category_get(config, category_name); +} + +void ast_category_append(struct ast_config *config, struct ast_category *category) +{ + if (config->last) + config->last->next = category; + else + config->root = category; + config->last = category; + config->current = category; +} + +void ast_category_destroy(struct ast_category *cat) +{ + ast_variables_destroy(cat->root); + free(cat); +} + +static struct ast_category *next_available_category(struct ast_category *cat) +{ + for (; cat && cat->ignored; cat = cat->next); + + return cat; +} + +char *ast_category_browse(struct ast_config *config, const char *prev) +{ + struct ast_category *cat = NULL; + + if (prev && config->last_browse && (config->last_browse->name == prev)) + cat = config->last_browse->next; + else if (!prev && config->root) + cat = config->root; + else if (prev) { + for (cat = config->root; cat; cat = cat->next) { + if (cat->name == prev) { + cat = cat->next; + break; + } + } + if (!cat) { + for (cat = config->root; cat; cat = cat->next) { + if (!strcasecmp(cat->name, prev)) { + cat = cat->next; + break; + } + } + } + } + + if (cat) + cat = next_available_category(cat); + + config->last_browse = cat; + return (cat) ? cat->name : NULL; +} + +struct ast_variable *ast_category_detach_variables(struct ast_category *cat) +{ + struct ast_variable *v; + + v = cat->root; + cat->root = NULL; + + return v; +} + +void ast_category_rename(struct ast_category *cat, const char *name) +{ + ast_copy_string(cat->name, name, sizeof(cat->name)); +} + +static void inherit_category(struct ast_category *new, const struct ast_category *base) +{ + struct ast_variable *var; + + for (var = base->root; var; var = var->next) + ast_variable_append(new, variable_clone(var)); +} + +struct ast_config *ast_config_new(void) +{ + struct ast_config *config; + + if ((config = ast_calloc(1, sizeof(*config)))) + config->max_include_level = MAX_INCLUDE_LEVEL; + return config; +} + +int ast_variable_delete(struct ast_category *category, char *variable, char *match) +{ + struct ast_variable *cur, *prev=NULL, *curn; + int res = -1; + cur = category->root; + while (cur) { + if (cur->name == variable) { + if (prev) { + prev->next = cur->next; + if (cur == category->last) + category->last = prev; + } else { + category->root = cur->next; + if (cur == category->last) + category->last = NULL; + } + cur->next = NULL; + ast_variables_destroy(cur); + return 0; + } + prev = cur; + cur = cur->next; + } + + prev = NULL; + cur = category->root; + while (cur) { + curn = cur->next; + if (!strcasecmp(cur->name, variable) && (ast_strlen_zero(match) || !strcasecmp(cur->value, match))) { + if (prev) { + prev->next = cur->next; + if (cur == category->last) + category->last = prev; + } else { + category->root = cur->next; + if (cur == category->last) + category->last = NULL; + } + cur->next = NULL; + ast_variables_destroy(cur); + res = 0; + } else + prev = cur; + + cur = curn; + } + return res; +} + +int ast_variable_update(struct ast_category *category, char *variable, char *value, char *match) +{ + struct ast_variable *cur, *prev=NULL, *newer; + newer = ast_variable_new(variable, value); + if (!newer) + return -1; + cur = category->root; + while (cur) { + if (cur->name == variable) { + newer->next = cur->next; + newer->object = cur->object; + if (prev) + prev->next = newer; + else + category->root = newer; + if (category->last == cur) + category->last = newer; + cur->next = NULL; + ast_variables_destroy(cur); + return 0; + } + prev = cur; + cur = cur->next; + } + + prev = NULL; + cur = category->root; + while (cur) { + if (!strcasecmp(cur->name, variable) && (ast_strlen_zero(match) || !strcasecmp(cur->value, match))) { + newer->next = cur->next; + newer->object = cur->object; + if (prev) + prev->next = newer; + else + category->root = newer; + if (category->last == cur) + category->last = newer; + cur->next = NULL; + ast_variables_destroy(cur); + return 0; + } + prev = cur; + cur = cur->next; + } + if (prev) + prev->next = newer; + else + category->root = newer; + return 0; +} + +int ast_category_delete(struct ast_config *cfg, char *category) +{ + struct ast_category *prev=NULL, *cat; + cat = cfg->root; + while(cat) { + if (cat->name == category) { + ast_variables_destroy(cat->root); + if (prev) { + prev->next = cat->next; + if (cat == cfg->last) + cfg->last = prev; + } else { + cfg->root = cat->next; + if (cat == cfg->last) + cfg->last = NULL; + } + free(cat); + return 0; + } + prev = cat; + cat = cat->next; + } + + prev = NULL; + cat = cfg->root; + while(cat) { + if (!strcasecmp(cat->name, category)) { + ast_variables_destroy(cat->root); + if (prev) { + prev->next = cat->next; + if (cat == cfg->last) + cfg->last = prev; + } else { + cfg->root = cat->next; + if (cat == cfg->last) + cfg->last = NULL; + } + free(cat); + return 0; + } + prev = cat; + cat = cat->next; + } + return -1; +} + +void ast_config_destroy(struct ast_config *cfg) +{ + struct ast_category *cat, *catn; + + if (!cfg) + return; + + cat = cfg->root; + while(cat) { + ast_variables_destroy(cat->root); + catn = cat; + cat = cat->next; + free(catn); + } + free(cfg); +} + +struct ast_category *ast_config_get_current_category(const struct ast_config *cfg) +{ + return cfg->current; +} + +void ast_config_set_current_category(struct ast_config *cfg, const struct ast_category *cat) +{ + /* cast below is just to silence compiler warning about dropping "const" */ + cfg->current = (struct ast_category *) cat; +} + +static int process_text_line(struct ast_config *cfg, struct ast_category **cat, char *buf, int lineno, const char *configfile, int withcomments) +{ + char *c; + char *cur = buf; + struct ast_variable *v; + char cmd[512], exec_file[512]; + int object, do_exec, do_include; + + /* Actually parse the entry */ + if (cur[0] == '[') { + struct ast_category *newcat = NULL; + char *catname; + + /* A category header */ + c = strchr(cur, ']'); + if (!c) { + ast_log(LOG_WARNING, "parse error: no closing ']', line %d of %s\n", lineno, configfile); + return -1; + } + *c++ = '\0'; + cur++; + if (*c++ != '(') + c = NULL; + catname = cur; + if (!(*cat = newcat = ast_category_new(catname))) { + return -1; + } + /* If there are options or categories to inherit from, process them now */ + if (c) { + if (!(cur = strchr(c, ')'))) { + ast_log(LOG_WARNING, "parse error: no closing ')', line %d of %s\n", lineno, configfile); + return -1; + } + *cur = '\0'; + while ((cur = strsep(&c, ","))) { + if (!strcasecmp(cur, "!")) { + (*cat)->ignored = 1; + } else if (!strcasecmp(cur, "+")) { + *cat = category_get(cfg, catname, 1); + if (!*cat) { + ast_config_destroy(cfg); + if (newcat) + ast_category_destroy(newcat); + ast_log(LOG_WARNING, "Category addition requested, but category '%s' does not exist, line %d of %s\n", catname, lineno, configfile); + return -1; + } + if (newcat) { + move_variables(newcat, *cat); + ast_category_destroy(newcat); + newcat = NULL; + } + } else { + struct ast_category *base; + + base = category_get(cfg, cur, 1); + if (!base) { + ast_log(LOG_WARNING, "Inheritance requested, but category '%s' does not exist, line %d of %s\n", cur, lineno, configfile); + return -1; + } + inherit_category(*cat, base); + } + } + } + if (newcat) + ast_category_append(cfg, *cat); + } else if (cur[0] == '#') { + /* A directive */ + cur++; + c = cur; + while(*c && (*c > 32)) c++; + if (*c) { + *c = '\0'; + /* Find real argument */ + c = ast_skip_blanks(c + 1); + if (!*c) + c = NULL; + } else + c = NULL; + do_include = !strcasecmp(cur, "include"); + if(!do_include) + do_exec = !strcasecmp(cur, "exec"); + else + do_exec = 0; + if (do_exec && !ast_opt_exec_includes) { + ast_log(LOG_WARNING, "Cannot perform #exec unless execincludes option is enabled in asterisk.conf (options section)!\n"); + do_exec = 0; + } + if (do_include || do_exec) { + if (c) { + /* Strip off leading and trailing "'s and <>'s */ + while((*c == '<') || (*c == '>') || (*c == '\"')) c++; + /* Get rid of leading mess */ + cur = c; + while (!ast_strlen_zero(cur)) { + c = cur + strlen(cur) - 1; + if ((*c == '>') || (*c == '<') || (*c == '\"')) + *c = '\0'; + else + break; + } + /* #exec + We create a tmp file, then we #include it, then we delete it. */ + if (do_exec) { + snprintf(exec_file, sizeof(exec_file), "/var/tmp/exec.%d.%ld", (int)time(NULL), (long)pthread_self()); + snprintf(cmd, sizeof(cmd), "%s > %s 2>&1", cur, exec_file); + ast_safe_system(cmd); + cur = exec_file; + } else + exec_file[0] = '\0'; + /* A #include */ + do_include = ast_config_internal_load(cur, cfg, withcomments) ? 1 : 0; + if(!ast_strlen_zero(exec_file)) + unlink(exec_file); + if(!do_include) + return 0; + + } else { + ast_log(LOG_WARNING, "Directive '#%s' needs an argument (%s) at line %d of %s\n", + do_exec ? "exec" : "include", + do_exec ? "/path/to/executable" : "filename", + lineno, + configfile); + } + } + else + ast_log(LOG_WARNING, "Unknown directive '%s' at line %d of %s\n", cur, lineno, configfile); + } else { + /* Just a line (variable = value) */ + if (!*cat) { + ast_log(LOG_WARNING, + "parse error: No category context for line %d of %s\n", lineno, configfile); + return -1; + } + c = strchr(cur, '='); + if (c) { + *c = 0; + c++; + /* Ignore > in => */ + if (*c== '>') { + object = 1; + c++; + } else + object = 0; + if ((v = ast_variable_new(ast_strip(cur), ast_strip(c)))) { + v->lineno = lineno; + v->object = object; + /* Put and reset comments */ + v->blanklines = 0; + ast_variable_append(*cat, v); + } else { + return -1; + } + } else { + ast_log(LOG_WARNING, "No '=' (equal sign) in line %d of %s\n", lineno, configfile); + } + + } + return 0; +} + +static struct ast_config *config_text_file_load(const char *database, const char *table, const char *filename, struct ast_config *cfg, int withcomments) +{ + char fn[256]; + char buf[8192]; + char *new_buf, *comment_p, *process_buf; + FILE *f; + int lineno=0; + int comment = 0, nest[MAX_NESTED_COMMENTS]; + struct ast_category *cat = NULL; + int count = 0; + struct stat statbuf; + + cat = ast_config_get_current_category(cfg); + + if (filename[0] == '/') { + ast_copy_string(fn, filename, sizeof(fn)); + } else { + snprintf(fn, sizeof(fn), "%s/%s", (char *)ast_config_AST_CONFIG_DIR, filename); + } + +#ifdef AST_INCLUDE_GLOB + { + int glob_ret; + glob_t globbuf; + globbuf.gl_offs = 0; /* initialize it to silence gcc */ +#ifdef SOLARIS + glob_ret = glob(fn, GLOB_NOCHECK, NULL, &globbuf); +#else + glob_ret = glob(fn, GLOB_NOMAGIC|GLOB_BRACE, NULL, &globbuf); +#endif + if (glob_ret == GLOB_NOSPACE) + ast_log(LOG_WARNING, + "Glob Expansion of pattern '%s' failed: Not enough memory\n", fn); + else if (glob_ret == GLOB_ABORTED) + ast_log(LOG_WARNING, + "Glob Expansion of pattern '%s' failed: Read error\n", fn); + else { + /* loop over expanded files */ + int i; + for (i=0; i 1) && !option_debug) { + ast_verbose(VERBOSE_PREFIX_2 "Parsing '%s': ", fn); + fflush(stdout); + } + if (!(f = fopen(fn, "r"))) { + if (option_debug) + ast_log(LOG_DEBUG, "No file to parse: %s\n", fn); + else if (option_verbose > 1) + ast_verbose( "Not found (%s)\n", strerror(errno)); + continue; + } + count++; + if (option_debug) + ast_log(LOG_DEBUG, "Parsing %s\n", fn); + else if (option_verbose > 1) + ast_verbose("Found\n"); + while(!feof(f)) { + lineno++; + if (fgets(buf, sizeof(buf), f)) { + new_buf = buf; + if (comment) + process_buf = NULL; + else + process_buf = buf; + while ((comment_p = strchr(new_buf, COMMENT_META))) { + if ((comment_p > new_buf) && (*(comment_p-1) == '\\')) { + /* Yuck, gotta memmove */ + memmove(comment_p - 1, comment_p, strlen(comment_p) + 1); + new_buf = comment_p; + } else if(comment_p[1] == COMMENT_TAG && comment_p[2] == COMMENT_TAG && (comment_p[3] != '-')) { + /* Meta-Comment start detected ";--" */ + if (comment < MAX_NESTED_COMMENTS) { + *comment_p = '\0'; + new_buf = comment_p + 3; + comment++; + nest[comment-1] = lineno; + } else { + ast_log(LOG_ERROR, "Maximum nest limit of %d reached.\n", MAX_NESTED_COMMENTS); + } + } else if ((comment_p >= new_buf + 2) && + (*(comment_p - 1) == COMMENT_TAG) && + (*(comment_p - 2) == COMMENT_TAG)) { + /* Meta-Comment end detected */ + comment--; + new_buf = comment_p + 1; + if (!comment) { + /* Back to non-comment now */ + if (process_buf) { + /* Actually have to move what's left over the top, then continue */ + char *oldptr; + oldptr = process_buf + strlen(process_buf); + memmove(oldptr, new_buf, strlen(new_buf) + 1); + new_buf = oldptr; + } else + process_buf = new_buf; + } + } else { + if (!comment) { + /* If ; is found, and we are not nested in a comment, + we immediately stop all comment processing */ + *comment_p = '\0'; + new_buf = comment_p; + } else + new_buf = comment_p + 1; + } + } + if (process_buf) { + char *buf = ast_strip(process_buf); + if (!ast_strlen_zero(buf)) { + if (process_text_line(cfg, &cat, buf, lineno, filename, withcomments)) { + cfg = NULL; + break; + } + } + } + } + } + fclose(f); + } while(0); + if (comment) { + ast_log(LOG_WARNING,"Unterminated comment detected beginning on line %d\n", nest[comment]); + } +#ifdef AST_INCLUDE_GLOB + if (!cfg) + break; + } + globfree(&globbuf); + } + } +#endif + if (count == 0) + return NULL; + + return cfg; +} + +int config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator) +{ + FILE *f; + char fn[256]; + char date[256]=""; + time_t t; + struct ast_variable *var; + struct ast_category *cat; + int blanklines = 0; + + if (configfile[0] == '/') { + ast_copy_string(fn, configfile, sizeof(fn)); + } else { + snprintf(fn, sizeof(fn), "%s/%s", ast_config_AST_CONFIG_DIR, configfile); + } + time(&t); + ast_copy_string(date, ctime(&t), sizeof(date)); +#ifdef __CYGWIN__ + if ((f = fopen(fn, "w+"))) { +#else + if ((f = fopen(fn, "w"))) { +#endif + if ((option_verbose > 1) && !option_debug) + ast_verbose( VERBOSE_PREFIX_2 "Saving '%s': ", fn); + fprintf(f, ";!\n"); + fprintf(f, ";! Automatically generated configuration file\n"); + fprintf(f, ";! Filename: %s (%s)\n", configfile, fn); + fprintf(f, ";! Generator: %s\n", generator); + fprintf(f, ";! Creation Date: %s", date); + fprintf(f, ";!\n"); + cat = cfg->root; + while(cat) { + /* Dump section with any appropriate comment */ + fprintf(f, "\n[%s]\n", cat->name); + var = cat->root; + while(var) { + if (var->sameline) + fprintf(f, "%s %s %s ; %s\n", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt); + else + fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value); + if (var->blanklines) { + blanklines = var->blanklines; + while (blanklines--) + fprintf(f, "\n"); + } + + var = var->next; + } +#if 0 + /* Put an empty line */ + fprintf(f, "\n"); +#endif + cat = cat->next; + } + if ((option_verbose > 1) && !option_debug) + ast_verbose("Saved\n"); + } else { + if (option_debug) + printf("Unable to open for writing: %s\n", fn); + else if (option_verbose > 1) + printf( "Unable to write (%s)", strerror(errno)); + return -1; + } + fclose(f); + return 0; +} + +static void clear_config_maps(void) +{ + struct ast_config_map *map; + + ast_mutex_lock(&config_lock); + + while (config_maps) { + map = config_maps; + config_maps = config_maps->next; + free(map); + } + + ast_mutex_unlock(&config_lock); +} + +static int append_mapping(char *name, char *driver, char *database, char *table) +{ + struct ast_config_map *map; + int length; + + length = sizeof(*map); + length += strlen(name) + 1; + length += strlen(driver) + 1; + length += strlen(database) + 1; + if (table) + length += strlen(table) + 1; + + if (!(map = ast_calloc(1, length))) + return -1; + + map->name = map->stuff; + strcpy(map->name, name); + map->driver = map->name + strlen(map->name) + 1; + strcpy(map->driver, driver); + map->database = map->driver + strlen(map->driver) + 1; + strcpy(map->database, database); + if (table) { + map->table = map->database + strlen(map->database) + 1; + strcpy(map->table, table); + } + map->next = config_maps; + + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Binding %s to %s/%s/%s\n", + map->name, map->driver, map->database, map->table ? map->table : map->name); + + config_maps = map; + return 0; +} + +int read_config_maps(void) +{ + struct ast_config *config, *configtmp; + struct ast_variable *v; + char *driver, *table, *database, *stringp; + + clear_config_maps(); + + configtmp = ast_config_new(); + configtmp->max_include_level = 1; + config = ast_config_internal_load(extconfig_conf, configtmp, 0); + if (!config) { + ast_config_destroy(configtmp); + return 0; + } + + for (v = ast_variable_browse(config, "settings"); v; v = v->next) { + stringp = v->value; + driver = strsep(&stringp, ","); + + /* check if the database text starts with a double quote */ + if (*stringp == '"') { + stringp++; + database = strsep(&stringp, "\""); + strsep(&stringp, ","); + } else { + /* apparently this text has no quotes */ + database = strsep(&stringp, ","); + } + + table = strsep(&stringp, ","); + + if (!strcmp(v->name, extconfig_conf)) { + ast_log(LOG_WARNING, "Cannot bind '%s'!\n", extconfig_conf); + continue; + } + + if (!strcmp(v->name, "asterisk.conf")) { + ast_log(LOG_WARNING, "Cannot bind 'asterisk.conf'!\n"); + continue; + } + + if (!strcmp(v->name, "logger.conf")) { + ast_log(LOG_WARNING, "Cannot bind 'logger.conf'!\n"); + continue; + } + + if (!driver || !database) + continue; + if (!strcasecmp(v->name, "sipfriends")) { + ast_log(LOG_WARNING, "The 'sipfriends' table is obsolete, update your config to use sipusers and sippeers, though they can point to the same table.\n"); + append_mapping("sipusers", driver, database, table ? table : "sipfriends"); + append_mapping("sippeers", driver, database, table ? table : "sipfriends"); + } else if (!strcasecmp(v->name, "iaxfriends")) { + ast_log(LOG_WARNING, "The 'iaxfriends' table is obsolete, update your config to use iaxusers and iaxpeers, though they can point to the same table.\n"); + append_mapping("iaxusers", driver, database, table ? table : "iaxfriends"); + append_mapping("iaxpeers", driver, database, table ? table : "iaxfriends"); + } else + append_mapping(v->name, driver, database, table); + } + + ast_config_destroy(config); + return 0; +} + +int ast_config_engine_register(struct ast_config_engine *new) +{ + struct ast_config_engine *ptr; + + ast_mutex_lock(&config_lock); + + if (!config_engine_list) { + config_engine_list = new; + } else { + for (ptr = config_engine_list; ptr->next; ptr=ptr->next); + ptr->next = new; + } + + ast_mutex_unlock(&config_lock); + ast_log(LOG_NOTICE,"Registered Config Engine %s\n", new->name); + + return 1; +} + +int ast_config_engine_deregister(struct ast_config_engine *del) +{ + struct ast_config_engine *ptr, *last=NULL; + + ast_mutex_lock(&config_lock); + + for (ptr = config_engine_list; ptr; ptr=ptr->next) { + if (ptr == del) { + if (last) + last->next = ptr->next; + else + config_engine_list = ptr->next; + break; + } + last = ptr; + } + + ast_mutex_unlock(&config_lock); + + return 0; +} + +/*! \brief Find realtime engine for realtime family */ +static struct ast_config_engine *find_engine(const char *family, char *database, int dbsiz, char *table, int tabsiz) +{ + struct ast_config_engine *eng, *ret = NULL; + struct ast_config_map *map; + + ast_mutex_lock(&config_lock); + + for (map = config_maps; map; map = map->next) { + if (!strcasecmp(family, map->name)) { + if (database) + ast_copy_string(database, map->database, dbsiz); + if (table) + ast_copy_string(table, map->table ? map->table : family, tabsiz); + break; + } + } + + /* Check if the required driver (engine) exist */ + if (map) { + for (eng = config_engine_list; !ret && eng; eng = eng->next) { + if (!strcasecmp(eng->name, map->driver)) + ret = eng; + } + } + + ast_mutex_unlock(&config_lock); + + /* if we found a mapping, but the engine is not available, then issue a warning */ + if (map && !ret) + ast_log(LOG_WARNING, "Realtime mapping for '%s' found to engine '%s', but the engine is not available\n", map->name, map->driver); + + return ret; +} + +static struct ast_config_engine text_file_engine = { + .name = "text", + .load_func = config_text_file_load, +}; + +struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg, int withcomments) +{ + char db[256]; + char table[256]; + struct ast_config_engine *loader = &text_file_engine; + struct ast_config *result; + + if (cfg->include_level == cfg->max_include_level) { + ast_log(LOG_WARNING, "Maximum Include level (%d) exceeded\n", cfg->max_include_level); + return NULL; + } + + cfg->include_level++; + + if (strcmp(filename, extconfig_conf) && strcmp(filename, "asterisk.conf") && config_engine_list) { + struct ast_config_engine *eng; + + eng = find_engine(filename, db, sizeof(db), table, sizeof(table)); + + + if (eng && eng->load_func) { + loader = eng; + } else { + eng = find_engine("global", db, sizeof(db), table, sizeof(table)); + if (eng && eng->load_func) + loader = eng; + } + } + + result = loader->load_func(db, table, filename, cfg, withcomments); + + if (result) + result->include_level--; + + return result; +} + +struct ast_config *ast_config_load(const char *filename) +{ + struct ast_config *cfg; + struct ast_config *result; + + cfg = ast_config_new(); + if (!cfg) + return NULL; + + result = ast_config_internal_load(filename, cfg, 0); + if (!result) + ast_config_destroy(cfg); + + return result; +} + +struct ast_config *ast_config_load_with_comments(const char *filename) +{ + struct ast_config *cfg; + struct ast_config *result; + + cfg = ast_config_new(); + if (!cfg) + return NULL; + + result = ast_config_internal_load(filename, cfg, 1); + if (!result) + ast_config_destroy(cfg); + + return result; +} + +struct ast_variable *ast_load_realtime(const char *family, ...) +{ + struct ast_config_engine *eng; + char db[256]=""; + char table[256]=""; + struct ast_variable *res=NULL; + va_list ap; + + va_start(ap, family); + eng = find_engine(family, db, sizeof(db), table, sizeof(table)); + if (eng && eng->realtime_func) + res = eng->realtime_func(db, table, ap); + va_end(ap); + + return res; +} + +/*! \brief Check if realtime engine is configured for family */ +int ast_check_realtime(const char *family) +{ + struct ast_config_engine *eng; + + eng = find_engine(family, NULL, 0, NULL, 0); + if (eng) + return 1; + return 0; + +} + +struct ast_config *ast_load_realtime_multientry(const char *family, ...) +{ + struct ast_config_engine *eng; + char db[256]=""; + char table[256]=""; + struct ast_config *res=NULL; + va_list ap; + + va_start(ap, family); + eng = find_engine(family, db, sizeof(db), table, sizeof(table)); + if (eng && eng->realtime_multi_func) + res = eng->realtime_multi_func(db, table, ap); + va_end(ap); + + return res; +} + +int ast_update_realtime(const char *family, const char *keyfield, const char *lookup, ...) +{ + struct ast_config_engine *eng; + int res = -1; + char db[256]=""; + char table[256]=""; + va_list ap; + + va_start(ap, lookup); + eng = find_engine(family, db, sizeof(db), table, sizeof(table)); + if (eng && eng->update_func) + res = eng->update_func(db, table, keyfield, lookup, ap); + va_end(ap); + + return res; +} + +static int config_command(int fd, int argc, char **argv) +{ + struct ast_config_engine *eng; + struct ast_config_map *map; + + ast_mutex_lock(&config_lock); + + ast_cli(fd, "\n\n"); + for (eng = config_engine_list; eng; eng = eng->next) { + ast_cli(fd, "\nConfig Engine: %s\n", eng->name); + for (map = config_maps; map; map = map->next) + if (!strcasecmp(map->driver, eng->name)) { + ast_cli(fd, "===> %s (db=%s, table=%s)\n", map->name, map->database, + map->table ? map->table : map->name); + } + } + ast_cli(fd,"\n\n"); + + ast_mutex_unlock(&config_lock); + + return 0; +} + +static char show_config_help[] = + "Usage: show config mappings\n" + " Shows the filenames to config engines.\n"; + +static struct ast_cli_entry config_command_struct = { + { "show", "config", "mappings", NULL }, config_command, "Show Config mappings (file names to config engines)", show_config_help, NULL +}; + +int register_config_cli() +{ + return ast_cli_register(&config_command_struct); +} diff --git a/main/cryptostub.c b/main/cryptostub.c new file mode 100644 index 000000000..676110374 --- /dev/null +++ b/main/cryptostub.c @@ -0,0 +1,95 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Stubs for res_crypto routines + * + * \author Mark Spencer + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include + +#include "asterisk/crypto.h" +#include "asterisk/logger.h" + +/* Hrm, I wonder if the compiler is smart enough to only create two functions + for all these... I could force it to only make two, but those would be some + really nasty looking casts. */ + +static struct ast_key *stub_ast_key_get(const char *kname, int ktype) +{ + ast_log(LOG_NOTICE, "Crypto support not loaded!\n"); + return NULL; +} + +static int stub_ast_check_signature(struct ast_key *key, const char *msg, const char *sig) +{ + ast_log(LOG_NOTICE, "Crypto support not loaded!\n"); + return -1; +} + +static int stub_ast_check_signature_bin(struct ast_key *key, const char *msg, int msglen, const unsigned char *sig) +{ + ast_log(LOG_NOTICE, "Crypto support not loaded!\n"); + return -1; +} + +static int stub_ast_sign(struct ast_key *key, char *msg, char *sig) +{ + ast_log(LOG_NOTICE, "Crypto support not loaded!\n"); + return -1; +} + +static int stub_ast_sign_bin(struct ast_key *key, const char *msg, int msglen, unsigned char *sig) +{ + ast_log(LOG_NOTICE, "Crypto support not loaded!\n"); + return -1; +} + +static int stub_ast_encdec_bin(unsigned char *dst, const unsigned char *src, int srclen, struct ast_key *key) +{ + ast_log(LOG_NOTICE, "Crypto support not loaded!\n"); + return -1; +} + +struct ast_key *(*ast_key_get)(const char *key, int type) = + stub_ast_key_get; + +int (*ast_check_signature)(struct ast_key *key, const char *msg, const char *sig) = + stub_ast_check_signature; + +int (*ast_check_signature_bin)(struct ast_key *key, const char *msg, int msglen, const unsigned char *sig) = + stub_ast_check_signature_bin; + +int (*ast_sign)(struct ast_key *key, char *msg, char *sig) = + stub_ast_sign; + +int (*ast_sign_bin)(struct ast_key *key, const char *msg, int msglen, unsigned char *sig) = + stub_ast_sign_bin; + +int (*ast_encrypt_bin)(unsigned char *dst, const unsigned char *src, int srclen, struct ast_key *key) = + stub_ast_encdec_bin; + +int (*ast_decrypt_bin)(unsigned char *dst, const unsigned char *src, int srclen, struct ast_key *key) = + stub_ast_encdec_bin; diff --git a/main/db.c b/main/db.c new file mode 100644 index 000000000..e64e0f521 --- /dev/null +++ b/main/db.c @@ -0,0 +1,593 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief ASTdb Management + * + * \author Mark Spencer + * + * \note DB3 is licensed under Sleepycat Public License and is thus incompatible + * with GPL. To avoid having to make another exception (and complicate + * licensing even further) we elect to use DB1 which is BSD licensed + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/channel.h" +#include "asterisk/file.h" +#include "asterisk/app.h" +#include "asterisk/dsp.h" +#include "asterisk/logger.h" +#include "asterisk/options.h" +#include "asterisk/astdb.h" +#include "asterisk/cli.h" +#include "asterisk/utils.h" +#include "asterisk/lock.h" +#include "asterisk/manager.h" +#include "db1-ast/include/db.h" + +#ifdef __CYGWIN__ +#define dbopen __dbopen +#endif + +static DB *astdb; +AST_MUTEX_DEFINE_STATIC(dblock); + +static int dbinit(void) +{ + if (!astdb && !(astdb = dbopen((char *)ast_config_AST_DB, O_CREAT | O_RDWR, 0664, DB_BTREE, NULL))) { + ast_log(LOG_WARNING, "Unable to open Asterisk database\n"); + return -1; + } + return 0; +} + + +static inline int keymatch(const char *key, const char *prefix) +{ + int preflen = strlen(prefix); + if (!preflen) + return 1; + if (!strcasecmp(key, prefix)) + return 1; + if ((strlen(key) > preflen) && !strncasecmp(key, prefix, preflen)) { + if (key[preflen] == '/') + return 1; + } + return 0; +} + +static inline int subkeymatch(const char *key, const char *suffix) +{ + int suffixlen = strlen(suffix); + if (suffixlen) { + const char *subkey = key + strlen(key) - suffixlen; + if (subkey < key) + return 0; + if (!strcasecmp(subkey, suffix)) + return 1; + } + return 0; +} + +int ast_db_deltree(const char *family, const char *keytree) +{ + char prefix[256]; + DBT key, data; + char *keys; + int res; + int pass; + + if (family) { + if (keytree) { + snprintf(prefix, sizeof(prefix), "/%s/%s", family, keytree); + } else { + snprintf(prefix, sizeof(prefix), "/%s", family); + } + } else if (keytree) { + return -1; + } else { + prefix[0] = '\0'; + } + + ast_mutex_lock(&dblock); + if (dbinit()) { + ast_mutex_unlock(&dblock); + return -1; + } + + memset(&key, 0, sizeof(key)); + memset(&data, 0, sizeof(data)); + pass = 0; + while (!(res = astdb->seq(astdb, &key, &data, pass++ ? R_NEXT : R_FIRST))) { + if (key.size) { + keys = key.data; + keys[key.size - 1] = '\0'; + } else { + keys = ""; + } + if (keymatch(keys, prefix)) { + astdb->del(astdb, &key, 0); + } + } + astdb->sync(astdb, 0); + ast_mutex_unlock(&dblock); + return 0; +} + +int ast_db_put(const char *family, const char *keys, char *value) +{ + char fullkey[256]; + DBT key, data; + int res, fullkeylen; + + ast_mutex_lock(&dblock); + if (dbinit()) { + ast_mutex_unlock(&dblock); + return -1; + } + + fullkeylen = snprintf(fullkey, sizeof(fullkey), "/%s/%s", family, keys); + memset(&key, 0, sizeof(key)); + memset(&data, 0, sizeof(data)); + key.data = fullkey; + key.size = fullkeylen + 1; + data.data = value; + data.size = strlen(value) + 1; + res = astdb->put(astdb, &key, &data, 0); + astdb->sync(astdb, 0); + ast_mutex_unlock(&dblock); + if (res) + ast_log(LOG_WARNING, "Unable to put value '%s' for key '%s' in family '%s'\n", value, keys, family); + return res; +} + +int ast_db_get(const char *family, const char *keys, char *value, int valuelen) +{ + char fullkey[256] = ""; + DBT key, data; + int res, fullkeylen; + + ast_mutex_lock(&dblock); + if (dbinit()) { + ast_mutex_unlock(&dblock); + return -1; + } + + fullkeylen = snprintf(fullkey, sizeof(fullkey), "/%s/%s", family, keys); + memset(&key, 0, sizeof(key)); + memset(&data, 0, sizeof(data)); + memset(value, 0, valuelen); + key.data = fullkey; + key.size = fullkeylen + 1; + + res = astdb->get(astdb, &key, &data, 0); + + ast_mutex_unlock(&dblock); + + /* Be sure to NULL terminate our data either way */ + if (res) { + ast_log(LOG_DEBUG, "Unable to find key '%s' in family '%s'\n", keys, family); + } else { +#if 0 + printf("Got value of size %d\n", data.size); +#endif + if (data.size) { + ((char *)data.data)[data.size - 1] = '\0'; + /* Make sure that we don't write too much to the dst pointer or we don't read too much from the source pointer */ + strncpy(value, data.data, (valuelen > data.size) ? data.size : valuelen); + value[valuelen - 1] = '\0'; + } else { + ast_log(LOG_NOTICE, "Strange, empty value for /%s/%s\n", family, keys); + } + } + return res; +} + +int ast_db_del(const char *family, const char *keys) +{ + char fullkey[256]; + DBT key; + int res, fullkeylen; + + ast_mutex_lock(&dblock); + if (dbinit()) { + ast_mutex_unlock(&dblock); + return -1; + } + + fullkeylen = snprintf(fullkey, sizeof(fullkey), "/%s/%s", family, keys); + memset(&key, 0, sizeof(key)); + key.data = fullkey; + key.size = fullkeylen + 1; + + res = astdb->del(astdb, &key, 0); + astdb->sync(astdb, 0); + + ast_mutex_unlock(&dblock); + + if (res) + ast_log(LOG_DEBUG, "Unable to find key '%s' in family '%s'\n", keys, family); + return res; +} + +static int database_put(int fd, int argc, char *argv[]) +{ + int res; + if (argc != 5) + return RESULT_SHOWUSAGE; + res = ast_db_put(argv[2], argv[3], argv[4]); + if (res) { + ast_cli(fd, "Failed to update entry\n"); + } else { + ast_cli(fd, "Updated database successfully\n"); + } + return RESULT_SUCCESS; +} + +static int database_get(int fd, int argc, char *argv[]) +{ + int res; + char tmp[256]; + if (argc != 4) + return RESULT_SHOWUSAGE; + res = ast_db_get(argv[2], argv[3], tmp, sizeof(tmp)); + if (res) { + ast_cli(fd, "Database entry not found.\n"); + } else { + ast_cli(fd, "Value: %s\n", tmp); + } + return RESULT_SUCCESS; +} + +static int database_del(int fd, int argc, char *argv[]) +{ + int res; + if (argc != 4) + return RESULT_SHOWUSAGE; + res = ast_db_del(argv[2], argv[3]); + if (res) { + ast_cli(fd, "Database entry does not exist.\n"); + } else { + ast_cli(fd, "Database entry removed.\n"); + } + return RESULT_SUCCESS; +} + +static int database_deltree(int fd, int argc, char *argv[]) +{ + int res; + if ((argc < 3) || (argc > 4)) + return RESULT_SHOWUSAGE; + if (argc == 4) { + res = ast_db_deltree(argv[2], argv[3]); + } else { + res = ast_db_deltree(argv[2], NULL); + } + if (res) { + ast_cli(fd, "Database entries do not exist.\n"); + } else { + ast_cli(fd, "Database entries removed.\n"); + } + return RESULT_SUCCESS; +} + +static int database_show(int fd, int argc, char *argv[]) +{ + char prefix[256]; + DBT key, data; + char *keys, *values; + int res; + int pass; + + if (argc == 4) { + /* Family and key tree */ + snprintf(prefix, sizeof(prefix), "/%s/%s", argv[2], argv[3]); + } else if (argc == 3) { + /* Family only */ + snprintf(prefix, sizeof(prefix), "/%s", argv[2]); + } else if (argc == 2) { + /* Neither */ + prefix[0] = '\0'; + } else { + return RESULT_SHOWUSAGE; + } + ast_mutex_lock(&dblock); + if (dbinit()) { + ast_mutex_unlock(&dblock); + ast_cli(fd, "Database unavailable\n"); + return RESULT_SUCCESS; + } + memset(&key, 0, sizeof(key)); + memset(&data, 0, sizeof(data)); + pass = 0; + while (!(res = astdb->seq(astdb, &key, &data, pass++ ? R_NEXT : R_FIRST))) { + if (key.size) { + keys = key.data; + keys[key.size - 1] = '\0'; + } else { + keys = ""; + } + if (data.size) { + values = data.data; + values[data.size - 1]='\0'; + } else { + values = ""; + } + if (keymatch(keys, prefix)) { + ast_cli(fd, "%-50s: %-25s\n", keys, values); + } + } + ast_mutex_unlock(&dblock); + return RESULT_SUCCESS; +} + +static int database_showkey(int fd, int argc, char *argv[]) +{ + char suffix[256]; + DBT key, data; + char *keys, *values; + int res; + int pass; + + if (argc == 3) { + /* Key only */ + snprintf(suffix, sizeof(suffix), "/%s", argv[2]); + } else { + return RESULT_SHOWUSAGE; + } + ast_mutex_lock(&dblock); + if (dbinit()) { + ast_mutex_unlock(&dblock); + ast_cli(fd, "Database unavailable\n"); + return RESULT_SUCCESS; + } + memset(&key, 0, sizeof(key)); + memset(&data, 0, sizeof(data)); + pass = 0; + while (!(res = astdb->seq(astdb, &key, &data, pass++ ? R_NEXT : R_FIRST))) { + if (key.size) { + keys = key.data; + keys[key.size - 1] = '\0'; + } else { + keys = ""; + } + if (data.size) { + values = data.data; + values[data.size - 1]='\0'; + } else { + values = ""; + } + if (subkeymatch(keys, suffix)) { + ast_cli(fd, "%-50s: %-25s\n", keys, values); + } + } + ast_mutex_unlock(&dblock); + return RESULT_SUCCESS; +} + +struct ast_db_entry *ast_db_gettree(const char *family, const char *keytree) +{ + char prefix[256]; + DBT key, data; + char *keys, *values; + int values_len; + int res; + int pass; + struct ast_db_entry *last = NULL; + struct ast_db_entry *cur, *ret=NULL; + + if (!ast_strlen_zero(family)) { + if (!ast_strlen_zero(keytree)) { + /* Family and key tree */ + snprintf(prefix, sizeof(prefix), "/%s/%s", family, prefix); + } else { + /* Family only */ + snprintf(prefix, sizeof(prefix), "/%s", family); + } + } else { + prefix[0] = '\0'; + } + ast_mutex_lock(&dblock); + if (dbinit()) { + ast_mutex_unlock(&dblock); + ast_log(LOG_WARNING, "Database unavailable\n"); + return NULL; + } + memset(&key, 0, sizeof(key)); + memset(&data, 0, sizeof(data)); + pass = 0; + while (!(res = astdb->seq(astdb, &key, &data, pass++ ? R_NEXT : R_FIRST))) { + if (key.size) { + keys = key.data; + keys[key.size - 1] = '\0'; + } else { + keys = ""; + } + if (data.size) { + values = data.data; + values[data.size - 1] = '\0'; + } else { + values = ""; + } + values_len = strlen(values) + 1; + if (keymatch(keys, prefix) && (cur = ast_malloc(sizeof(*cur) + strlen(keys) + 1 + values_len))) { + cur->next = NULL; + cur->key = cur->data + values_len; + strcpy(cur->data, values); + strcpy(cur->key, keys); + if (last) { + last->next = cur; + } else { + ret = cur; + } + last = cur; + } + } + ast_mutex_unlock(&dblock); + return ret; +} + +void ast_db_freetree(struct ast_db_entry *dbe) +{ + struct ast_db_entry *last; + while (dbe) { + last = dbe; + dbe = dbe->next; + free(last); + } +} + +static char database_show_usage[] = +"Usage: database show [family [keytree]]\n" +" Shows Asterisk database contents, optionally restricted\n" +"to a given family, or family and keytree.\n"; + +static char database_showkey_usage[] = +"Usage: database showkey \n" +" Shows Asterisk database contents, restricted to a given key.\n"; + +static char database_put_usage[] = +"Usage: database put \n" +" Adds or updates an entry in the Asterisk database for\n" +"a given family, key, and value.\n"; + +static char database_get_usage[] = +"Usage: database get \n" +" Retrieves an entry in the Asterisk database for a given\n" +"family and key.\n"; + +static char database_del_usage[] = +"Usage: database del \n" +" Deletes an entry in the Asterisk database for a given\n" +"family and key.\n"; + +static char database_deltree_usage[] = +"Usage: database deltree [keytree]\n" +" Deletes a family or specific keytree within a family\n" +"in the Asterisk database.\n"; + +struct ast_cli_entry cli_database_show = +{ { "database", "show", NULL }, database_show, "Shows database contents", database_show_usage }; + +struct ast_cli_entry cli_database_showkey = +{ { "database", "showkey", NULL }, database_showkey, "Shows database contents", database_showkey_usage }; + +struct ast_cli_entry cli_database_get = +{ { "database", "get", NULL }, database_get, "Gets database value", database_get_usage }; + +struct ast_cli_entry cli_database_put = +{ { "database", "put", NULL }, database_put, "Adds/updates database value", database_put_usage }; + +struct ast_cli_entry cli_database_del = +{ { "database", "del", NULL }, database_del, "Removes database key/value", database_del_usage }; + +struct ast_cli_entry cli_database_deltree = +{ { "database", "deltree", NULL }, database_deltree, "Removes database keytree/values", database_deltree_usage }; + +static int manager_dbput(struct mansession *s, struct message *m) +{ + char *family = astman_get_header(m, "Family"); + char *key = astman_get_header(m, "Key"); + char *val = astman_get_header(m, "Val"); + int res; + + if (ast_strlen_zero(family)) { + astman_send_error(s, m, "No family specified"); + return 0; + } + if (ast_strlen_zero(key)) { + astman_send_error(s, m, "No key specified"); + return 0; + } + if (ast_strlen_zero(val)) { + astman_send_error(s, m, "No val specified"); + return 0; + } + + res = ast_db_put(family, key, val); + if (res) { + astman_send_error(s, m, "Failed to update entry"); + } else { + astman_send_ack(s, m, "Updated database successfully"); + } + return 0; +} + +static int manager_dbget(struct mansession *s, struct message *m) +{ + char *id = astman_get_header(m,"ActionID"); + char idText[256] = ""; + char *family = astman_get_header(m, "Family"); + char *key = astman_get_header(m, "Key"); + char tmp[256]; + int res; + + if (ast_strlen_zero(family)) { + astman_send_error(s, m, "No family specified."); + return 0; + } + if (ast_strlen_zero(key)) { + astman_send_error(s, m, "No key specified."); + return 0; + } + + if (!ast_strlen_zero(id)) + snprintf(idText, sizeof(idText) ,"ActionID: %s\r\n", id); + + res = ast_db_get(family, key, tmp, sizeof(tmp)); + if (res) { + astman_send_error(s, m, "Database entry not found"); + } else { + astman_send_ack(s, m, "Result will follow"); + astman_append(s, "Event: DBGetResponse\r\n" + "Family: %s\r\n" + "Key: %s\r\n" + "Val: %s\r\n" + "%s" + "\r\n", + family, key, tmp, idText); + } + return 0; +} + +int astdb_init(void) +{ + dbinit(); + ast_cli_register(&cli_database_show); + ast_cli_register(&cli_database_showkey); + ast_cli_register(&cli_database_get); + ast_cli_register(&cli_database_put); + ast_cli_register(&cli_database_del); + ast_cli_register(&cli_database_deltree); + ast_manager_register("DBGet", EVENT_FLAG_SYSTEM, manager_dbget, "Get DB Entry"); + ast_manager_register("DBPut", EVENT_FLAG_SYSTEM, manager_dbput, "Put DB Entry"); + return 0; +} diff --git a/main/db1-ast/Makefile b/main/db1-ast/Makefile new file mode 100644 index 000000000..5a9f29cde --- /dev/null +++ b/main/db1-ast/Makefile @@ -0,0 +1,73 @@ +# @(#)Makefile 8.9 (Berkeley) 7/14/94 + +LIBDB= libdb1.a +ARCH=$(shell uname -m) +ifeq ($(ARCH),alpha) +SOVER=2.1 +else +SOVER=2 +endif +#Added support for UltraSparc - Belgarath +ifeq ($(ARCH),sparc64) +PROC=ultrasparc +CFLAGS += -mtune=$(PROC) -pipe -fomit-frame-pointer -mcpu=v8 +endif + +ifeq ($(OSARCH),Darwin) + OSARCH_DEFINE+=-D__Darwin__ +endif + +LIBDBSO=libdb.so.$(SOVER) +PROG= db_dump185 +OBJ1= hash/hash.o hash/hash_bigkey.o hash/hash_buf.o hash/hash_func.o hash/hash_log2.o hash/hash_page.o \ + hash/ndbm.o +OBJ2= btree/bt_close.o btree/bt_conv.o btree/bt_debug.o btree/bt_delete.o btree/bt_get.o btree/bt_open.o \ + btree/bt_overflow.o btree/bt_page.o btree/bt_put.o btree/bt_search.o btree/bt_seq.o btree/bt_split.o \ + btree/bt_utils.o +OBJ3= db/db.o +OBJ4= mpool/mpool.o +OBJ5= recno/rec_close.o recno/rec_delete.o recno/rec_get.o recno/rec_open.o recno/rec_put.o recno/rec_search.o \ + recno/rec_seq.o recno/rec_utils.o +MISC= +OBJS= $(OBJ1) $(OBJ2) $(OBJ3) $(OBJ4) $(OBJ5) $(MISC) +SHOBJS= $(patsubst %.o,%.os,$(OBJS)) + +include $(ASTTOPDIR)/Makefile.rules + +all: $(LIBDB) #$(LIBDBSO) $(PROG) + +$(eval $(call ast_make_a_o,$(LIBDB),$(OBJS))) + +$(LIBDBSO): $(SHOBJS) + $(CC) -Wl,-O1 -Wl,--version-script=libdb.map -Wl,-soname=$(LIBDBSO) -shared -o $@ $^ + ln -sf $@ libdb.so + +$(PROG): db_dump185.o $(LIBDBSO) + $(CC) -o $@ db_dump185.o -L. -ldb + +clean-depend: + +clean: + rm -f $(LIBDB) $(LIBDBSO) $(OBJS) $(SHOBJS) + +CFLAGS+=-Wall -D__DBINTERFACE_PRIVATE -I. -I.. -Iinclude -Ihash -Ibtree -Irecno + +OSTYPE=$(shell uname -s) +ifeq ($(OSTYPE),SunOS) +CFLAGS+=-I../include -I../include/solaris-compat -DSOLARIS +endif + +db_dump185.o: db_dump185.c + $(CL) -o $@ $< +x%.o: hash/%.c + $(CL) -Ihash $(OSARCH_DEFINE) -o $@ $< +%.os: hash/%.c + $(CL) -Ihash -fPIC -o $@ $< +x%.o: btree/%.c + $(CL) -Ibtree -o $@ $< +%.os: btree/%.c + $(CL) -Ibtree -fPIC -o $@ $< +x%.o: recno/%.c + $(CL) -Irecno -o $@ $< +%.os: recno/%.c + $(CL) -Irecno -fPIC -o $@ $< diff --git a/main/db1-ast/btree/bt_close.c b/main/db1-ast/btree/bt_close.c new file mode 100644 index 000000000..27f9ab660 --- /dev/null +++ b/main/db1-ast/btree/bt_close.c @@ -0,0 +1,182 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Olson. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)bt_close.c 8.7 (Berkeley) 8/17/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include +#include +#include +#include +#include + +#include +#include "btree.h" + +static int bt_meta __P((BTREE *)); + +/* + * BT_CLOSE -- Close a btree. + * + * Parameters: + * dbp: pointer to access method + * + * Returns: + * RET_ERROR, RET_SUCCESS + */ +int +__bt_close(dbp) + DB *dbp; +{ + BTREE *t; + int fd; + + t = dbp->internal; + + /* Toss any page pinned across calls. */ + if (t->bt_pinned != NULL) { + mpool_put(t->bt_mp, t->bt_pinned, 0); + t->bt_pinned = NULL; + } + + /* Sync the tree. */ + if (__bt_sync(dbp, 0) == RET_ERROR) + return (RET_ERROR); + + /* Close the memory pool. */ + if (mpool_close(t->bt_mp) == RET_ERROR) + return (RET_ERROR); + + /* Free random memory. */ + if (t->bt_cursor.key.data != NULL) { + free(t->bt_cursor.key.data); + t->bt_cursor.key.size = 0; + t->bt_cursor.key.data = NULL; + } + if (t->bt_rkey.data) { + free(t->bt_rkey.data); + t->bt_rkey.size = 0; + t->bt_rkey.data = NULL; + } + if (t->bt_rdata.data) { + free(t->bt_rdata.data); + t->bt_rdata.size = 0; + t->bt_rdata.data = NULL; + } + + fd = t->bt_fd; + free(t); + free(dbp); + return (close(fd) ? RET_ERROR : RET_SUCCESS); +} + +/* + * BT_SYNC -- sync the btree to disk. + * + * Parameters: + * dbp: pointer to access method + * + * Returns: + * RET_SUCCESS, RET_ERROR. + */ +int +__bt_sync(dbp, flags) + const DB *dbp; + u_int flags; +{ + BTREE *t; + int status; + + t = dbp->internal; + + /* Toss any page pinned across calls. */ + if (t->bt_pinned != NULL) { + mpool_put(t->bt_mp, t->bt_pinned, 0); + t->bt_pinned = NULL; + } + + /* Sync doesn't currently take any flags. */ + if (flags != 0) { + errno = EINVAL; + return (RET_ERROR); + } + + if (F_ISSET(t, B_INMEM | B_RDONLY) || !F_ISSET(t, B_MODIFIED)) + return (RET_SUCCESS); + + if (F_ISSET(t, B_METADIRTY) && bt_meta(t) == RET_ERROR) + return (RET_ERROR); + + if ((status = mpool_sync(t->bt_mp)) == RET_SUCCESS) + F_CLR(t, B_MODIFIED); + + return (status); +} + +/* + * BT_META -- write the tree meta data to disk. + * + * Parameters: + * t: tree + * + * Returns: + * RET_ERROR, RET_SUCCESS + */ +static int +bt_meta(t) + BTREE *t; +{ + BTMETA m; + void *p; + + if ((p = mpool_get(t->bt_mp, P_META, 0)) == NULL) + return (RET_ERROR); + + /* Fill in metadata. */ + m.magic = BTREEMAGIC; + m.version = BTREEVERSION; + m.psize = t->bt_psize; + m.free = t->bt_free; + m.nrecs = t->bt_nrecs; + m.flags = F_ISSET(t, SAVEMETA); + + memmove(p, &m, sizeof(BTMETA)); + mpool_put(t->bt_mp, p, MPOOL_DIRTY); + return (RET_SUCCESS); +} diff --git a/main/db1-ast/btree/bt_conv.c b/main/db1-ast/btree/bt_conv.c new file mode 100644 index 000000000..1cb208b14 --- /dev/null +++ b/main/db1-ast/btree/bt_conv.c @@ -0,0 +1,221 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Olson. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)bt_conv.c 8.5 (Berkeley) 8/17/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include + +#include +#include "btree.h" + +static void mswap __P((PAGE *)); + +/* + * __BT_BPGIN, __BT_BPGOUT -- + * Convert host-specific number layout to/from the host-independent + * format stored on disk. + * + * Parameters: + * t: tree + * pg: page number + * h: page to convert + */ +void +__bt_pgin(t, pg, pp) + void *t; + pgno_t pg; + void *pp; +{ + PAGE *h; + indx_t i, top; + u_char flags; + char *p; + + if (!F_ISSET(((BTREE *)t), B_NEEDSWAP)) + return; + if (pg == P_META) { + mswap(pp); + return; + } + + h = pp; + M_32_SWAP(h->pgno); + M_32_SWAP(h->prevpg); + M_32_SWAP(h->nextpg); + M_32_SWAP(h->flags); + M_16_SWAP(h->lower); + M_16_SWAP(h->upper); + + top = NEXTINDEX(h); + if ((h->flags & P_TYPE) == P_BINTERNAL) + for (i = 0; i < top; i++) { + M_16_SWAP(h->linp[i]); + p = (char *)GETBINTERNAL(h, i); + P_32_SWAP(p); + p += sizeof(u_int32_t); + P_32_SWAP(p); + p += sizeof(pgno_t); + if (*(u_char *)p & P_BIGKEY) { + p += sizeof(u_char); + P_32_SWAP(p); + p += sizeof(pgno_t); + P_32_SWAP(p); + } + } + else if ((h->flags & P_TYPE) == P_BLEAF) + for (i = 0; i < top; i++) { + M_16_SWAP(h->linp[i]); + p = (char *)GETBLEAF(h, i); + P_32_SWAP(p); + p += sizeof(u_int32_t); + P_32_SWAP(p); + p += sizeof(u_int32_t); + flags = *(u_char *)p; + if (flags & (P_BIGKEY | P_BIGDATA)) { + p += sizeof(u_char); + if (flags & P_BIGKEY) { + P_32_SWAP(p); + p += sizeof(pgno_t); + P_32_SWAP(p); + } + if (flags & P_BIGDATA) { + p += sizeof(u_int32_t); + P_32_SWAP(p); + p += sizeof(pgno_t); + P_32_SWAP(p); + } + } + } +} + +void +__bt_pgout(t, pg, pp) + void *t; + pgno_t pg; + void *pp; +{ + PAGE *h; + indx_t i, top; + u_char flags; + char *p; + + if (!F_ISSET(((BTREE *)t), B_NEEDSWAP)) + return; + if (pg == P_META) { + mswap(pp); + return; + } + + h = pp; + top = NEXTINDEX(h); + if ((h->flags & P_TYPE) == P_BINTERNAL) + for (i = 0; i < top; i++) { + p = (char *)GETBINTERNAL(h, i); + P_32_SWAP(p); + p += sizeof(u_int32_t); + P_32_SWAP(p); + p += sizeof(pgno_t); + if (*(u_char *)p & P_BIGKEY) { + p += sizeof(u_char); + P_32_SWAP(p); + p += sizeof(pgno_t); + P_32_SWAP(p); + } + M_16_SWAP(h->linp[i]); + } + else if ((h->flags & P_TYPE) == P_BLEAF) + for (i = 0; i < top; i++) { + p = (char *)GETBLEAF(h, i); + P_32_SWAP(p); + p += sizeof(u_int32_t); + P_32_SWAP(p); + p += sizeof(u_int32_t); + flags = *(u_char *)p; + if (flags & (P_BIGKEY | P_BIGDATA)) { + p += sizeof(u_char); + if (flags & P_BIGKEY) { + P_32_SWAP(p); + p += sizeof(pgno_t); + P_32_SWAP(p); + } + if (flags & P_BIGDATA) { + p += sizeof(u_int32_t); + P_32_SWAP(p); + p += sizeof(pgno_t); + P_32_SWAP(p); + } + } + M_16_SWAP(h->linp[i]); + } + + M_32_SWAP(h->pgno); + M_32_SWAP(h->prevpg); + M_32_SWAP(h->nextpg); + M_32_SWAP(h->flags); + M_16_SWAP(h->lower); + M_16_SWAP(h->upper); +} + +/* + * MSWAP -- Actually swap the bytes on the meta page. + * + * Parameters: + * p: page to convert + */ +static void +mswap(pg) + PAGE *pg; +{ + char *p; + + p = (char *)pg; + P_32_SWAP(p); /* magic */ + p += sizeof(u_int32_t); + P_32_SWAP(p); /* version */ + p += sizeof(u_int32_t); + P_32_SWAP(p); /* psize */ + p += sizeof(u_int32_t); + P_32_SWAP(p); /* free */ + p += sizeof(u_int32_t); + P_32_SWAP(p); /* nrecs */ + p += sizeof(u_int32_t); + P_32_SWAP(p); /* flags */ + p += sizeof(u_int32_t); +} diff --git a/main/db1-ast/btree/bt_debug.c b/main/db1-ast/btree/bt_debug.c new file mode 100644 index 000000000..443f2bf99 --- /dev/null +++ b/main/db1-ast/btree/bt_debug.c @@ -0,0 +1,329 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Olson. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)bt_debug.c 8.5 (Berkeley) 8/17/94"; +#endif /* LIBC_SCCS and not lint */ + +#ifdef DEBUG +#include + +#include +#include +#include + +#include +#include "btree.h" + +/* + * BT_DUMP -- Dump the tree + * + * Parameters: + * dbp: pointer to the DB + */ +void +__bt_dump(dbp) + DB *dbp; +{ + BTREE *t; + PAGE *h; + pgno_t i; + char *sep; + + t = dbp->internal; + (void)fprintf(stderr, "%s: pgsz %d", + F_ISSET(t, B_INMEM) ? "memory" : "disk", t->bt_psize); + if (F_ISSET(t, R_RECNO)) + (void)fprintf(stderr, " keys %lu", t->bt_nrecs); +#undef X +#define X(flag, name) \ + if (F_ISSET(t, flag)) { \ + (void)fprintf(stderr, "%s%s", sep, name); \ + sep = ", "; \ + } + if (t->flags != 0) { + sep = " flags ("; + X(R_FIXLEN, "FIXLEN"); + X(B_INMEM, "INMEM"); + X(B_NODUPS, "NODUPS"); + X(B_RDONLY, "RDONLY"); + X(R_RECNO, "RECNO"); + X(B_METADIRTY,"METADIRTY"); + (void)fprintf(stderr, ")\n"); + } +#undef X + + for (i = P_ROOT; (h = mpool_get(t->bt_mp, i, 0)) != NULL; ++i) { + __bt_dpage(h); + (void)mpool_put(t->bt_mp, h, 0); + } +} + +/* + * BT_DMPAGE -- Dump the meta page + * + * Parameters: + * h: pointer to the PAGE + */ +void +__bt_dmpage(h) + PAGE *h; +{ + BTMETA *m; + char *sep; + + m = (BTMETA *)h; + (void)fprintf(stderr, "magic %lx\n", m->magic); + (void)fprintf(stderr, "version %lu\n", m->version); + (void)fprintf(stderr, "psize %lu\n", m->psize); + (void)fprintf(stderr, "free %lu\n", m->free); + (void)fprintf(stderr, "nrecs %lu\n", m->nrecs); + (void)fprintf(stderr, "flags %lu", m->flags); +#undef X +#define X(flag, name) \ + if (m->flags & flag) { \ + (void)fprintf(stderr, "%s%s", sep, name); \ + sep = ", "; \ + } + if (m->flags) { + sep = " ("; + X(B_NODUPS, "NODUPS"); + X(R_RECNO, "RECNO"); + (void)fprintf(stderr, ")"); + } +} + +/* + * BT_DNPAGE -- Dump the page + * + * Parameters: + * n: page number to dump. + */ +void +__bt_dnpage(dbp, pgno) + DB *dbp; + pgno_t pgno; +{ + BTREE *t; + PAGE *h; + + t = dbp->internal; + if ((h = mpool_get(t->bt_mp, pgno, 0)) != NULL) { + __bt_dpage(h); + (void)mpool_put(t->bt_mp, h, 0); + } +} + +/* + * BT_DPAGE -- Dump the page + * + * Parameters: + * h: pointer to the PAGE + */ +void +__bt_dpage(h) + PAGE *h; +{ + BINTERNAL *bi; + BLEAF *bl; + RINTERNAL *ri; + RLEAF *rl; + indx_t cur, top; + char *sep; + + (void)fprintf(stderr, " page %d: (", h->pgno); +#undef X +#define X(flag, name) \ + if (h->flags & flag) { \ + (void)fprintf(stderr, "%s%s", sep, name); \ + sep = ", "; \ + } + sep = ""; + X(P_BINTERNAL, "BINTERNAL") /* types */ + X(P_BLEAF, "BLEAF") + X(P_RINTERNAL, "RINTERNAL") /* types */ + X(P_RLEAF, "RLEAF") + X(P_OVERFLOW, "OVERFLOW") + X(P_PRESERVE, "PRESERVE"); + (void)fprintf(stderr, ")\n"); +#undef X + + (void)fprintf(stderr, "\tprev %2d next %2d", h->prevpg, h->nextpg); + if (h->flags & P_OVERFLOW) + return; + + top = NEXTINDEX(h); + (void)fprintf(stderr, " lower %3d upper %3d nextind %d\n", + h->lower, h->upper, top); + for (cur = 0; cur < top; cur++) { + (void)fprintf(stderr, "\t[%03d] %4d ", cur, h->linp[cur]); + switch (h->flags & P_TYPE) { + case P_BINTERNAL: + bi = GETBINTERNAL(h, cur); + (void)fprintf(stderr, + "size %03d pgno %03d", bi->ksize, bi->pgno); + if (bi->flags & P_BIGKEY) + (void)fprintf(stderr, " (indirect)"); + else if (bi->ksize) + (void)fprintf(stderr, + " {%.*s}", (int)bi->ksize, bi->bytes); + break; + case P_RINTERNAL: + ri = GETRINTERNAL(h, cur); + (void)fprintf(stderr, "entries %03d pgno %03d", + ri->nrecs, ri->pgno); + break; + case P_BLEAF: + bl = GETBLEAF(h, cur); + if (bl->flags & P_BIGKEY) + (void)fprintf(stderr, + "big key page %lu size %u/", + *(pgno_t *)bl->bytes, + *(u_int32_t *)(bl->bytes + sizeof(pgno_t))); + else if (bl->ksize) + (void)fprintf(stderr, "%s/", bl->bytes); + if (bl->flags & P_BIGDATA) + (void)fprintf(stderr, + "big data page %lu size %u", + *(pgno_t *)(bl->bytes + bl->ksize), + *(u_int32_t *)(bl->bytes + bl->ksize + + sizeof(pgno_t))); + else if (bl->dsize) + (void)fprintf(stderr, "%.*s", + (int)bl->dsize, bl->bytes + bl->ksize); + break; + case P_RLEAF: + rl = GETRLEAF(h, cur); + if (rl->flags & P_BIGDATA) + (void)fprintf(stderr, + "big data page %lu size %u", + *(pgno_t *)rl->bytes, + *(u_int32_t *)(rl->bytes + sizeof(pgno_t))); + else if (rl->dsize) + (void)fprintf(stderr, + "%.*s", (int)rl->dsize, rl->bytes); + break; + } + (void)fprintf(stderr, "\n"); + } +} +#endif + +#ifdef STATISTICS +/* + * BT_STAT -- Gather/print the tree statistics + * + * Parameters: + * dbp: pointer to the DB + */ +void +__bt_stat(dbp) + DB *dbp; +{ + extern u_long bt_cache_hit, bt_cache_miss, bt_pfxsaved, bt_rootsplit; + extern u_long bt_sortsplit, bt_split; + BTREE *t; + PAGE *h; + pgno_t i, pcont, pinternal, pleaf; + u_long ifree, lfree, nkeys; + int levels; + + t = dbp->internal; + pcont = pinternal = pleaf = 0; + nkeys = ifree = lfree = 0; + for (i = P_ROOT; (h = mpool_get(t->bt_mp, i, 0)) != NULL; ++i) { + switch (h->flags & P_TYPE) { + case P_BINTERNAL: + case P_RINTERNAL: + ++pinternal; + ifree += h->upper - h->lower; + break; + case P_BLEAF: + case P_RLEAF: + ++pleaf; + lfree += h->upper - h->lower; + nkeys += NEXTINDEX(h); + break; + case P_OVERFLOW: + ++pcont; + break; + } + (void)mpool_put(t->bt_mp, h, 0); + } + + /* Count the levels of the tree. */ + for (i = P_ROOT, levels = 0 ;; ++levels) { + h = mpool_get(t->bt_mp, i, 0); + if (h->flags & (P_BLEAF|P_RLEAF)) { + if (levels == 0) + levels = 1; + (void)mpool_put(t->bt_mp, h, 0); + break; + } + i = F_ISSET(t, R_RECNO) ? + GETRINTERNAL(h, 0)->pgno : + GETBINTERNAL(h, 0)->pgno; + (void)mpool_put(t->bt_mp, h, 0); + } + + (void)fprintf(stderr, "%d level%s with %ld keys", + levels, levels == 1 ? "" : "s", nkeys); + if (F_ISSET(t, R_RECNO)) + (void)fprintf(stderr, " (%ld header count)", t->bt_nrecs); + (void)fprintf(stderr, + "\n%lu pages (leaf %ld, internal %ld, overflow %ld)\n", + pinternal + pleaf + pcont, pleaf, pinternal, pcont); + (void)fprintf(stderr, "%ld cache hits, %ld cache misses\n", + bt_cache_hit, bt_cache_miss); + (void)fprintf(stderr, "%ld splits (%ld root splits, %ld sort splits)\n", + bt_split, bt_rootsplit, bt_sortsplit); + pleaf *= t->bt_psize - BTDATAOFF; + if (pleaf) + (void)fprintf(stderr, + "%.0f%% leaf fill (%ld bytes used, %ld bytes free)\n", + ((double)(pleaf - lfree) / pleaf) * 100, + pleaf - lfree, lfree); + pinternal *= t->bt_psize - BTDATAOFF; + if (pinternal) + (void)fprintf(stderr, + "%.0f%% internal fill (%ld bytes used, %ld bytes free\n", + ((double)(pinternal - ifree) / pinternal) * 100, + pinternal - ifree, ifree); + if (bt_pfxsaved) + (void)fprintf(stderr, "prefix checking removed %lu bytes.\n", + bt_pfxsaved); +} +#endif diff --git a/main/db1-ast/btree/bt_delete.c b/main/db1-ast/btree/bt_delete.c new file mode 100644 index 000000000..9908a7c3e --- /dev/null +++ b/main/db1-ast/btree/bt_delete.c @@ -0,0 +1,657 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Olson. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)bt_delete.c 8.13 (Berkeley) 7/28/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include +#include +#include + +#include +#include "btree.h" + +static int __bt_bdelete __P((BTREE *, const DBT *)); +static int __bt_curdel __P((BTREE *, const DBT *, PAGE *, u_int)); +static int __bt_pdelete __P((BTREE *, PAGE *)); +static int __bt_relink __P((BTREE *, PAGE *)); +static int __bt_stkacq __P((BTREE *, PAGE **, CURSOR *)); + +/* + * __bt_delete + * Delete the item(s) referenced by a key. + * + * Return RET_SPECIAL if the key is not found. + */ +int +__bt_delete(dbp, key, flags) + const DB *dbp; + const DBT *key; + u_int flags; +{ + BTREE *t; + CURSOR *c; + PAGE *h; + int status; + + t = dbp->internal; + + /* Toss any page pinned across calls. */ + if (t->bt_pinned != NULL) { + mpool_put(t->bt_mp, t->bt_pinned, 0); + t->bt_pinned = NULL; + } + + /* Check for change to a read-only tree. */ + if (F_ISSET(t, B_RDONLY)) { + errno = EPERM; + return (RET_ERROR); + } + + switch (flags) { + case 0: + status = __bt_bdelete(t, key); + break; + case R_CURSOR: + /* + * If flags is R_CURSOR, delete the cursor. Must already + * have started a scan and not have already deleted it. + */ + c = &t->bt_cursor; + if (F_ISSET(c, CURS_INIT)) { + if (F_ISSET(c, CURS_ACQUIRE | CURS_AFTER | CURS_BEFORE)) + return (RET_SPECIAL); + if ((h = mpool_get(t->bt_mp, c->pg.pgno, 0)) == NULL) + return (RET_ERROR); + + /* + * If the page is about to be emptied, we'll need to + * delete it, which means we have to acquire a stack. + */ + if (NEXTINDEX(h) == 1) + if (__bt_stkacq(t, &h, &t->bt_cursor)) + return (RET_ERROR); + + status = __bt_dleaf(t, NULL, h, c->pg.index); + + if (NEXTINDEX(h) == 0 && status == RET_SUCCESS) { + if (__bt_pdelete(t, h)) + return (RET_ERROR); + } else + mpool_put(t->bt_mp, + h, status == RET_SUCCESS ? MPOOL_DIRTY : 0); + break; + } + /* FALLTHROUGH */ + default: + errno = EINVAL; + return (RET_ERROR); + } + if (status == RET_SUCCESS) + F_SET(t, B_MODIFIED); + return (status); +} + +/* + * __bt_stkacq -- + * Acquire a stack so we can delete a cursor entry. + * + * Parameters: + * t: tree + * hp: pointer to current, pinned PAGE pointer + * c: pointer to the cursor + * + * Returns: + * 0 on success, 1 on failure + */ +static int +__bt_stkacq(t, hp, c) + BTREE *t; + PAGE **hp; + CURSOR *c; +{ + BINTERNAL *bi; + EPG *e; + EPGNO *parent; + PAGE *h; + indx_t index = 0; + pgno_t pgno; + recno_t nextpg, prevpg; + int exact, level; + + /* + * Find the first occurrence of the key in the tree. Toss the + * currently locked page so we don't hit an already-locked page. + */ + h = *hp; + mpool_put(t->bt_mp, h, 0); + if ((e = __bt_search(t, &c->key, &exact)) == NULL) + return (1); + h = e->page; + + /* See if we got it in one shot. */ + if (h->pgno == c->pg.pgno) + goto ret; + + /* + * Move right, looking for the page. At each move we have to move + * up the stack until we don't have to move to the next page. If + * we have to change pages at an internal level, we have to fix the + * stack back up. + */ + while (h->pgno != c->pg.pgno) { + if ((nextpg = h->nextpg) == P_INVALID) + break; + mpool_put(t->bt_mp, h, 0); + + /* Move up the stack. */ + for (level = 0; (parent = BT_POP(t)) != NULL; ++level) { + /* Get the parent page. */ + if ((h = mpool_get(t->bt_mp, parent->pgno, 0)) == NULL) + return (1); + + /* Move to the next index. */ + if (parent->index != NEXTINDEX(h) - 1) { + index = parent->index + 1; + BT_PUSH(t, h->pgno, index); + break; + } + mpool_put(t->bt_mp, h, 0); + } + + /* Restore the stack. */ + while (level--) { + /* Push the next level down onto the stack. */ + bi = GETBINTERNAL(h, index); + pgno = bi->pgno; + BT_PUSH(t, pgno, 0); + + /* Lose the currently pinned page. */ + mpool_put(t->bt_mp, h, 0); + + /* Get the next level down. */ + if ((h = mpool_get(t->bt_mp, pgno, 0)) == NULL) + return (1); + index = 0; + } + mpool_put(t->bt_mp, h, 0); + if ((h = mpool_get(t->bt_mp, nextpg, 0)) == NULL) + return (1); + } + + if (h->pgno == c->pg.pgno) + goto ret; + + /* Reacquire the original stack. */ + mpool_put(t->bt_mp, h, 0); + if ((e = __bt_search(t, &c->key, &exact)) == NULL) + return (1); + h = e->page; + + /* + * Move left, looking for the page. At each move we have to move + * up the stack until we don't have to change pages to move to the + * next page. If we have to change pages at an internal level, we + * have to fix the stack back up. + */ + while (h->pgno != c->pg.pgno) { + if ((prevpg = h->prevpg) == P_INVALID) + break; + mpool_put(t->bt_mp, h, 0); + + /* Move up the stack. */ + for (level = 0; (parent = BT_POP(t)) != NULL; ++level) { + /* Get the parent page. */ + if ((h = mpool_get(t->bt_mp, parent->pgno, 0)) == NULL) + return (1); + + /* Move to the next index. */ + if (parent->index != 0) { + index = parent->index - 1; + BT_PUSH(t, h->pgno, index); + break; + } + mpool_put(t->bt_mp, h, 0); + } + + /* Restore the stack. */ + while (level--) { + /* Push the next level down onto the stack. */ + bi = GETBINTERNAL(h, index); + pgno = bi->pgno; + + /* Lose the currently pinned page. */ + mpool_put(t->bt_mp, h, 0); + + /* Get the next level down. */ + if ((h = mpool_get(t->bt_mp, pgno, 0)) == NULL) + return (1); + + index = NEXTINDEX(h) - 1; + BT_PUSH(t, pgno, index); + } + mpool_put(t->bt_mp, h, 0); + if ((h = mpool_get(t->bt_mp, prevpg, 0)) == NULL) + return (1); + } + + +ret: mpool_put(t->bt_mp, h, 0); + return ((*hp = mpool_get(t->bt_mp, c->pg.pgno, 0)) == NULL); +} + +/* + * __bt_bdelete -- + * Delete all key/data pairs matching the specified key. + * + * Parameters: + * t: tree + * key: key to delete + * + * Returns: + * RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key not found. + */ +static int +__bt_bdelete(t, key) + BTREE *t; + const DBT *key; +{ + EPG *e; + PAGE *h; + int deleted, exact, redo; + + deleted = 0; + + /* Find any matching record; __bt_search pins the page. */ +loop: if ((e = __bt_search(t, key, &exact)) == NULL) + return (deleted ? RET_SUCCESS : RET_ERROR); + if (!exact) { + mpool_put(t->bt_mp, e->page, 0); + return (deleted ? RET_SUCCESS : RET_SPECIAL); + } + + /* + * Delete forward, then delete backward, from the found key. If + * there are duplicates and we reach either side of the page, do + * the key search again, so that we get them all. + */ + redo = 0; + h = e->page; + do { + if (__bt_dleaf(t, key, h, e->index)) { + mpool_put(t->bt_mp, h, 0); + return (RET_ERROR); + } + if (F_ISSET(t, B_NODUPS)) { + if (NEXTINDEX(h) == 0) { + if (__bt_pdelete(t, h)) + return (RET_ERROR); + } else + mpool_put(t->bt_mp, h, MPOOL_DIRTY); + return (RET_SUCCESS); + } + deleted = 1; + } while (e->index < NEXTINDEX(h) && __bt_cmp(t, key, e) == 0); + + /* Check for right-hand edge of the page. */ + if (e->index == NEXTINDEX(h)) + redo = 1; + + /* Delete from the key to the beginning of the page. */ + while (e->index-- > 0) { + if (__bt_cmp(t, key, e) != 0) + break; + if (__bt_dleaf(t, key, h, e->index) == RET_ERROR) { + mpool_put(t->bt_mp, h, 0); + return (RET_ERROR); + } + if (e->index == 0) + redo = 1; + } + + /* Check for an empty page. */ + if (NEXTINDEX(h) == 0) { + if (__bt_pdelete(t, h)) + return (RET_ERROR); + goto loop; + } + + /* Put the page. */ + mpool_put(t->bt_mp, h, MPOOL_DIRTY); + + if (redo) + goto loop; + return (RET_SUCCESS); +} + +/* + * __bt_pdelete -- + * Delete a single page from the tree. + * + * Parameters: + * t: tree + * h: leaf page + * + * Returns: + * RET_SUCCESS, RET_ERROR. + * + * Side-effects: + * mpool_put's the page + */ +static int +__bt_pdelete(t, h) + BTREE *t; + PAGE *h; +{ + BINTERNAL *bi; + PAGE *pg; + EPGNO *parent; + indx_t cnt, index, *ip, offset; + u_int32_t nksize; + char *from; + + /* + * Walk the parent page stack -- a LIFO stack of the pages that were + * traversed when we searched for the page where the delete occurred. + * Each stack entry is a page number and a page index offset. The + * offset is for the page traversed on the search. We've just deleted + * a page, so we have to delete the key from the parent page. + * + * If the delete from the parent page makes it empty, this process may + * continue all the way up the tree. We stop if we reach the root page + * (which is never deleted, it's just not worth the effort) or if the + * delete does not empty the page. + */ + while ((parent = BT_POP(t)) != NULL) { + /* Get the parent page. */ + if ((pg = mpool_get(t->bt_mp, parent->pgno, 0)) == NULL) + return (RET_ERROR); + + index = parent->index; + bi = GETBINTERNAL(pg, index); + + /* Free any overflow pages. */ + if (bi->flags & P_BIGKEY && + __ovfl_delete(t, bi->bytes) == RET_ERROR) { + mpool_put(t->bt_mp, pg, 0); + return (RET_ERROR); + } + + /* + * Free the parent if it has only the one key and it's not the + * root page. If it's the rootpage, turn it back into an empty + * leaf page. + */ + if (NEXTINDEX(pg) == 1) { + if (pg->pgno == P_ROOT) { + pg->lower = BTDATAOFF; + pg->upper = t->bt_psize; + pg->flags = P_BLEAF; + } else { + if (__bt_relink(t, pg) || __bt_free(t, pg)) + return (RET_ERROR); + continue; + } + } else { + /* Pack remaining key items at the end of the page. */ + nksize = NBINTERNAL(bi->ksize); + from = (char *)pg + pg->upper; + memmove(from + nksize, from, (char *)bi - from); + pg->upper += nksize; + + /* Adjust indices' offsets, shift the indices down. */ + offset = pg->linp[index]; + for (cnt = index, ip = &pg->linp[0]; cnt--; ++ip) + if (ip[0] < offset) + ip[0] += nksize; + for (cnt = NEXTINDEX(pg) - index; --cnt; ++ip) + ip[0] = ip[1] < offset ? ip[1] + nksize : ip[1]; + pg->lower -= sizeof(indx_t); + } + + mpool_put(t->bt_mp, pg, MPOOL_DIRTY); + break; + } + + /* Free the leaf page, as long as it wasn't the root. */ + if (h->pgno == P_ROOT) { + mpool_put(t->bt_mp, h, MPOOL_DIRTY); + return (RET_SUCCESS); + } + return (__bt_relink(t, h) || __bt_free(t, h)); +} + +/* + * __bt_dleaf -- + * Delete a single record from a leaf page. + * + * Parameters: + * t: tree + * key: referenced key + * h: page + * index: index on page to delete + * + * Returns: + * RET_SUCCESS, RET_ERROR. + */ +int +__bt_dleaf(t, key, h, index) + BTREE *t; + const DBT *key; + PAGE *h; + u_int index; +{ + BLEAF *bl; + indx_t cnt, *ip, offset; + u_int32_t nbytes; + void *to; + char *from; + + /* If this record is referenced by the cursor, delete the cursor. */ + if (F_ISSET(&t->bt_cursor, CURS_INIT) && + !F_ISSET(&t->bt_cursor, CURS_ACQUIRE) && + t->bt_cursor.pg.pgno == h->pgno && t->bt_cursor.pg.index == index && + __bt_curdel(t, key, h, index)) + return (RET_ERROR); + + /* If the entry uses overflow pages, make them available for reuse. */ + to = bl = GETBLEAF(h, index); + if (bl->flags & P_BIGKEY && __ovfl_delete(t, bl->bytes) == RET_ERROR) + return (RET_ERROR); + if (bl->flags & P_BIGDATA && + __ovfl_delete(t, bl->bytes + bl->ksize) == RET_ERROR) + return (RET_ERROR); + + /* Pack the remaining key/data items at the end of the page. */ + nbytes = NBLEAF(bl); + from = (char *)h + h->upper; + memmove(from + nbytes, from, (char *)to - from); + h->upper += nbytes; + + /* Adjust the indices' offsets, shift the indices down. */ + offset = h->linp[index]; + for (cnt = index, ip = &h->linp[0]; cnt--; ++ip) + if (ip[0] < offset) + ip[0] += nbytes; + for (cnt = NEXTINDEX(h) - index; --cnt; ++ip) + ip[0] = ip[1] < offset ? ip[1] + nbytes : ip[1]; + h->lower -= sizeof(indx_t); + + /* If the cursor is on this page, adjust it as necessary. */ + if (F_ISSET(&t->bt_cursor, CURS_INIT) && + !F_ISSET(&t->bt_cursor, CURS_ACQUIRE) && + t->bt_cursor.pg.pgno == h->pgno && t->bt_cursor.pg.index > index) + --t->bt_cursor.pg.index; + + return (RET_SUCCESS); +} + +/* + * __bt_curdel -- + * Delete the cursor. + * + * Parameters: + * t: tree + * key: referenced key (or NULL) + * h: page + * index: index on page to delete + * + * Returns: + * RET_SUCCESS, RET_ERROR. + */ +static int +__bt_curdel(t, key, h, index) + BTREE *t; + const DBT *key; + PAGE *h; + u_int index; +{ + CURSOR *c; + EPG e; + PAGE *pg; + int curcopy, status; + + /* + * If there are duplicates, move forward or backward to one. + * Otherwise, copy the key into the cursor area. + */ + c = &t->bt_cursor; + F_CLR(c, CURS_AFTER | CURS_BEFORE | CURS_ACQUIRE); + + curcopy = 0; + if (!F_ISSET(t, B_NODUPS)) { + /* + * We're going to have to do comparisons. If we weren't + * provided a copy of the key, i.e. the user is deleting + * the current cursor position, get one. + */ + if (key == NULL) { + e.page = h; + e.index = index; + if ((status = __bt_ret(t, &e, + &c->key, &c->key, NULL, NULL, 1)) != RET_SUCCESS) + return (status); + curcopy = 1; + key = &c->key; + } + /* Check previous key, if not at the beginning of the page. */ + if (index > 0) { + e.page = h; + e.index = index - 1; + if (__bt_cmp(t, key, &e) == 0) { + F_SET(c, CURS_BEFORE); + goto dup2; + } + } + /* Check next key, if not at the end of the page. */ + if (index < NEXTINDEX(h) - 1) { + e.page = h; + e.index = index + 1; + if (__bt_cmp(t, key, &e) == 0) { + F_SET(c, CURS_AFTER); + goto dup2; + } + } + /* Check previous key if at the beginning of the page. */ + if (index == 0 && h->prevpg != P_INVALID) { + if ((pg = mpool_get(t->bt_mp, h->prevpg, 0)) == NULL) + return (RET_ERROR); + e.page = pg; + e.index = NEXTINDEX(pg) - 1; + if (__bt_cmp(t, key, &e) == 0) { + F_SET(c, CURS_BEFORE); + goto dup1; + } + mpool_put(t->bt_mp, pg, 0); + } + /* Check next key if at the end of the page. */ + if (index == NEXTINDEX(h) - 1 && h->nextpg != P_INVALID) { + if ((pg = mpool_get(t->bt_mp, h->nextpg, 0)) == NULL) + return (RET_ERROR); + e.page = pg; + e.index = 0; + if (__bt_cmp(t, key, &e) == 0) { + F_SET(c, CURS_AFTER); +dup1: mpool_put(t->bt_mp, pg, 0); +dup2: c->pg.pgno = e.page->pgno; + c->pg.index = e.index; + return (RET_SUCCESS); + } + mpool_put(t->bt_mp, pg, 0); + } + } + e.page = h; + e.index = index; + if (curcopy || (status = + __bt_ret(t, &e, &c->key, &c->key, NULL, NULL, 1)) == RET_SUCCESS) { + F_SET(c, CURS_ACQUIRE); + return (RET_SUCCESS); + } + return (status); +} + +/* + * __bt_relink -- + * Link around a deleted page. + * + * Parameters: + * t: tree + * h: page to be deleted + */ +static int +__bt_relink(t, h) + BTREE *t; + PAGE *h; +{ + PAGE *pg; + + if (h->nextpg != P_INVALID) { + if ((pg = mpool_get(t->bt_mp, h->nextpg, 0)) == NULL) + return (RET_ERROR); + pg->prevpg = h->prevpg; + mpool_put(t->bt_mp, pg, MPOOL_DIRTY); + } + if (h->prevpg != P_INVALID) { + if ((pg = mpool_get(t->bt_mp, h->prevpg, 0)) == NULL) + return (RET_ERROR); + pg->nextpg = h->nextpg; + mpool_put(t->bt_mp, pg, MPOOL_DIRTY); + } + return (0); +} diff --git a/main/db1-ast/btree/bt_get.c b/main/db1-ast/btree/bt_get.c new file mode 100644 index 000000000..74824c73f --- /dev/null +++ b/main/db1-ast/btree/bt_get.c @@ -0,0 +1,105 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Olson. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)bt_get.c 8.6 (Berkeley) 7/20/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include +#include +#include + +#include +#include "btree.h" + +/* + * __BT_GET -- Get a record from the btree. + * + * Parameters: + * dbp: pointer to access method + * key: key to find + * data: data to return + * flag: currently unused + * + * Returns: + * RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key not found. + */ +int +__bt_get(dbp, key, data, flags) + const DB *dbp; + const DBT *key; + DBT *data; + u_int flags; +{ + BTREE *t; + EPG *e; + int exact, status; + + t = dbp->internal; + + /* Toss any page pinned across calls. */ + if (t->bt_pinned != NULL) { + mpool_put(t->bt_mp, t->bt_pinned, 0); + t->bt_pinned = NULL; + } + + /* Get currently doesn't take any flags. */ + if (flags) { + errno = EINVAL; + return (RET_ERROR); + } + + if ((e = __bt_search(t, key, &exact)) == NULL) + return (RET_ERROR); + if (!exact) { + mpool_put(t->bt_mp, e->page, 0); + return (RET_SPECIAL); + } + + status = __bt_ret(t, e, NULL, NULL, data, &t->bt_rdata, 0); + + /* + * If the user is doing concurrent access, we copied the + * key/data, toss the page. + */ + if (F_ISSET(t, B_DB_LOCK)) + mpool_put(t->bt_mp, e->page, 0); + else + t->bt_pinned = e->page; + return (status); +} diff --git a/main/db1-ast/btree/bt_open.c b/main/db1-ast/btree/bt_open.c new file mode 100644 index 000000000..8c2f48ebf --- /dev/null +++ b/main/db1-ast/btree/bt_open.c @@ -0,0 +1,458 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Olson. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)bt_open.c 8.10 (Berkeley) 8/17/94"; +#endif /* LIBC_SCCS and not lint */ + +/* + * Implementation of btree access method for 4.4BSD. + * + * The design here was originally based on that of the btree access method + * used in the Postgres database system at UC Berkeley. This implementation + * is wholly independent of the Postgres code. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "btree.h" + +#ifdef DEBUG +#undef MINPSIZE +#define MINPSIZE 128 +#endif + +static int byteorder __P((void)); +static int nroot __P((BTREE *)); +static int tmp __P((void)); + +/* + * __BT_OPEN -- Open a btree. + * + * Creates and fills a DB struct, and calls the routine that actually + * opens the btree. + * + * Parameters: + * fname: filename (NULL for in-memory trees) + * flags: open flag bits + * mode: open permission bits + * b: BTREEINFO pointer + * + * Returns: + * NULL on failure, pointer to DB on success. + * + */ +DB * +__bt_open(fname, flags, mode, openinfo, dflags) + const char *fname; + int flags, mode, dflags; + const BTREEINFO *openinfo; +{ + struct stat sb; + BTMETA m; + BTREE *t; + BTREEINFO b; + DB *dbp; + pgno_t ncache; + ssize_t nr; + int machine_lorder; + + t = NULL; + + /* + * Intention is to make sure all of the user's selections are okay + * here and then use them without checking. Can't be complete, since + * we don't know the right page size, lorder or flags until the backing + * file is opened. Also, the file's page size can cause the cachesize + * to change. + */ + machine_lorder = byteorder(); + if (openinfo) { + b = *openinfo; + + /* Flags: R_DUP. */ + if (b.flags & ~(R_DUP)) + goto einval; + + /* + * Page size must be indx_t aligned and >= MINPSIZE. Default + * page size is set farther on, based on the underlying file + * transfer size. + */ + if (b.psize && + (b.psize < MINPSIZE || b.psize > MAX_PAGE_OFFSET + 1 || + b.psize & (sizeof(indx_t) - 1))) + goto einval; + + /* Minimum number of keys per page; absolute minimum is 2. */ + if (b.minkeypage) { + if (b.minkeypage < 2) + goto einval; + } else + b.minkeypage = DEFMINKEYPAGE; + + /* If no comparison, use default comparison and prefix. */ + if (b.compare == NULL) { + b.compare = __bt_defcmp; + if (b.prefix == NULL) + b.prefix = __bt_defpfx; + } + + if (b.lorder == 0) + b.lorder = machine_lorder; + } else { + b.compare = __bt_defcmp; + b.cachesize = 0; + b.flags = 0; + b.lorder = machine_lorder; + b.minkeypage = DEFMINKEYPAGE; + b.prefix = __bt_defpfx; + b.psize = 0; + } + + /* Check for the ubiquitous PDP-11. */ + if (b.lorder != BIG_ENDIAN && b.lorder != LITTLE_ENDIAN) + goto einval; + + /* Allocate and initialize DB and BTREE structures. */ + if ((t = (BTREE *)malloc(sizeof(BTREE))) == NULL) + goto err; + memset(t, 0, sizeof(BTREE)); + t->bt_fd = -1; /* Don't close unopened fd on error. */ + t->bt_lorder = b.lorder; + t->bt_order = NOT; + t->bt_cmp = b.compare; + t->bt_pfx = b.prefix; + t->bt_rfd = -1; + + if ((t->bt_dbp = dbp = (DB *)malloc(sizeof(DB))) == NULL) + goto err; + memset(t->bt_dbp, 0, sizeof(DB)); + if (t->bt_lorder != machine_lorder) + F_SET(t, B_NEEDSWAP); + + dbp->type = DB_BTREE; + dbp->internal = t; + dbp->close = __bt_close; + dbp->del = __bt_delete; + dbp->fd = __bt_fd; + dbp->get = __bt_get; + dbp->put = __bt_put; + dbp->seq = __bt_seq; + dbp->sync = __bt_sync; + + /* + * If no file name was supplied, this is an in-memory btree and we + * open a backing temporary file. Otherwise, it's a disk-based tree. + */ + if (fname) { + switch (flags & O_ACCMODE) { + case O_RDONLY: + F_SET(t, B_RDONLY); + break; + case O_RDWR: + break; + case O_WRONLY: + default: + goto einval; + } + + if ((t->bt_fd = open(fname, flags, mode)) < 0) + goto err; + + } else { + if ((flags & O_ACCMODE) != O_RDWR) + goto einval; + if ((t->bt_fd = tmp()) == -1) + goto err; + F_SET(t, B_INMEM); + } + + if (fcntl(t->bt_fd, F_SETFD, 1) == -1) + goto err; + + if (fstat(t->bt_fd, &sb)) + goto err; + if (sb.st_size) { + if ((nr = read(t->bt_fd, &m, sizeof(BTMETA))) < 0) + goto err; + if (nr != sizeof(BTMETA)) + goto eftype; + + /* + * Read in the meta-data. This can change the notion of what + * the lorder, page size and flags are, and, when the page size + * changes, the cachesize value can change too. If the user + * specified the wrong byte order for an existing database, we + * don't bother to return an error, we just clear the NEEDSWAP + * bit. + */ + if (m.magic == BTREEMAGIC) + F_CLR(t, B_NEEDSWAP); + else { + F_SET(t, B_NEEDSWAP); + M_32_SWAP(m.magic); + M_32_SWAP(m.version); + M_32_SWAP(m.psize); + M_32_SWAP(m.free); + M_32_SWAP(m.nrecs); + M_32_SWAP(m.flags); + } + if (m.magic != BTREEMAGIC || m.version != BTREEVERSION) + goto eftype; + if (m.psize < MINPSIZE || m.psize > MAX_PAGE_OFFSET + 1 || + m.psize & (sizeof(indx_t) - 1)) + goto eftype; + if (m.flags & ~SAVEMETA) + goto eftype; + b.psize = m.psize; + F_SET(t, m.flags); + t->bt_free = m.free; + t->bt_nrecs = m.nrecs; + } else { + /* + * Set the page size to the best value for I/O to this file. + * Don't overflow the page offset type. + */ + if (b.psize == 0) { +#ifdef _STATBUF_ST_BLKSIZE + b.psize = sb.st_blksize; +#endif + if (b.psize < MINPSIZE) + b.psize = MINPSIZE; + if (b.psize > MAX_PAGE_OFFSET + 1) + b.psize = MAX_PAGE_OFFSET + 1; + } + + /* Set flag if duplicates permitted. */ + if (!(b.flags & R_DUP)) + F_SET(t, B_NODUPS); + + t->bt_free = P_INVALID; + t->bt_nrecs = 0; + F_SET(t, B_METADIRTY); + } + + t->bt_psize = b.psize; + + /* Set the cache size; must be a multiple of the page size. */ + if (b.cachesize && b.cachesize & (b.psize - 1)) + b.cachesize += (~b.cachesize & (b.psize - 1)) + 1; + if (b.cachesize < b.psize * MINCACHE) + b.cachesize = b.psize * MINCACHE; + + /* Calculate number of pages to cache. */ + ncache = (b.cachesize + t->bt_psize - 1) / t->bt_psize; + + /* + * The btree data structure requires that at least two keys can fit on + * a page, but other than that there's no fixed requirement. The user + * specified a minimum number per page, and we translated that into the + * number of bytes a key/data pair can use before being placed on an + * overflow page. This calculation includes the page header, the size + * of the index referencing the leaf item and the size of the leaf item + * structure. Also, don't let the user specify a minkeypage such that + * a key/data pair won't fit even if both key and data are on overflow + * pages. + */ + t->bt_ovflsize = (t->bt_psize - BTDATAOFF) / b.minkeypage - + (sizeof(indx_t) + NBLEAFDBT(0, 0)); + if (t->bt_ovflsize < NBLEAFDBT(NOVFLSIZE, NOVFLSIZE) + sizeof(indx_t)) + t->bt_ovflsize = + NBLEAFDBT(NOVFLSIZE, NOVFLSIZE) + sizeof(indx_t); + + /* Initialize the buffer pool. */ + if ((t->bt_mp = + mpool_open(NULL, t->bt_fd, t->bt_psize, ncache)) == NULL) + goto err; + if (!F_ISSET(t, B_INMEM)) + mpool_filter(t->bt_mp, __bt_pgin, __bt_pgout, t); + + /* Create a root page if new tree. */ + if (nroot(t) == RET_ERROR) + goto err; + + /* Global flags. */ + if (dflags & DB_LOCK) + F_SET(t, B_DB_LOCK); + if (dflags & DB_SHMEM) + F_SET(t, B_DB_SHMEM); + if (dflags & DB_TXN) + F_SET(t, B_DB_TXN); + + return (dbp); + +einval: errno = EINVAL; + goto err; + +eftype: errno = EFTYPE; + goto err; + +err: if (t) { + if (t->bt_dbp) + free(t->bt_dbp); + if (t->bt_fd != -1) + (void)close(t->bt_fd); + free(t); + } + return (NULL); +} + +/* + * NROOT -- Create the root of a new tree. + * + * Parameters: + * t: tree + * + * Returns: + * RET_ERROR, RET_SUCCESS + */ +static int +nroot(t) + BTREE *t; +{ + PAGE *meta, *root; + pgno_t npg; + + if ((meta = mpool_get(t->bt_mp, 0, 0)) != NULL) { + mpool_put(t->bt_mp, meta, 0); + return (RET_SUCCESS); + } + if (errno != EINVAL) /* It's OK to not exist. */ + return (RET_ERROR); + errno = 0; + + if ((meta = mpool_new(t->bt_mp, &npg)) == NULL) + return (RET_ERROR); + + if ((root = mpool_new(t->bt_mp, &npg)) == NULL) + return (RET_ERROR); + + if (npg != P_ROOT) + return (RET_ERROR); + root->pgno = npg; + root->prevpg = root->nextpg = P_INVALID; + root->lower = BTDATAOFF; + root->upper = t->bt_psize; + root->flags = P_BLEAF; + memset(meta, 0, t->bt_psize); + mpool_put(t->bt_mp, meta, MPOOL_DIRTY); + mpool_put(t->bt_mp, root, MPOOL_DIRTY); + return (RET_SUCCESS); +} + +static int +tmp() +{ + sigset_t set, oset; + int fd; + const char *envtmp; + char *path; + static const char fmt[] = "%s/bt.XXXXXX"; + size_t n; + + envtmp = getenv("TMPDIR"); + if (!envtmp) + envtmp = "/tmp"; + n = strlen (envtmp) + sizeof fmt; +#ifdef __GNUC__ + path = __builtin_alloca(n); +#else + path = malloc(n); +#endif + (void)snprintf(path, n, fmt, envtmp ? envtmp : "/tmp"); + + (void)sigfillset(&set); + (void)sigprocmask(SIG_BLOCK, &set, &oset); + if ((fd = mkstemp(path)) != -1) + (void)unlink(path); + (void)sigprocmask(SIG_SETMASK, &oset, NULL); +#ifndef __GNUC__ + free(path); +#endif + return(fd); +} + +static int +byteorder() +{ + u_int32_t x; + u_char *p; + + x = 0x01020304; + p = (u_char *)&x; + switch (*p) { + case 1: + return (BIG_ENDIAN); + case 4: + return (LITTLE_ENDIAN); + default: + return (0); + } +} + +int +__bt_fd(dbp) + const DB *dbp; +{ + BTREE *t; + + t = dbp->internal; + + /* Toss any page pinned across calls. */ + if (t->bt_pinned != NULL) { + mpool_put(t->bt_mp, t->bt_pinned, 0); + t->bt_pinned = NULL; + } + + /* In-memory database can't have a file descriptor. */ + if (F_ISSET(t, B_INMEM)) { + errno = ENOENT; + return (-1); + } + return (t->bt_fd); +} diff --git a/main/db1-ast/btree/bt_overflow.c b/main/db1-ast/btree/bt_overflow.c new file mode 100644 index 000000000..b28b8e047 --- /dev/null +++ b/main/db1-ast/btree/bt_overflow.c @@ -0,0 +1,228 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Olson. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)bt_overflow.c 8.5 (Berkeley) 7/16/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include +#include +#include + +#include +#include "btree.h" + +/* + * Big key/data code. + * + * Big key and data entries are stored on linked lists of pages. The initial + * reference is byte string stored with the key or data and is the page number + * and size. The actual record is stored in a chain of pages linked by the + * nextpg field of the PAGE header. + * + * The first page of the chain has a special property. If the record is used + * by an internal page, it cannot be deleted and the P_PRESERVE bit will be set + * in the header. + * + * XXX + * A single DBT is written to each chain, so a lot of space on the last page + * is wasted. This is a fairly major bug for some data sets. + */ + +/* + * __OVFL_GET -- Get an overflow key/data item. + * + * Parameters: + * t: tree + * p: pointer to { pgno_t, u_int32_t } + * buf: storage address + * bufsz: storage size + * + * Returns: + * RET_ERROR, RET_SUCCESS + */ +int +__ovfl_get(t, p, ssz, buf, bufsz) + BTREE *t; + void *p; + size_t *ssz; + void **buf; + size_t *bufsz; +{ + PAGE *h; + pgno_t pg; + size_t nb, plen; + u_int32_t sz; + + memmove(&pg, p, sizeof(pgno_t)); + memmove(&sz, (char *)p + sizeof(pgno_t), sizeof(u_int32_t)); + *ssz = sz; + +#ifdef DEBUG + if (pg == P_INVALID || sz == 0) + abort(); +#endif + /* Make the buffer bigger as necessary. */ + if (*bufsz < sz) { + *buf = (char *)(*buf == NULL ? malloc(sz) : realloc(*buf, sz)); + if (*buf == NULL) + return (RET_ERROR); + *bufsz = sz; + } + + /* + * Step through the linked list of pages, copying the data on each one + * into the buffer. Never copy more than the data's length. + */ + plen = t->bt_psize - BTDATAOFF; + for (p = *buf;; p = (char *)p + nb, pg = h->nextpg) { + if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) + return (RET_ERROR); + + nb = MIN(sz, plen); + memmove(p, (char *)h + BTDATAOFF, nb); + mpool_put(t->bt_mp, h, 0); + + if ((sz -= nb) == 0) + break; + } + return (RET_SUCCESS); +} + +/* + * __OVFL_PUT -- Store an overflow key/data item. + * + * Parameters: + * t: tree + * data: DBT to store + * pgno: storage page number + * + * Returns: + * RET_ERROR, RET_SUCCESS + */ +int +__ovfl_put(t, dbt, pg) + BTREE *t; + const DBT *dbt; + pgno_t *pg; +{ + PAGE *h, *last; + void *p; + pgno_t npg; + size_t nb, plen; + u_int32_t sz; + + /* + * Allocate pages and copy the key/data record into them. Store the + * number of the first page in the chain. + */ + plen = t->bt_psize - BTDATAOFF; + for (last = NULL, p = dbt->data, sz = dbt->size;; + p = (char *)p + plen, last = h) { + if ((h = __bt_new(t, &npg)) == NULL) + return (RET_ERROR); + + h->pgno = npg; + h->nextpg = h->prevpg = P_INVALID; + h->flags = P_OVERFLOW; + h->lower = h->upper = 0; + + nb = MIN(sz, plen); + memmove((char *)h + BTDATAOFF, p, nb); + + if (last) { + last->nextpg = h->pgno; + mpool_put(t->bt_mp, last, MPOOL_DIRTY); + } else + *pg = h->pgno; + + if ((sz -= nb) == 0) { + mpool_put(t->bt_mp, h, MPOOL_DIRTY); + break; + } + } + return (RET_SUCCESS); +} + +/* + * __OVFL_DELETE -- Delete an overflow chain. + * + * Parameters: + * t: tree + * p: pointer to { pgno_t, u_int32_t } + * + * Returns: + * RET_ERROR, RET_SUCCESS + */ +int +__ovfl_delete(t, p) + BTREE *t; + void *p; +{ + PAGE *h; + pgno_t pg; + size_t plen; + u_int32_t sz; + + memmove(&pg, p, sizeof(pgno_t)); + memmove(&sz, (char *)p + sizeof(pgno_t), sizeof(u_int32_t)); + +#ifdef DEBUG + if (pg == P_INVALID || sz == 0) + abort(); +#endif + if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) + return (RET_ERROR); + + /* Don't delete chains used by internal pages. */ + if (h->flags & P_PRESERVE) { + mpool_put(t->bt_mp, h, 0); + return (RET_SUCCESS); + } + + /* Step through the chain, calling the free routine for each page. */ + for (plen = t->bt_psize - BTDATAOFF;; sz -= plen) { + pg = h->nextpg; + __bt_free(t, h); + if (sz <= plen) + break; + if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) + return (RET_ERROR); + } + return (RET_SUCCESS); +} diff --git a/main/db1-ast/btree/bt_page.c b/main/db1-ast/btree/bt_page.c new file mode 100644 index 000000000..ce9cbf15a --- /dev/null +++ b/main/db1-ast/btree/bt_page.c @@ -0,0 +1,100 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)bt_page.c 8.3 (Berkeley) 7/14/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include + +#include +#include "btree.h" + +/* + * __bt_free -- + * Put a page on the freelist. + * + * Parameters: + * t: tree + * h: page to free + * + * Returns: + * RET_ERROR, RET_SUCCESS + * + * Side-effect: + * mpool_put's the page. + */ +int +__bt_free(t, h) + BTREE *t; + PAGE *h; +{ + /* Insert the page at the head of the free list. */ + h->prevpg = P_INVALID; + h->nextpg = t->bt_free; + t->bt_free = h->pgno; + F_SET(t, B_METADIRTY); + + /* Make sure the page gets written back. */ + return (mpool_put(t->bt_mp, h, MPOOL_DIRTY)); +} + +/* + * __bt_new -- + * Get a new page, preferably from the freelist. + * + * Parameters: + * t: tree + * npg: storage for page number. + * + * Returns: + * Pointer to a page, NULL on error. + */ +PAGE * +__bt_new(t, npg) + BTREE *t; + pgno_t *npg; +{ + PAGE *h; + + if (t->bt_free != P_INVALID && + (h = mpool_get(t->bt_mp, t->bt_free, 0)) != NULL) { + *npg = t->bt_free; + t->bt_free = h->nextpg; + F_SET(t, B_METADIRTY); + return (h); + } + return (mpool_new(t->bt_mp, npg)); +} diff --git a/main/db1-ast/btree/bt_put.c b/main/db1-ast/btree/bt_put.c new file mode 100644 index 000000000..3f3807531 --- /dev/null +++ b/main/db1-ast/btree/bt_put.c @@ -0,0 +1,321 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Olson. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)bt_put.c 8.8 (Berkeley) 7/26/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include +#include +#include +#include + +#include +#include "btree.h" + +static EPG *bt_fast __P((BTREE *, const DBT *, const DBT *, int *)); + +/* + * __BT_PUT -- Add a btree item to the tree. + * + * Parameters: + * dbp: pointer to access method + * key: key + * data: data + * flag: R_NOOVERWRITE + * + * Returns: + * RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key is already in the + * tree and R_NOOVERWRITE specified. + */ +int +__bt_put(dbp, key, data, flags) + const DB *dbp; + DBT *key; + const DBT *data; + u_int flags; +{ + BTREE *t; + DBT tkey, tdata; + EPG *e = 0; + PAGE *h; + indx_t index, nxtindex; + pgno_t pg; + u_int32_t nbytes; + int dflags, exact, status; + char *dest, db[NOVFLSIZE], kb[NOVFLSIZE]; + + t = dbp->internal; + + /* Toss any page pinned across calls. */ + if (t->bt_pinned != NULL) { + mpool_put(t->bt_mp, t->bt_pinned, 0); + t->bt_pinned = NULL; + } + + /* Check for change to a read-only tree. */ + if (F_ISSET(t, B_RDONLY)) { + errno = EPERM; + return (RET_ERROR); + } + + switch (flags) { + case 0: + case R_NOOVERWRITE: + break; + case R_CURSOR: + /* + * If flags is R_CURSOR, put the cursor. Must already + * have started a scan and not have already deleted it. + */ + if (F_ISSET(&t->bt_cursor, CURS_INIT) && + !F_ISSET(&t->bt_cursor, + CURS_ACQUIRE | CURS_AFTER | CURS_BEFORE)) + break; + /* FALLTHROUGH */ + default: + errno = EINVAL; + return (RET_ERROR); + } + + /* + * If the key/data pair won't fit on a page, store it on overflow + * pages. Only put the key on the overflow page if the pair are + * still too big after moving the data to an overflow page. + * + * XXX + * If the insert fails later on, the overflow pages aren't recovered. + */ + dflags = 0; + if (key->size + data->size > t->bt_ovflsize) { + if (key->size > t->bt_ovflsize) { +storekey: if (__ovfl_put(t, key, &pg) == RET_ERROR) + return (RET_ERROR); + tkey.data = kb; + tkey.size = NOVFLSIZE; + memmove(kb, &pg, sizeof(pgno_t)); + memmove(kb + sizeof(pgno_t), + &key->size, sizeof(u_int32_t)); + dflags |= P_BIGKEY; + key = &tkey; + } + if (key->size + data->size > t->bt_ovflsize) { + if (__ovfl_put(t, data, &pg) == RET_ERROR) + return (RET_ERROR); + tdata.data = db; + tdata.size = NOVFLSIZE; + memmove(db, &pg, sizeof(pgno_t)); + memmove(db + sizeof(pgno_t), + &data->size, sizeof(u_int32_t)); + dflags |= P_BIGDATA; + data = &tdata; + } + if (key->size + data->size > t->bt_ovflsize) + goto storekey; + } + + /* Replace the cursor. */ + if (flags == R_CURSOR) { + if ((h = mpool_get(t->bt_mp, t->bt_cursor.pg.pgno, 0)) == NULL) + return (RET_ERROR); + index = t->bt_cursor.pg.index; + goto delete; + } + + /* + * Find the key to delete, or, the location at which to insert. + * Bt_fast and __bt_search both pin the returned page. + */ + if (t->bt_order == NOT || (e = bt_fast(t, key, data, &exact)) == NULL) + if ((e = __bt_search(t, key, &exact)) == NULL) + return (RET_ERROR); + h = e->page; + index = e->index; + + /* + * Add the key/data pair to the tree. If an identical key is already + * in the tree, and R_NOOVERWRITE is set, an error is returned. If + * R_NOOVERWRITE is not set, the key is either added (if duplicates are + * permitted) or an error is returned. + */ + switch (flags) { + case R_NOOVERWRITE: + if (!exact) + break; + mpool_put(t->bt_mp, h, 0); + return (RET_SPECIAL); + default: + if (!exact || !F_ISSET(t, B_NODUPS)) + break; + /* + * !!! + * Note, the delete may empty the page, so we need to put a + * new entry into the page immediately. + */ +delete: if (__bt_dleaf(t, key, h, index) == RET_ERROR) { + mpool_put(t->bt_mp, h, 0); + return (RET_ERROR); + } + break; + } + + /* + * If not enough room, or the user has put a ceiling on the number of + * keys permitted in the page, split the page. The split code will + * insert the key and data and unpin the current page. If inserting + * into the offset array, shift the pointers up. + */ + nbytes = NBLEAFDBT(key->size, data->size); + if ((u_int32_t) (h->upper - h->lower) < nbytes + sizeof(indx_t)) { + if ((status = __bt_split(t, h, key, + data, dflags, nbytes, index)) != RET_SUCCESS) + return (status); + goto success; + } + + if (index < (nxtindex = NEXTINDEX(h))) + memmove(h->linp + index + 1, h->linp + index, + (nxtindex - index) * sizeof(indx_t)); + h->lower += sizeof(indx_t); + + h->linp[index] = h->upper -= nbytes; + dest = (char *)h + h->upper; + WR_BLEAF(dest, key, data, dflags); + + /* If the cursor is on this page, adjust it as necessary. */ + if (F_ISSET(&t->bt_cursor, CURS_INIT) && + !F_ISSET(&t->bt_cursor, CURS_ACQUIRE) && + t->bt_cursor.pg.pgno == h->pgno && t->bt_cursor.pg.index >= index) + ++t->bt_cursor.pg.index; + + if (t->bt_order == NOT) { + if (h->nextpg == P_INVALID) { + if (index == NEXTINDEX(h) - 1) { + t->bt_order = FORWARD; + t->bt_last.index = index; + t->bt_last.pgno = h->pgno; + } + } else if (h->prevpg == P_INVALID) { + if (index == 0) { + t->bt_order = BACK; + t->bt_last.index = 0; + t->bt_last.pgno = h->pgno; + } + } + } + + mpool_put(t->bt_mp, h, MPOOL_DIRTY); + +success: + if (flags == R_SETCURSOR) + __bt_setcur(t, e->page->pgno, e->index); + + F_SET(t, B_MODIFIED); + return (RET_SUCCESS); +} + +#ifdef STATISTICS +u_long bt_cache_hit, bt_cache_miss; +#endif + +/* + * BT_FAST -- Do a quick check for sorted data. + * + * Parameters: + * t: tree + * key: key to insert + * + * Returns: + * EPG for new record or NULL if not found. + */ +static EPG * +bt_fast(t, key, data, exactp) + BTREE *t; + const DBT *key, *data; + int *exactp; +{ + PAGE *h; + u_int32_t nbytes; + int cmp; + + if ((h = mpool_get(t->bt_mp, t->bt_last.pgno, 0)) == NULL) { + t->bt_order = NOT; + return (NULL); + } + t->bt_cur.page = h; + t->bt_cur.index = t->bt_last.index; + + /* + * If won't fit in this page or have too many keys in this page, + * have to search to get split stack. + */ + nbytes = NBLEAFDBT(key->size, data->size); + if ((u_int32_t) (h->upper - h->lower) < nbytes + sizeof(indx_t)) + goto miss; + + if (t->bt_order == FORWARD) { + if (t->bt_cur.page->nextpg != P_INVALID) + goto miss; + if (t->bt_cur.index != NEXTINDEX(h) - 1) + goto miss; + if ((cmp = __bt_cmp(t, key, &t->bt_cur)) < 0) + goto miss; + t->bt_last.index = cmp ? ++t->bt_cur.index : t->bt_cur.index; + } else { + if (t->bt_cur.page->prevpg != P_INVALID) + goto miss; + if (t->bt_cur.index != 0) + goto miss; + if ((cmp = __bt_cmp(t, key, &t->bt_cur)) > 0) + goto miss; + t->bt_last.index = 0; + } + *exactp = cmp == 0; +#ifdef STATISTICS + ++bt_cache_hit; +#endif + return (&t->bt_cur); + +miss: +#ifdef STATISTICS + ++bt_cache_miss; +#endif + t->bt_order = NOT; + mpool_put(t->bt_mp, h, 0); + return (NULL); +} diff --git a/main/db1-ast/btree/bt_search.c b/main/db1-ast/btree/bt_search.c new file mode 100644 index 000000000..485afcbbf --- /dev/null +++ b/main/db1-ast/btree/bt_search.c @@ -0,0 +1,213 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Olson. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)bt_search.c 8.8 (Berkeley) 7/31/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include + +#include +#include "btree.h" + +static int __bt_snext __P((BTREE *, PAGE *, const DBT *, int *)); +static int __bt_sprev __P((BTREE *, PAGE *, const DBT *, int *)); + +/* + * __bt_search -- + * Search a btree for a key. + * + * Parameters: + * t: tree to search + * key: key to find + * exactp: pointer to exact match flag + * + * Returns: + * The EPG for matching record, if any, or the EPG for the location + * of the key, if it were inserted into the tree, is entered into + * the bt_cur field of the tree. A pointer to the field is returned. + */ +EPG * +__bt_search(t, key, exactp) + BTREE *t; + const DBT *key; + int *exactp; +{ + PAGE *h; + indx_t base, index, lim; + pgno_t pg; + int cmp; + + BT_CLR(t); + for (pg = P_ROOT;;) { + if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) + return (NULL); + + /* Do a binary search on the current page. */ + t->bt_cur.page = h; + for (base = 0, lim = NEXTINDEX(h); lim; lim >>= 1) { + t->bt_cur.index = index = base + (lim >> 1); + if ((cmp = __bt_cmp(t, key, &t->bt_cur)) == 0) { + if (h->flags & P_BLEAF) { + *exactp = 1; + return (&t->bt_cur); + } + goto next; + } + if (cmp > 0) { + base = index + 1; + --lim; + } + } + + /* + * If it's a leaf page, we're almost done. If no duplicates + * are allowed, or we have an exact match, we're done. Else, + * it's possible that there were matching keys on this page, + * which later deleted, and we're on a page with no matches + * while there are matches on other pages. If at the start or + * end of a page, check the adjacent page. + */ + if (h->flags & P_BLEAF) { + if (!F_ISSET(t, B_NODUPS)) { + if (base == 0 && + h->prevpg != P_INVALID && + __bt_sprev(t, h, key, exactp)) + return (&t->bt_cur); + if (base == NEXTINDEX(h) && + h->nextpg != P_INVALID && + __bt_snext(t, h, key, exactp)) + return (&t->bt_cur); + } + *exactp = 0; + t->bt_cur.index = base; + return (&t->bt_cur); + } + + /* + * No match found. Base is the smallest index greater than + * key and may be zero or a last + 1 index. If it's non-zero, + * decrement by one, and record the internal page which should + * be a parent page for the key. If a split later occurs, the + * inserted page will be to the right of the saved page. + */ + index = base ? base - 1 : base; + +next: BT_PUSH(t, h->pgno, index); + pg = GETBINTERNAL(h, index)->pgno; + mpool_put(t->bt_mp, h, 0); + } +} + +/* + * __bt_snext -- + * Check for an exact match after the key. + * + * Parameters: + * t: tree + * h: current page + * key: key + * exactp: pointer to exact match flag + * + * Returns: + * If an exact match found. + */ +static int +__bt_snext(t, h, key, exactp) + BTREE *t; + PAGE *h; + const DBT *key; + int *exactp; +{ + EPG e; + + /* + * Get the next page. The key is either an exact + * match, or not as good as the one we already have. + */ + if ((e.page = mpool_get(t->bt_mp, h->nextpg, 0)) == NULL) + return (0); + e.index = 0; + if (__bt_cmp(t, key, &e) == 0) { + mpool_put(t->bt_mp, h, 0); + t->bt_cur = e; + *exactp = 1; + return (1); + } + mpool_put(t->bt_mp, e.page, 0); + return (0); +} + +/* + * __bt_sprev -- + * Check for an exact match before the key. + * + * Parameters: + * t: tree + * h: current page + * key: key + * exactp: pointer to exact match flag + * + * Returns: + * If an exact match found. + */ +static int +__bt_sprev(t, h, key, exactp) + BTREE *t; + PAGE *h; + const DBT *key; + int *exactp; +{ + EPG e; + + /* + * Get the previous page. The key is either an exact + * match, or not as good as the one we already have. + */ + if ((e.page = mpool_get(t->bt_mp, h->prevpg, 0)) == NULL) + return (0); + e.index = NEXTINDEX(e.page) - 1; + if (__bt_cmp(t, key, &e) == 0) { + mpool_put(t->bt_mp, h, 0); + t->bt_cur = e; + *exactp = 1; + return (1); + } + mpool_put(t->bt_mp, e.page, 0); + return (0); +} diff --git a/main/db1-ast/btree/bt_seq.c b/main/db1-ast/btree/bt_seq.c new file mode 100644 index 000000000..5ba7b0301 --- /dev/null +++ b/main/db1-ast/btree/bt_seq.c @@ -0,0 +1,460 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Olson. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)bt_seq.c 8.7 (Berkeley) 7/20/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include +#include +#include +#include + +#include +#include "btree.h" + +static int __bt_first __P((BTREE *, const DBT *, EPG *, int *)); +static int __bt_seqadv __P((BTREE *, EPG *, int)); +static int __bt_seqset __P((BTREE *, EPG *, DBT *, int)); + +/* + * Sequential scan support. + * + * The tree can be scanned sequentially, starting from either end of the + * tree or from any specific key. A scan request before any scanning is + * done is initialized as starting from the least node. + */ + +/* + * __bt_seq -- + * Btree sequential scan interface. + * + * Parameters: + * dbp: pointer to access method + * key: key for positioning and return value + * data: data return value + * flags: R_CURSOR, R_FIRST, R_LAST, R_NEXT, R_PREV. + * + * Returns: + * RET_ERROR, RET_SUCCESS or RET_SPECIAL if there's no next key. + */ +int +__bt_seq(dbp, key, data, flags) + const DB *dbp; + DBT *key, *data; + u_int flags; +{ + BTREE *t; + EPG e; + int status; + + t = dbp->internal; + + /* Toss any page pinned across calls. */ + if (t->bt_pinned != NULL) { + mpool_put(t->bt_mp, t->bt_pinned, 0); + t->bt_pinned = NULL; + } + + /* + * If scan uninitialized as yet, or starting at a specific record, set + * the scan to a specific key. Both __bt_seqset and __bt_seqadv pin + * the page the cursor references if they're successful. + */ + switch (flags) { + case R_NEXT: + case R_PREV: + if (F_ISSET(&t->bt_cursor, CURS_INIT)) { + status = __bt_seqadv(t, &e, flags); + break; + } + /* FALLTHROUGH */ + case R_FIRST: + case R_LAST: + case R_CURSOR: + status = __bt_seqset(t, &e, key, flags); + break; + default: + errno = EINVAL; + return (RET_ERROR); + } + + if (status == RET_SUCCESS) { + __bt_setcur(t, e.page->pgno, e.index); + + status = + __bt_ret(t, &e, key, &t->bt_rkey, data, &t->bt_rdata, 0); + + /* + * If the user is doing concurrent access, we copied the + * key/data, toss the page. + */ + if (F_ISSET(t, B_DB_LOCK)) + mpool_put(t->bt_mp, e.page, 0); + else + t->bt_pinned = e.page; + } + return (status); +} + +/* + * __bt_seqset -- + * Set the sequential scan to a specific key. + * + * Parameters: + * t: tree + * ep: storage for returned key + * key: key for initial scan position + * flags: R_CURSOR, R_FIRST, R_LAST, R_NEXT, R_PREV + * + * Side effects: + * Pins the page the cursor references. + * + * Returns: + * RET_ERROR, RET_SUCCESS or RET_SPECIAL if there's no next key. + */ +static int +__bt_seqset(t, ep, key, flags) + BTREE *t; + EPG *ep; + DBT *key; + int flags; +{ + PAGE *h; + pgno_t pg; + int exact; + + /* + * Find the first, last or specific key in the tree and point the + * cursor at it. The cursor may not be moved until a new key has + * been found. + */ + switch (flags) { + case R_CURSOR: /* Keyed scan. */ + /* + * Find the first instance of the key or the smallest key + * which is greater than or equal to the specified key. + */ + if (key->data == NULL || key->size == 0) { + errno = EINVAL; + return (RET_ERROR); + } + return (__bt_first(t, key, ep, &exact)); + case R_FIRST: /* First record. */ + case R_NEXT: + /* Walk down the left-hand side of the tree. */ + for (pg = P_ROOT;;) { + if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) + return (RET_ERROR); + + /* Check for an empty tree. */ + if (NEXTINDEX(h) == 0) { + mpool_put(t->bt_mp, h, 0); + return (RET_SPECIAL); + } + + if (h->flags & (P_BLEAF | P_RLEAF)) + break; + pg = GETBINTERNAL(h, 0)->pgno; + mpool_put(t->bt_mp, h, 0); + } + ep->page = h; + ep->index = 0; + break; + case R_LAST: /* Last record. */ + case R_PREV: + /* Walk down the right-hand side of the tree. */ + for (pg = P_ROOT;;) { + if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) + return (RET_ERROR); + + /* Check for an empty tree. */ + if (NEXTINDEX(h) == 0) { + mpool_put(t->bt_mp, h, 0); + return (RET_SPECIAL); + } + + if (h->flags & (P_BLEAF | P_RLEAF)) + break; + pg = GETBINTERNAL(h, NEXTINDEX(h) - 1)->pgno; + mpool_put(t->bt_mp, h, 0); + } + + ep->page = h; + ep->index = NEXTINDEX(h) - 1; + break; + } + return (RET_SUCCESS); +} + +/* + * __bt_seqadvance -- + * Advance the sequential scan. + * + * Parameters: + * t: tree + * flags: R_NEXT, R_PREV + * + * Side effects: + * Pins the page the new key/data record is on. + * + * Returns: + * RET_ERROR, RET_SUCCESS or RET_SPECIAL if there's no next key. + */ +static int +__bt_seqadv(t, ep, flags) + BTREE *t; + EPG *ep; + int flags; +{ + CURSOR *c; + PAGE *h; + indx_t index = 0; + pgno_t pg; + int exact; + + /* + * There are a couple of states that we can be in. The cursor has + * been initialized by the time we get here, but that's all we know. + */ + c = &t->bt_cursor; + + /* + * The cursor was deleted where there weren't any duplicate records, + * so the key was saved. Find out where that key would go in the + * current tree. It doesn't matter if the returned key is an exact + * match or not -- if it's an exact match, the record was added after + * the delete so we can just return it. If not, as long as there's + * a record there, return it. + */ + if (F_ISSET(c, CURS_ACQUIRE)) + return (__bt_first(t, &c->key, ep, &exact)); + + /* Get the page referenced by the cursor. */ + if ((h = mpool_get(t->bt_mp, c->pg.pgno, 0)) == NULL) + return (RET_ERROR); + + /* + * Find the next/previous record in the tree and point the cursor at + * it. The cursor may not be moved until a new key has been found. + */ + switch (flags) { + case R_NEXT: /* Next record. */ + /* + * The cursor was deleted in duplicate records, and moved + * forward to a record that has yet to be returned. Clear + * that flag, and return the record. + */ + if (F_ISSET(c, CURS_AFTER)) + goto usecurrent; + index = c->pg.index; + if (++index == NEXTINDEX(h)) { + pg = h->nextpg; + mpool_put(t->bt_mp, h, 0); + if (pg == P_INVALID) + return (RET_SPECIAL); + if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) + return (RET_ERROR); + index = 0; + } + break; + case R_PREV: /* Previous record. */ + /* + * The cursor was deleted in duplicate records, and moved + * backward to a record that has yet to be returned. Clear + * that flag, and return the record. + */ + if (F_ISSET(c, CURS_BEFORE)) { +usecurrent: F_CLR(c, CURS_AFTER | CURS_BEFORE); + ep->page = h; + ep->index = c->pg.index; + return (RET_SUCCESS); + } + index = c->pg.index; + if (index == 0) { + pg = h->prevpg; + mpool_put(t->bt_mp, h, 0); + if (pg == P_INVALID) + return (RET_SPECIAL); + if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) + return (RET_ERROR); + index = NEXTINDEX(h) - 1; + } else + --index; + break; + } + + ep->page = h; + ep->index = index; + return (RET_SUCCESS); +} + +/* + * __bt_first -- + * Find the first entry. + * + * Parameters: + * t: the tree + * key: the key + * erval: return EPG + * exactp: pointer to exact match flag + * + * Returns: + * The first entry in the tree greater than or equal to key, + * or RET_SPECIAL if no such key exists. + */ +static int +__bt_first(t, key, erval, exactp) + BTREE *t; + const DBT *key; + EPG *erval; + int *exactp; +{ + PAGE *h; + EPG *ep, save; + pgno_t pg; + + /* + * Find any matching record; __bt_search pins the page. + * + * If it's an exact match and duplicates are possible, walk backwards + * in the tree until we find the first one. Otherwise, make sure it's + * a valid key (__bt_search may return an index just past the end of a + * page) and return it. + */ + if ((ep = __bt_search(t, key, exactp)) == NULL) + return (RET_SPECIAL); + if (*exactp) { + if (F_ISSET(t, B_NODUPS)) { + *erval = *ep; + return (RET_SUCCESS); + } + + /* + * Walk backwards, as long as the entry matches and there are + * keys left in the tree. Save a copy of each match in case + * we go too far. + */ + save = *ep; + h = ep->page; + do { + if (save.page->pgno != ep->page->pgno) { + mpool_put(t->bt_mp, save.page, 0); + save = *ep; + } else + save.index = ep->index; + + /* + * Don't unpin the page the last (or original) match + * was on, but make sure it's unpinned if an error + * occurs. + */ + if (ep->index == 0) { + if (h->prevpg == P_INVALID) + break; + if (h->pgno != save.page->pgno) + mpool_put(t->bt_mp, h, 0); + if ((h = mpool_get(t->bt_mp, + h->prevpg, 0)) == NULL) { + if (h->pgno == save.page->pgno) + mpool_put(t->bt_mp, + save.page, 0); + return (RET_ERROR); + } + ep->page = h; + ep->index = NEXTINDEX(h); + } + --ep->index; + } while (__bt_cmp(t, key, ep) == 0); + + /* + * Reach here with the last page that was looked at pinned, + * which may or may not be the same as the last (or original) + * match page. If it's not useful, release it. + */ + if (h->pgno != save.page->pgno) + mpool_put(t->bt_mp, h, 0); + + *erval = save; + return (RET_SUCCESS); + } + + /* If at the end of a page, find the next entry. */ + if (ep->index == NEXTINDEX(ep->page)) { + h = ep->page; + pg = h->nextpg; + mpool_put(t->bt_mp, h, 0); + if (pg == P_INVALID) + return (RET_SPECIAL); + if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) + return (RET_ERROR); + ep->index = 0; + ep->page = h; + } + *erval = *ep; + return (RET_SUCCESS); +} + +/* + * __bt_setcur -- + * Set the cursor to an entry in the tree. + * + * Parameters: + * t: the tree + * pgno: page number + * index: page index + */ +void +__bt_setcur(t, pgno, index) + BTREE *t; + pgno_t pgno; + u_int index; +{ + /* Lose any already deleted key. */ + if (t->bt_cursor.key.data != NULL) { + free(t->bt_cursor.key.data); + t->bt_cursor.key.size = 0; + t->bt_cursor.key.data = NULL; + } + F_CLR(&t->bt_cursor, CURS_ACQUIRE | CURS_AFTER | CURS_BEFORE); + + /* Update the cursor. */ + t->bt_cursor.pg.pgno = pgno; + t->bt_cursor.pg.index = index; + F_SET(&t->bt_cursor, CURS_INIT); +} diff --git a/main/db1-ast/btree/bt_split.c b/main/db1-ast/btree/bt_split.c new file mode 100644 index 000000000..e6bd540b4 --- /dev/null +++ b/main/db1-ast/btree/bt_split.c @@ -0,0 +1,829 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Olson. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)bt_split.c 8.9 (Berkeley) 7/26/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include +#include +#include +#include + +#include +#include "btree.h" + +static int bt_broot __P((BTREE *, PAGE *, PAGE *, PAGE *)); +static PAGE *bt_page + __P((BTREE *, PAGE *, PAGE **, PAGE **, indx_t *, size_t)); +static int bt_preserve __P((BTREE *, pgno_t)); +static PAGE *bt_psplit + __P((BTREE *, PAGE *, PAGE *, PAGE *, indx_t *, size_t)); +static PAGE *bt_root + __P((BTREE *, PAGE *, PAGE **, PAGE **, indx_t *, size_t)); +static int bt_rroot __P((BTREE *, PAGE *, PAGE *, PAGE *)); +static recno_t rec_total __P((PAGE *)); + +#ifdef STATISTICS +u_long bt_rootsplit, bt_split, bt_sortsplit, bt_pfxsaved; +#endif + +/* + * __BT_SPLIT -- Split the tree. + * + * Parameters: + * t: tree + * sp: page to split + * key: key to insert + * data: data to insert + * flags: BIGKEY/BIGDATA flags + * ilen: insert length + * skip: index to leave open + * + * Returns: + * RET_ERROR, RET_SUCCESS + */ +int +__bt_split(t, sp, key, data, flags, ilen, argskip) + BTREE *t; + PAGE *sp; + const DBT *key, *data; + int flags; + size_t ilen; + u_int32_t argskip; +{ + BINTERNAL *bi = 0; + BLEAF *bl = 0, *tbl; + DBT a, b; + EPGNO *parent; + PAGE *h, *l, *r, *lchild, *rchild; + indx_t nxtindex; + u_int16_t skip; + u_int32_t n, nbytes, nksize = 0; + int parentsplit; + char *dest; + + /* + * Split the page into two pages, l and r. The split routines return + * a pointer to the page into which the key should be inserted and with + * skip set to the offset which should be used. Additionally, l and r + * are pinned. + */ + skip = argskip; + h = sp->pgno == P_ROOT ? + bt_root(t, sp, &l, &r, &skip, ilen) : + bt_page(t, sp, &l, &r, &skip, ilen); + if (h == NULL) + return (RET_ERROR); + + /* + * Insert the new key/data pair into the leaf page. (Key inserts + * always cause a leaf page to split first.) + */ + h->linp[skip] = h->upper -= ilen; + dest = (char *)h + h->upper; + if (F_ISSET(t, R_RECNO)) + WR_RLEAF(dest, data, flags) + else + WR_BLEAF(dest, key, data, flags) + + /* If the root page was split, make it look right. */ + if (sp->pgno == P_ROOT && + (F_ISSET(t, R_RECNO) ? + bt_rroot(t, sp, l, r) : bt_broot(t, sp, l, r)) == RET_ERROR) + goto err2; + + /* + * Now we walk the parent page stack -- a LIFO stack of the pages that + * were traversed when we searched for the page that split. Each stack + * entry is a page number and a page index offset. The offset is for + * the page traversed on the search. We've just split a page, so we + * have to insert a new key into the parent page. + * + * If the insert into the parent page causes it to split, may have to + * continue splitting all the way up the tree. We stop if the root + * splits or the page inserted into didn't have to split to hold the + * new key. Some algorithms replace the key for the old page as well + * as the new page. We don't, as there's no reason to believe that the + * first key on the old page is any better than the key we have, and, + * in the case of a key being placed at index 0 causing the split, the + * key is unavailable. + * + * There are a maximum of 5 pages pinned at any time. We keep the left + * and right pages pinned while working on the parent. The 5 are the + * two children, left parent and right parent (when the parent splits) + * and the root page or the overflow key page when calling bt_preserve. + * This code must make sure that all pins are released other than the + * root page or overflow page which is unlocked elsewhere. + */ + while ((parent = BT_POP(t)) != NULL) { + lchild = l; + rchild = r; + + /* Get the parent page. */ + if ((h = mpool_get(t->bt_mp, parent->pgno, 0)) == NULL) + goto err2; + + /* + * The new key goes ONE AFTER the index, because the split + * was to the right. + */ + skip = parent->index + 1; + + /* + * Calculate the space needed on the parent page. + * + * Prefix trees: space hack when inserting into BINTERNAL + * pages. Retain only what's needed to distinguish between + * the new entry and the LAST entry on the page to its left. + * If the keys compare equal, retain the entire key. Note, + * we don't touch overflow keys, and the entire key must be + * retained for the next-to-left most key on the leftmost + * page of each level, or the search will fail. Applicable + * ONLY to internal pages that have leaf pages as children. + * Further reduction of the key between pairs of internal + * pages loses too much information. + */ + switch (rchild->flags & P_TYPE) { + case P_BINTERNAL: + bi = GETBINTERNAL(rchild, 0); + nbytes = NBINTERNAL(bi->ksize); + break; + case P_BLEAF: + bl = GETBLEAF(rchild, 0); + nbytes = NBINTERNAL(bl->ksize); + if (t->bt_pfx && !(bl->flags & P_BIGKEY) && + (h->prevpg != P_INVALID || skip > 1)) { + tbl = GETBLEAF(lchild, NEXTINDEX(lchild) - 1); + a.size = tbl->ksize; + a.data = tbl->bytes; + b.size = bl->ksize; + b.data = bl->bytes; + nksize = t->bt_pfx(&a, &b); + n = NBINTERNAL(nksize); + if (n < nbytes) { +#ifdef STATISTICS + bt_pfxsaved += nbytes - n; +#endif + nbytes = n; + } else + nksize = 0; + } else + nksize = 0; + break; + case P_RINTERNAL: + case P_RLEAF: + nbytes = NRINTERNAL; + break; + default: + abort(); + } + + /* Split the parent page if necessary or shift the indices. */ + if ((u_int32_t) (h->upper - h->lower) + < nbytes + sizeof(indx_t)) { + sp = h; + h = h->pgno == P_ROOT ? + bt_root(t, h, &l, &r, &skip, nbytes) : + bt_page(t, h, &l, &r, &skip, nbytes); + if (h == NULL) + goto err1; + parentsplit = 1; + } else { + if (skip < (nxtindex = NEXTINDEX(h))) + memmove(h->linp + skip + 1, h->linp + skip, + (nxtindex - skip) * sizeof(indx_t)); + h->lower += sizeof(indx_t); + parentsplit = 0; + } + + /* Insert the key into the parent page. */ + switch (rchild->flags & P_TYPE) { + case P_BINTERNAL: + h->linp[skip] = h->upper -= nbytes; + dest = (char *)h + h->linp[skip]; + memmove(dest, bi, nbytes); + ((BINTERNAL *)dest)->pgno = rchild->pgno; + break; + case P_BLEAF: + h->linp[skip] = h->upper -= nbytes; + dest = (char *)h + h->linp[skip]; + WR_BINTERNAL(dest, nksize ? nksize : bl->ksize, + rchild->pgno, bl->flags & P_BIGKEY); + memmove(dest, bl->bytes, nksize ? nksize : bl->ksize); + if (bl->flags & P_BIGKEY && + bt_preserve(t, *(pgno_t *)bl->bytes) == RET_ERROR) + goto err1; + break; + case P_RINTERNAL: + /* + * Update the left page count. If split + * added at index 0, fix the correct page. + */ + if (skip > 0) + dest = (char *)h + h->linp[skip - 1]; + else + dest = (char *)l + l->linp[NEXTINDEX(l) - 1]; + ((RINTERNAL *)dest)->nrecs = rec_total(lchild); + ((RINTERNAL *)dest)->pgno = lchild->pgno; + + /* Update the right page count. */ + h->linp[skip] = h->upper -= nbytes; + dest = (char *)h + h->linp[skip]; + ((RINTERNAL *)dest)->nrecs = rec_total(rchild); + ((RINTERNAL *)dest)->pgno = rchild->pgno; + break; + case P_RLEAF: + /* + * Update the left page count. If split + * added at index 0, fix the correct page. + */ + if (skip > 0) + dest = (char *)h + h->linp[skip - 1]; + else + dest = (char *)l + l->linp[NEXTINDEX(l) - 1]; + ((RINTERNAL *)dest)->nrecs = NEXTINDEX(lchild); + ((RINTERNAL *)dest)->pgno = lchild->pgno; + + /* Update the right page count. */ + h->linp[skip] = h->upper -= nbytes; + dest = (char *)h + h->linp[skip]; + ((RINTERNAL *)dest)->nrecs = NEXTINDEX(rchild); + ((RINTERNAL *)dest)->pgno = rchild->pgno; + break; + default: + abort(); + } + + /* Unpin the held pages. */ + if (!parentsplit) { + mpool_put(t->bt_mp, h, MPOOL_DIRTY); + break; + } + + /* If the root page was split, make it look right. */ + if (sp->pgno == P_ROOT && + (F_ISSET(t, R_RECNO) ? + bt_rroot(t, sp, l, r) : bt_broot(t, sp, l, r)) == RET_ERROR) + goto err1; + + mpool_put(t->bt_mp, lchild, MPOOL_DIRTY); + mpool_put(t->bt_mp, rchild, MPOOL_DIRTY); + } + + /* Unpin the held pages. */ + mpool_put(t->bt_mp, l, MPOOL_DIRTY); + mpool_put(t->bt_mp, r, MPOOL_DIRTY); + + /* Clear any pages left on the stack. */ + return (RET_SUCCESS); + + /* + * If something fails in the above loop we were already walking back + * up the tree and the tree is now inconsistent. Nothing much we can + * do about it but release any memory we're holding. + */ +err1: mpool_put(t->bt_mp, lchild, MPOOL_DIRTY); + mpool_put(t->bt_mp, rchild, MPOOL_DIRTY); + +err2: mpool_put(t->bt_mp, l, 0); + mpool_put(t->bt_mp, r, 0); + __dbpanic(t->bt_dbp); + return (RET_ERROR); +} + +/* + * BT_PAGE -- Split a non-root page of a btree. + * + * Parameters: + * t: tree + * h: root page + * lp: pointer to left page pointer + * rp: pointer to right page pointer + * skip: pointer to index to leave open + * ilen: insert length + * + * Returns: + * Pointer to page in which to insert or NULL on error. + */ +static PAGE * +bt_page(t, h, lp, rp, skip, ilen) + BTREE *t; + PAGE *h, **lp, **rp; + indx_t *skip; + size_t ilen; +{ + PAGE *l, *r, *tp; + pgno_t npg; + +#ifdef STATISTICS + ++bt_split; +#endif + /* Put the new right page for the split into place. */ + if ((r = __bt_new(t, &npg)) == NULL) + return (NULL); + r->pgno = npg; + r->lower = BTDATAOFF; + r->upper = t->bt_psize; + r->nextpg = h->nextpg; + r->prevpg = h->pgno; + r->flags = h->flags & P_TYPE; + + /* + * If we're splitting the last page on a level because we're appending + * a key to it (skip is NEXTINDEX()), it's likely that the data is + * sorted. Adding an empty page on the side of the level is less work + * and can push the fill factor much higher than normal. If we're + * wrong it's no big deal, we'll just do the split the right way next + * time. It may look like it's equally easy to do a similar hack for + * reverse sorted data, that is, split the tree left, but it's not. + * Don't even try. + */ + if (h->nextpg == P_INVALID && *skip == NEXTINDEX(h)) { +#ifdef STATISTICS + ++bt_sortsplit; +#endif + h->nextpg = r->pgno; + r->lower = BTDATAOFF + sizeof(indx_t); + *skip = 0; + *lp = h; + *rp = r; + return (r); + } + + /* Put the new left page for the split into place. */ + if ((l = (PAGE *)malloc(t->bt_psize)) == NULL) { + mpool_put(t->bt_mp, r, 0); + return (NULL); + } +#ifdef PURIFY + memset(l, 0xff, t->bt_psize); +#endif + l->pgno = h->pgno; + l->nextpg = r->pgno; + l->prevpg = h->prevpg; + l->lower = BTDATAOFF; + l->upper = t->bt_psize; + l->flags = h->flags & P_TYPE; + + /* Fix up the previous pointer of the page after the split page. */ + if (h->nextpg != P_INVALID) { + if ((tp = mpool_get(t->bt_mp, h->nextpg, 0)) == NULL) { + free(l); + /* XXX mpool_free(t->bt_mp, r->pgno); */ + return (NULL); + } + tp->prevpg = r->pgno; + mpool_put(t->bt_mp, tp, MPOOL_DIRTY); + } + + /* + * Split right. The key/data pairs aren't sorted in the btree page so + * it's simpler to copy the data from the split page onto two new pages + * instead of copying half the data to the right page and compacting + * the left page in place. Since the left page can't change, we have + * to swap the original and the allocated left page after the split. + */ + tp = bt_psplit(t, h, l, r, skip, ilen); + + /* Move the new left page onto the old left page. */ + memmove(h, l, t->bt_psize); + if (tp == l) + tp = h; + free(l); + + *lp = h; + *rp = r; + return (tp); +} + +/* + * BT_ROOT -- Split the root page of a btree. + * + * Parameters: + * t: tree + * h: root page + * lp: pointer to left page pointer + * rp: pointer to right page pointer + * skip: pointer to index to leave open + * ilen: insert length + * + * Returns: + * Pointer to page in which to insert or NULL on error. + */ +static PAGE * +bt_root(t, h, lp, rp, skip, ilen) + BTREE *t; + PAGE *h, **lp, **rp; + indx_t *skip; + size_t ilen; +{ + PAGE *l, *r, *tp; + pgno_t lnpg, rnpg; + +#ifdef STATISTICS + ++bt_split; + ++bt_rootsplit; +#endif + /* Put the new left and right pages for the split into place. */ + if ((l = __bt_new(t, &lnpg)) == NULL || + (r = __bt_new(t, &rnpg)) == NULL) + return (NULL); + l->pgno = lnpg; + r->pgno = rnpg; + l->nextpg = r->pgno; + r->prevpg = l->pgno; + l->prevpg = r->nextpg = P_INVALID; + l->lower = r->lower = BTDATAOFF; + l->upper = r->upper = t->bt_psize; + l->flags = r->flags = h->flags & P_TYPE; + + /* Split the root page. */ + tp = bt_psplit(t, h, l, r, skip, ilen); + + *lp = l; + *rp = r; + return (tp); +} + +/* + * BT_RROOT -- Fix up the recno root page after it has been split. + * + * Parameters: + * t: tree + * h: root page + * l: left page + * r: right page + * + * Returns: + * RET_ERROR, RET_SUCCESS + */ +static int +bt_rroot(t, h, l, r) + BTREE *t; + PAGE *h, *l, *r; +{ + char *dest; + + /* Insert the left and right keys, set the header information. */ + h->linp[0] = h->upper = t->bt_psize - NRINTERNAL; + dest = (char *)h + h->upper; + WR_RINTERNAL(dest, + l->flags & P_RLEAF ? NEXTINDEX(l) : rec_total(l), l->pgno); + + h->linp[1] = h->upper -= NRINTERNAL; + dest = (char *)h + h->upper; + WR_RINTERNAL(dest, + r->flags & P_RLEAF ? NEXTINDEX(r) : rec_total(r), r->pgno); + + h->lower = BTDATAOFF + 2 * sizeof(indx_t); + + /* Unpin the root page, set to recno internal page. */ + h->flags &= ~P_TYPE; + h->flags |= P_RINTERNAL; + mpool_put(t->bt_mp, h, MPOOL_DIRTY); + + return (RET_SUCCESS); +} + +/* + * BT_BROOT -- Fix up the btree root page after it has been split. + * + * Parameters: + * t: tree + * h: root page + * l: left page + * r: right page + * + * Returns: + * RET_ERROR, RET_SUCCESS + */ +static int +bt_broot(t, h, l, r) + BTREE *t; + PAGE *h, *l, *r; +{ + BINTERNAL *bi; + BLEAF *bl; + u_int32_t nbytes; + char *dest; + + /* + * If the root page was a leaf page, change it into an internal page. + * We copy the key we split on (but not the key's data, in the case of + * a leaf page) to the new root page. + * + * The btree comparison code guarantees that the left-most key on any + * level of the tree is never used, so it doesn't need to be filled in. + */ + nbytes = NBINTERNAL(0); + h->linp[0] = h->upper = t->bt_psize - nbytes; + dest = (char *)h + h->upper; + WR_BINTERNAL(dest, 0, l->pgno, 0); + + switch (h->flags & P_TYPE) { + case P_BLEAF: + bl = GETBLEAF(r, 0); + nbytes = NBINTERNAL(bl->ksize); + h->linp[1] = h->upper -= nbytes; + dest = (char *)h + h->upper; + WR_BINTERNAL(dest, bl->ksize, r->pgno, 0); + memmove(dest, bl->bytes, bl->ksize); + + /* + * If the key is on an overflow page, mark the overflow chain + * so it isn't deleted when the leaf copy of the key is deleted. + */ + if (bl->flags & P_BIGKEY && + bt_preserve(t, *(pgno_t *)bl->bytes) == RET_ERROR) + return (RET_ERROR); + break; + case P_BINTERNAL: + bi = GETBINTERNAL(r, 0); + nbytes = NBINTERNAL(bi->ksize); + h->linp[1] = h->upper -= nbytes; + dest = (char *)h + h->upper; + memmove(dest, bi, nbytes); + ((BINTERNAL *)dest)->pgno = r->pgno; + break; + default: + abort(); + } + + /* There are two keys on the page. */ + h->lower = BTDATAOFF + 2 * sizeof(indx_t); + + /* Unpin the root page, set to btree internal page. */ + h->flags &= ~P_TYPE; + h->flags |= P_BINTERNAL; + mpool_put(t->bt_mp, h, MPOOL_DIRTY); + + return (RET_SUCCESS); +} + +/* + * BT_PSPLIT -- Do the real work of splitting the page. + * + * Parameters: + * t: tree + * h: page to be split + * l: page to put lower half of data + * r: page to put upper half of data + * pskip: pointer to index to leave open + * ilen: insert length + * + * Returns: + * Pointer to page in which to insert. + */ +static PAGE * +bt_psplit(t, h, l, r, pskip, ilen) + BTREE *t; + PAGE *h, *l, *r; + indx_t *pskip; + size_t ilen; +{ + BINTERNAL *bi; + BLEAF *bl; + CURSOR *c; + RLEAF *rl; + PAGE *rval; + void *src = 0; + indx_t full, half, nxt, off, skip, top, used; + u_int32_t nbytes; + int bigkeycnt, isbigkey; + + /* + * Split the data to the left and right pages. Leave the skip index + * open. Additionally, make some effort not to split on an overflow + * key. This makes internal page processing faster and can save + * space as overflow keys used by internal pages are never deleted. + */ + bigkeycnt = 0; + skip = *pskip; + full = t->bt_psize - BTDATAOFF; + half = full / 2; + used = 0; + for (nxt = off = 0, top = NEXTINDEX(h); nxt < top; ++off) { + if (skip == off) { + nbytes = ilen; + isbigkey = 0; /* XXX: not really known. */ + } else + switch (h->flags & P_TYPE) { + case P_BINTERNAL: + src = bi = GETBINTERNAL(h, nxt); + nbytes = NBINTERNAL(bi->ksize); + isbigkey = bi->flags & P_BIGKEY; + break; + case P_BLEAF: + src = bl = GETBLEAF(h, nxt); + nbytes = NBLEAF(bl); + isbigkey = bl->flags & P_BIGKEY; + break; + case P_RINTERNAL: + src = GETRINTERNAL(h, nxt); + nbytes = NRINTERNAL; + isbigkey = 0; + break; + case P_RLEAF: + src = rl = GETRLEAF(h, nxt); + nbytes = NRLEAF(rl); + isbigkey = 0; + break; + default: + abort(); + } + + /* + * If the key/data pairs are substantial fractions of the max + * possible size for the page, it's possible to get situations + * where we decide to try and copy too much onto the left page. + * Make sure that doesn't happen. + */ + if ((skip <= off && used + nbytes + sizeof(indx_t) >= full) + || nxt == top - 1) { + --off; + break; + } + + /* Copy the key/data pair, if not the skipped index. */ + if (skip != off) { + ++nxt; + + l->linp[off] = l->upper -= nbytes; + memmove((char *)l + l->upper, src, nbytes); + } + + used += nbytes + sizeof(indx_t); + if (used >= half) { + if (!isbigkey || bigkeycnt == 3) + break; + else + ++bigkeycnt; + } + } + + /* + * Off is the last offset that's valid for the left page. + * Nxt is the first offset to be placed on the right page. + */ + l->lower += (off + 1) * sizeof(indx_t); + + /* + * If splitting the page that the cursor was on, the cursor has to be + * adjusted to point to the same record as before the split. If the + * cursor is at or past the skipped slot, the cursor is incremented by + * one. If the cursor is on the right page, it is decremented by the + * number of records split to the left page. + */ + c = &t->bt_cursor; + if (F_ISSET(c, CURS_INIT) && c->pg.pgno == h->pgno) { + if (c->pg.index >= skip) + ++c->pg.index; + if (c->pg.index < nxt) /* Left page. */ + c->pg.pgno = l->pgno; + else { /* Right page. */ + c->pg.pgno = r->pgno; + c->pg.index -= nxt; + } + } + + /* + * If the skipped index was on the left page, just return that page. + * Otherwise, adjust the skip index to reflect the new position on + * the right page. + */ + if (skip <= off) { + skip = 0; + rval = l; + } else { + rval = r; + *pskip -= nxt; + } + + for (off = 0; nxt < top; ++off) { + if (skip == nxt) { + ++off; + skip = 0; + } + switch (h->flags & P_TYPE) { + case P_BINTERNAL: + src = bi = GETBINTERNAL(h, nxt); + nbytes = NBINTERNAL(bi->ksize); + break; + case P_BLEAF: + src = bl = GETBLEAF(h, nxt); + nbytes = NBLEAF(bl); + break; + case P_RINTERNAL: + src = GETRINTERNAL(h, nxt); + nbytes = NRINTERNAL; + break; + case P_RLEAF: + src = rl = GETRLEAF(h, nxt); + nbytes = NRLEAF(rl); + break; + default: + abort(); + } + ++nxt; + r->linp[off] = r->upper -= nbytes; + memmove((char *)r + r->upper, src, nbytes); + } + r->lower += off * sizeof(indx_t); + + /* If the key is being appended to the page, adjust the index. */ + if (skip == top) + r->lower += sizeof(indx_t); + + return (rval); +} + +/* + * BT_PRESERVE -- Mark a chain of pages as used by an internal node. + * + * Chains of indirect blocks pointed to by leaf nodes get reclaimed when the + * record that references them gets deleted. Chains pointed to by internal + * pages never get deleted. This routine marks a chain as pointed to by an + * internal page. + * + * Parameters: + * t: tree + * pg: page number of first page in the chain. + * + * Returns: + * RET_SUCCESS, RET_ERROR. + */ +static int +bt_preserve(t, pg) + BTREE *t; + pgno_t pg; +{ + PAGE *h; + + if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) + return (RET_ERROR); + h->flags |= P_PRESERVE; + mpool_put(t->bt_mp, h, MPOOL_DIRTY); + return (RET_SUCCESS); +} + +/* + * REC_TOTAL -- Return the number of recno entries below a page. + * + * Parameters: + * h: page + * + * Returns: + * The number of recno entries below a page. + * + * XXX + * These values could be set by the bt_psplit routine. The problem is that the + * entry has to be popped off of the stack etc. or the values have to be passed + * all the way back to bt_split/bt_rroot and it's not very clean. + */ +static recno_t +rec_total(h) + PAGE *h; +{ + recno_t recs; + indx_t nxt, top; + + for (recs = 0, nxt = 0, top = NEXTINDEX(h); nxt < top; ++nxt) + recs += GETRINTERNAL(h, nxt)->nrecs; + return (recs); +} diff --git a/main/db1-ast/btree/bt_utils.c b/main/db1-ast/btree/bt_utils.c new file mode 100644 index 000000000..1416c7862 --- /dev/null +++ b/main/db1-ast/btree/bt_utils.c @@ -0,0 +1,260 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Olson. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)bt_utils.c 8.8 (Berkeley) 7/20/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include +#include +#include + +#include +#include "btree.h" + +/* + * __bt_ret -- + * Build return key/data pair. + * + * Parameters: + * t: tree + * e: key/data pair to be returned + * key: user's key structure (NULL if not to be filled in) + * rkey: memory area to hold key + * data: user's data structure (NULL if not to be filled in) + * rdata: memory area to hold data + * copy: always copy the key/data item + * + * Returns: + * RET_SUCCESS, RET_ERROR. + */ +int +__bt_ret(t, e, key, rkey, data, rdata, copy) + BTREE *t; + EPG *e; + DBT *key, *rkey, *data, *rdata; + int copy; +{ + BLEAF *bl; + void *p; + + bl = GETBLEAF(e->page, e->index); + + /* + * We must copy big keys/data to make them contiguous. Otherwise, + * leave the page pinned and don't copy unless the user specified + * concurrent access. + */ + if (key == NULL) + goto dataonly; + + if (bl->flags & P_BIGKEY) { + if (__ovfl_get(t, bl->bytes, + &key->size, &rkey->data, &rkey->size)) + return (RET_ERROR); + key->data = rkey->data; + } else if (copy || F_ISSET(t, B_DB_LOCK)) { + if (bl->ksize > rkey->size) { + p = (void *)(rkey->data == NULL ? + malloc(bl->ksize) : realloc(rkey->data, bl->ksize)); + if (p == NULL) + return (RET_ERROR); + rkey->data = p; + rkey->size = bl->ksize; + } + memmove(rkey->data, bl->bytes, bl->ksize); + key->size = bl->ksize; + key->data = rkey->data; + } else { + key->size = bl->ksize; + key->data = bl->bytes; + } + +dataonly: + if (data == NULL) + return (RET_SUCCESS); + + if (bl->flags & P_BIGDATA) { + if (__ovfl_get(t, bl->bytes + bl->ksize, + &data->size, &rdata->data, &rdata->size)) + return (RET_ERROR); + data->data = rdata->data; + } else if (copy || F_ISSET(t, B_DB_LOCK)) { + /* Use +1 in case the first record retrieved is 0 length. */ + if (bl->dsize + 1 > rdata->size) { + p = (void *)(rdata->data == NULL ? + malloc(bl->dsize + 1) : + realloc(rdata->data, bl->dsize + 1)); + if (p == NULL) + return (RET_ERROR); + rdata->data = p; + rdata->size = bl->dsize + 1; + } + memmove(rdata->data, bl->bytes + bl->ksize, bl->dsize); + data->size = bl->dsize; + data->data = rdata->data; + } else { + data->size = bl->dsize; + data->data = bl->bytes + bl->ksize; + } + + return (RET_SUCCESS); +} + +/* + * __BT_CMP -- Compare a key to a given record. + * + * Parameters: + * t: tree + * k1: DBT pointer of first arg to comparison + * e: pointer to EPG for comparison + * + * Returns: + * < 0 if k1 is < record + * = 0 if k1 is = record + * > 0 if k1 is > record + */ +int +__bt_cmp(t, k1, e) + BTREE *t; + const DBT *k1; + EPG *e; +{ + BINTERNAL *bi; + BLEAF *bl; + DBT k2; + PAGE *h; + void *bigkey; + + /* + * The left-most key on internal pages, at any level of the tree, is + * guaranteed by the following code to be less than any user key. + * This saves us from having to update the leftmost key on an internal + * page when the user inserts a new key in the tree smaller than + * anything we've yet seen. + */ + h = e->page; + if (e->index == 0 && h->prevpg == P_INVALID && !(h->flags & P_BLEAF)) + return (1); + + bigkey = NULL; + if (h->flags & P_BLEAF) { + bl = GETBLEAF(h, e->index); + if (bl->flags & P_BIGKEY) + bigkey = bl->bytes; + else { + k2.data = bl->bytes; + k2.size = bl->ksize; + } + } else { + bi = GETBINTERNAL(h, e->index); + if (bi->flags & P_BIGKEY) + bigkey = bi->bytes; + else { + k2.data = bi->bytes; + k2.size = bi->ksize; + } + } + + if (bigkey) { + if (__ovfl_get(t, bigkey, + &k2.size, &t->bt_rdata.data, &t->bt_rdata.size)) + return (RET_ERROR); + k2.data = t->bt_rdata.data; + } + return ((*t->bt_cmp)(k1, &k2)); +} + +/* + * __BT_DEFCMP -- Default comparison routine. + * + * Parameters: + * a: DBT #1 + * b: DBT #2 + * + * Returns: + * < 0 if a is < b + * = 0 if a is = b + * > 0 if a is > b + */ +int +__bt_defcmp(a, b) + const DBT *a, *b; +{ + register size_t len; + register u_char *p1, *p2; + + /* + * XXX + * If a size_t doesn't fit in an int, this routine can lose. + * What we need is a integral type which is guaranteed to be + * larger than a size_t, and there is no such thing. + */ + len = MIN(a->size, b->size); + for (p1 = a->data, p2 = b->data; len--; ++p1, ++p2) + if (*p1 != *p2) + return ((int)*p1 - (int)*p2); + return ((int)a->size - (int)b->size); +} + +/* + * __BT_DEFPFX -- Default prefix routine. + * + * Parameters: + * a: DBT #1 + * b: DBT #2 + * + * Returns: + * Number of bytes needed to distinguish b from a. + */ +size_t +__bt_defpfx(a, b) + const DBT *a, *b; +{ + register u_char *p1, *p2; + register size_t cnt, len; + + cnt = 1; + len = MIN(a->size, b->size); + for (p1 = a->data, p2 = b->data; len--; ++p1, ++p2, ++cnt) + if (*p1 != *p2) + return (cnt); + + /* a->size must be <= b->size, or they wouldn't be in this order. */ + return (a->size < b->size ? a->size + 1 : a->size); +} diff --git a/main/db1-ast/btree/btree.h b/main/db1-ast/btree/btree.h new file mode 100644 index 000000000..1f4a9ec91 --- /dev/null +++ b/main/db1-ast/btree/btree.h @@ -0,0 +1,391 @@ +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Olson. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)btree.h 8.11 (Berkeley) 8/17/94 + */ + +/* Macros to set/clear/test flags. */ +#define F_SET(p, f) (p)->flags |= (f) +#define F_CLR(p, f) (p)->flags &= ~(f) +#define F_ISSET(p, f) ((p)->flags & (f)) + +#include + +#define mpool_open __mpool_open +#define mpool_filter __mpool_filter +#define mpool_new __mpool_new +#define mpool_get __mpool_get +#define mpool_put __mpool_put +#define mpool_sync __mpool_sync +#define mpool_close __mpool_close + +#define DEFMINKEYPAGE (2) /* Minimum keys per page */ +#define MINCACHE (5) /* Minimum cached pages */ +#define MINPSIZE (512) /* Minimum page size */ + +/* + * Page 0 of a btree file contains a copy of the meta-data. This page is also + * used as an out-of-band page, i.e. page pointers that point to nowhere point + * to page 0. Page 1 is the root of the btree. + */ +#define P_INVALID 0 /* Invalid tree page number. */ +#define P_META 0 /* Tree metadata page number. */ +#define P_ROOT 1 /* Tree root page number. */ + +/* + * There are five page layouts in the btree: btree internal pages (BINTERNAL), + * btree leaf pages (BLEAF), recno internal pages (RINTERNAL), recno leaf pages + * (RLEAF) and overflow pages. All five page types have a page header (PAGE). + * This implementation requires that values within structures NOT be padded. + * (ANSI C permits random padding.) If your compiler pads randomly you'll have + * to do some work to get this package to run. + */ +typedef struct _page { + pgno_t pgno; /* this page's page number */ + pgno_t prevpg; /* left sibling */ + pgno_t nextpg; /* right sibling */ + +#define P_BINTERNAL 0x01 /* btree internal page */ +#define P_BLEAF 0x02 /* leaf page */ +#define P_OVERFLOW 0x04 /* overflow page */ +#define P_RINTERNAL 0x08 /* recno internal page */ +#define P_RLEAF 0x10 /* leaf page */ +#define P_TYPE 0x1f /* type mask */ +#define P_PRESERVE 0x20 /* never delete this chain of pages */ + u_int32_t flags; + + indx_t lower; /* lower bound of free space on page */ + indx_t upper; /* upper bound of free space on page */ + indx_t linp[1]; /* indx_t-aligned VAR. LENGTH DATA */ +} PAGE; + +/* First and next index. */ +#define BTDATAOFF \ + (sizeof(pgno_t) + sizeof(pgno_t) + sizeof(pgno_t) + \ + sizeof(u_int32_t) + sizeof(indx_t) + sizeof(indx_t)) +#define NEXTINDEX(p) (((p)->lower - BTDATAOFF) / sizeof(indx_t)) + +/* + * For pages other than overflow pages, there is an array of offsets into the + * rest of the page immediately following the page header. Each offset is to + * an item which is unique to the type of page. The h_lower offset is just + * past the last filled-in index. The h_upper offset is the first item on the + * page. Offsets are from the beginning of the page. + * + * If an item is too big to store on a single page, a flag is set and the item + * is a { page, size } pair such that the page is the first page of an overflow + * chain with size bytes of item. Overflow pages are simply bytes without any + * external structure. + * + * The page number and size fields in the items are pgno_t-aligned so they can + * be manipulated without copying. (This presumes that 32 bit items can be + * manipulated on this system.) + */ +#define LALIGN(n) (((n) + sizeof(pgno_t) - 1) & ~(sizeof(pgno_t) - 1)) +#define NOVFLSIZE (sizeof(pgno_t) + sizeof(u_int32_t)) + +/* + * For the btree internal pages, the item is a key. BINTERNALs are {key, pgno} + * pairs, such that the key compares less than or equal to all of the records + * on that page. For a tree without duplicate keys, an internal page with two + * consecutive keys, a and b, will have all records greater than or equal to a + * and less than b stored on the page associated with a. Duplicate keys are + * somewhat special and can cause duplicate internal and leaf page records and + * some minor modifications of the above rule. + */ +typedef struct _binternal { + u_int32_t ksize; /* key size */ + pgno_t pgno; /* page number stored on */ +#define P_BIGDATA 0x01 /* overflow data */ +#define P_BIGKEY 0x02 /* overflow key */ + u_char flags; + char bytes[1]; /* data */ +} BINTERNAL; + +/* Get the page's BINTERNAL structure at index indx. */ +#define GETBINTERNAL(pg, indx) \ + ((BINTERNAL *)((char *)(pg) + (pg)->linp[indx])) + +/* Get the number of bytes in the entry. */ +#define NBINTERNAL(len) \ + LALIGN(sizeof(u_int32_t) + sizeof(pgno_t) + sizeof(u_char) + (len)) + +/* Copy a BINTERNAL entry to the page. */ +#define WR_BINTERNAL(p, size, pgno, flags) { \ + *(u_int32_t *)p = size; \ + p += sizeof(u_int32_t); \ + *(pgno_t *)p = pgno; \ + p += sizeof(pgno_t); \ + *(u_char *)p = flags; \ + p += sizeof(u_char); \ +} + +/* + * For the recno internal pages, the item is a page number with the number of + * keys found on that page and below. + */ +typedef struct _rinternal { + recno_t nrecs; /* number of records */ + pgno_t pgno; /* page number stored below */ +} RINTERNAL; + +/* Get the page's RINTERNAL structure at index indx. */ +#define GETRINTERNAL(pg, indx) \ + ((RINTERNAL *)((char *)(pg) + (pg)->linp[indx])) + +/* Get the number of bytes in the entry. */ +#define NRINTERNAL \ + LALIGN(sizeof(recno_t) + sizeof(pgno_t)) + +/* Copy a RINTERNAL entry to the page. */ +#define WR_RINTERNAL(p, nrecs, pgno) { \ + *(recno_t *)p = nrecs; \ + p += sizeof(recno_t); \ + *(pgno_t *)p = pgno; \ +} + +/* For the btree leaf pages, the item is a key and data pair. */ +typedef struct _bleaf { + u_int32_t ksize; /* size of key */ + u_int32_t dsize; /* size of data */ + u_char flags; /* P_BIGDATA, P_BIGKEY */ + char bytes[1]; /* data */ +} BLEAF; + +/* Get the page's BLEAF structure at index indx. */ +#define GETBLEAF(pg, indx) \ + ((BLEAF *)((char *)(pg) + (pg)->linp[indx])) + +/* Get the number of bytes in the entry. */ +#define NBLEAF(p) NBLEAFDBT((p)->ksize, (p)->dsize) + +/* Get the number of bytes in the user's key/data pair. */ +#define NBLEAFDBT(ksize, dsize) \ + LALIGN(sizeof(u_int32_t) + sizeof(u_int32_t) + sizeof(u_char) + \ + (ksize) + (dsize)) + +/* Copy a BLEAF entry to the page. */ +#define WR_BLEAF(p, key, data, flags) { \ + *(u_int32_t *)p = key->size; \ + p += sizeof(u_int32_t); \ + *(u_int32_t *)p = data->size; \ + p += sizeof(u_int32_t); \ + *(u_char *)p = flags; \ + p += sizeof(u_char); \ + memmove(p, key->data, key->size); \ + p += key->size; \ + memmove(p, data->data, data->size); \ +} + +/* For the recno leaf pages, the item is a data entry. */ +typedef struct _rleaf { + u_int32_t dsize; /* size of data */ + u_char flags; /* P_BIGDATA */ + char bytes[1]; +} RLEAF; + +/* Get the page's RLEAF structure at index indx. */ +#define GETRLEAF(pg, indx) \ + ((RLEAF *)((char *)(pg) + (pg)->linp[indx])) + +/* Get the number of bytes in the entry. */ +#define NRLEAF(p) NRLEAFDBT((p)->dsize) + +/* Get the number of bytes from the user's data. */ +#define NRLEAFDBT(dsize) \ + LALIGN(sizeof(u_int32_t) + sizeof(u_char) + (dsize)) + +/* Copy a RLEAF entry to the page. */ +#define WR_RLEAF(p, data, flags) { \ + *(u_int32_t *)p = data->size; \ + p += sizeof(u_int32_t); \ + *(u_char *)p = flags; \ + p += sizeof(u_char); \ + memmove(p, data->data, data->size); \ +} + +/* + * A record in the tree is either a pointer to a page and an index in the page + * or a page number and an index. These structures are used as a cursor, stack + * entry and search returns as well as to pass records to other routines. + * + * One comment about searches. Internal page searches must find the largest + * record less than key in the tree so that descents work. Leaf page searches + * must find the smallest record greater than key so that the returned index + * is the record's correct position for insertion. + */ +typedef struct _epgno { + pgno_t pgno; /* the page number */ + indx_t index; /* the index on the page */ +} EPGNO; + +typedef struct _epg { + PAGE *page; /* the (pinned) page */ + indx_t index; /* the index on the page */ +} EPG; + +/* + * About cursors. The cursor (and the page that contained the key/data pair + * that it referenced) can be deleted, which makes things a bit tricky. If + * there are no duplicates of the cursor key in the tree (i.e. B_NODUPS is set + * or there simply aren't any duplicates of the key) we copy the key that it + * referenced when it's deleted, and reacquire a new cursor key if the cursor + * is used again. If there are duplicates keys, we move to the next/previous + * key, and set a flag so that we know what happened. NOTE: if duplicate (to + * the cursor) keys are added to the tree during this process, it is undefined + * if they will be returned or not in a cursor scan. + * + * The flags determine the possible states of the cursor: + * + * CURS_INIT The cursor references *something*. + * CURS_ACQUIRE The cursor was deleted, and a key has been saved so that + * we can reacquire the right position in the tree. + * CURS_AFTER, CURS_BEFORE + * The cursor was deleted, and now references a key/data pair + * that has not yet been returned, either before or after the + * deleted key/data pair. + * XXX + * This structure is broken out so that we can eventually offer multiple + * cursors as part of the DB interface. + */ +typedef struct _cursor { + EPGNO pg; /* B: Saved tree reference. */ + DBT key; /* B: Saved key, or key.data == NULL. */ + recno_t rcursor; /* R: recno cursor (1-based) */ + +#define CURS_ACQUIRE 0x01 /* B: Cursor needs to be reacquired. */ +#define CURS_AFTER 0x02 /* B: Unreturned cursor after key. */ +#define CURS_BEFORE 0x04 /* B: Unreturned cursor before key. */ +#define CURS_INIT 0x08 /* RB: Cursor initialized. */ + u_int8_t flags; +} CURSOR; + +/* + * The metadata of the tree. The nrecs field is used only by the RECNO code. + * This is because the btree doesn't really need it and it requires that every + * put or delete call modify the metadata. + */ +typedef struct _btmeta { + u_int32_t magic; /* magic number */ + u_int32_t version; /* version */ + u_int32_t psize; /* page size */ + u_int32_t free; /* page number of first free page */ + u_int32_t nrecs; /* R: number of records */ + +#define SAVEMETA (B_NODUPS | R_RECNO) + u_int32_t flags; /* bt_flags & SAVEMETA */ +} BTMETA; + +/* The in-memory btree/recno data structure. */ +typedef struct _btree { + MPOOL *bt_mp; /* memory pool cookie */ + + DB *bt_dbp; /* pointer to enclosing DB */ + + EPG bt_cur; /* current (pinned) page */ + PAGE *bt_pinned; /* page pinned across calls */ + + CURSOR bt_cursor; /* cursor */ + +#define BT_PUSH(t, p, i) { \ + t->bt_sp->pgno = p; \ + t->bt_sp->index = i; \ + ++t->bt_sp; \ +} +#define BT_POP(t) (t->bt_sp == t->bt_stack ? NULL : --t->bt_sp) +#define BT_CLR(t) (t->bt_sp = t->bt_stack) + EPGNO bt_stack[50]; /* stack of parent pages */ + EPGNO *bt_sp; /* current stack pointer */ + + DBT bt_rkey; /* returned key */ + DBT bt_rdata; /* returned data */ + + int bt_fd; /* tree file descriptor */ + + pgno_t bt_free; /* next free page */ + u_int32_t bt_psize; /* page size */ + indx_t bt_ovflsize; /* cut-off for key/data overflow */ + int bt_lorder; /* byte order */ + /* sorted order */ + enum { NOT, BACK, FORWARD } bt_order; + EPGNO bt_last; /* last insert */ + + /* B: key comparison function */ + int (*bt_cmp) __P((const DBT *, const DBT *)); + /* B: prefix comparison function */ + size_t (*bt_pfx) __P((const DBT *, const DBT *)); + /* R: recno input function */ + int (*bt_irec) __P((struct _btree *, recno_t)); + + FILE *bt_rfp; /* R: record FILE pointer */ + int bt_rfd; /* R: record file descriptor */ + + caddr_t bt_cmap; /* R: current point in mapped space */ + caddr_t bt_smap; /* R: start of mapped space */ + caddr_t bt_emap; /* R: end of mapped space */ + size_t bt_msize; /* R: size of mapped region. */ + + recno_t bt_nrecs; /* R: number of records */ + size_t bt_reclen; /* R: fixed record length */ + u_char bt_bval; /* R: delimiting byte/pad character */ + +/* + * NB: + * B_NODUPS and R_RECNO are stored on disk, and may not be changed. + */ +#define B_INMEM 0x00001 /* in-memory tree */ +#define B_METADIRTY 0x00002 /* need to write metadata */ +#define B_MODIFIED 0x00004 /* tree modified */ +#define B_NEEDSWAP 0x00008 /* if byte order requires swapping */ +#define B_RDONLY 0x00010 /* read-only tree */ + +#define B_NODUPS 0x00020 /* no duplicate keys permitted */ +#define R_RECNO 0x00080 /* record oriented tree */ + +#define R_CLOSEFP 0x00040 /* opened a file pointer */ +#define R_EOF 0x00100 /* end of input file reached. */ +#define R_FIXLEN 0x00200 /* fixed length records */ +#define R_MEMMAPPED 0x00400 /* memory mapped file. */ +#define R_INMEM 0x00800 /* in-memory file */ +#define R_MODIFIED 0x01000 /* modified file */ +#define R_RDONLY 0x02000 /* read-only file */ + +#define B_DB_LOCK 0x04000 /* DB_LOCK specified. */ +#define B_DB_SHMEM 0x08000 /* DB_SHMEM specified. */ +#define B_DB_TXN 0x10000 /* DB_TXN specified. */ + u_int32_t flags; +} BTREE; + +#include "extern.h" diff --git a/main/db1-ast/btree/extern.h b/main/db1-ast/btree/extern.h new file mode 100644 index 000000000..ebd9c5492 --- /dev/null +++ b/main/db1-ast/btree/extern.h @@ -0,0 +1,70 @@ +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)extern.h 8.10 (Berkeley) 7/20/94 + */ + +int __bt_close __P((DB *)); +int __bt_cmp __P((BTREE *, const DBT *, EPG *)); +int __bt_crsrdel __P((BTREE *, EPGNO *)); +int __bt_defcmp __P((const DBT *, const DBT *)); +size_t __bt_defpfx __P((const DBT *, const DBT *)); +int __bt_delete __P((const DB *, const DBT *, u_int)); +int __bt_dleaf __P((BTREE *, const DBT *, PAGE *, u_int)); +int __bt_fd __P((const DB *)); +int __bt_free __P((BTREE *, PAGE *)); +int __bt_get __P((const DB *, const DBT *, DBT *, u_int)); +PAGE *__bt_new __P((BTREE *, pgno_t *)); +void __bt_pgin __P((void *, pgno_t, void *)); +void __bt_pgout __P((void *, pgno_t, void *)); +int __bt_push __P((BTREE *, pgno_t, int)); +int __bt_put __P((const DB *dbp, DBT *, const DBT *, u_int)); +int __bt_ret __P((BTREE *, EPG *, DBT *, DBT *, DBT *, DBT *, int)); +EPG *__bt_search __P((BTREE *, const DBT *, int *)); +int __bt_seq __P((const DB *, DBT *, DBT *, u_int)); +void __bt_setcur __P((BTREE *, pgno_t, u_int)); +int __bt_split __P((BTREE *, PAGE *, + const DBT *, const DBT *, int, size_t, u_int32_t)); +int __bt_sync __P((const DB *, u_int)); + +int __ovfl_delete __P((BTREE *, void *)); +int __ovfl_get __P((BTREE *, void *, size_t *, void **, size_t *)); +int __ovfl_put __P((BTREE *, const DBT *, pgno_t *)); + +#ifdef DEBUG +void __bt_dnpage __P((DB *, pgno_t)); +void __bt_dpage __P((PAGE *)); +void __bt_dump __P((DB *)); +#endif +#ifdef STATISTICS +void __bt_stat __P((DB *)); +#endif diff --git a/main/db1-ast/db/db.c b/main/db1-ast/db/db.c new file mode 100644 index 000000000..c93b36ff7 --- /dev/null +++ b/main/db1-ast/db/db.c @@ -0,0 +1,103 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)db.c 8.4 (Berkeley) 2/21/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include +#include +#include +#include + +#include + +DB * +dbopen(fname, flags, mode, type, openinfo) + const char *fname; + int flags, mode; + DBTYPE type; + const void *openinfo; +{ + +#define DB_FLAGS (DB_LOCK | DB_SHMEM | DB_TXN) +#define USE_OPEN_FLAGS \ + (O_CREAT | O_EXCL | O_EXLOCK | O_NONBLOCK | O_RDONLY | \ + O_RDWR | O_SHLOCK | O_TRUNC) + + if ((flags & ~(USE_OPEN_FLAGS | DB_FLAGS)) == 0) + switch (type) { + case DB_BTREE: + return (__bt_open(fname, flags & USE_OPEN_FLAGS, + mode, openinfo, flags & DB_FLAGS)); + case DB_HASH: + return (__hash_open(fname, flags & USE_OPEN_FLAGS, + mode, openinfo, flags & DB_FLAGS)); + case DB_RECNO: + return (__rec_open(fname, flags & USE_OPEN_FLAGS, + mode, openinfo, flags & DB_FLAGS)); + } + errno = EINVAL; + return (NULL); +} + +static int +__dberr __P((void)) +{ + return (RET_ERROR); +} + +/* + * __DBPANIC -- Stop. + * + * Parameters: + * dbp: pointer to the DB structure. + */ +void +__dbpanic(dbp) + DB *dbp; +{ + /* The only thing that can succeed is a close. */ + dbp->del = (int (*)__P((const struct __db *, + const DBT *, u_int))) __dberr; + dbp->get = (int (*)__P((const struct __db *, + const DBT *, DBT *, u_int))) __dberr; + dbp->put = (int (*)__P((const struct __db *, + DBT *, const DBT *, u_int))) __dberr; + dbp->seq = (int (*)__P((const struct __db *, + DBT *, DBT *, u_int))) __dberr; + dbp->sync = (int (*)__P((const struct __db *, u_int))) __dberr; + dbp->fd = (int (*)__P((const struct __db *))) __dberr; +} diff --git a/main/db1-ast/hash/README b/main/db1-ast/hash/README new file mode 100644 index 000000000..f29ccf7e1 --- /dev/null +++ b/main/db1-ast/hash/README @@ -0,0 +1,72 @@ +# @(#)README 8.1 (Berkeley) 6/4/93 + +This package implements a superset of the hsearch and dbm/ndbm libraries. + +Test Programs: + All test programs which need key/data pairs expect them entered + with key and data on separate lines + + tcreat3.c + Takes + bucketsize (bsize), + fill factor (ffactor), and + initial number of elements (nelem). + Creates a hash table named hashtest containing the + keys/data pairs entered from standard in. + thash4.c + Takes + bucketsize (bsize), + fill factor (ffactor), + initial number of elements (nelem) + bytes of cache (ncached), and + file from which to read data (fname) + Creates a table from the key/data pairs on standard in and + then does a read of each key/data in fname + tdel.c + Takes + bucketsize (bsize), and + fill factor (ffactor). + file from which to read data (fname) + Reads each key/data pair from fname and deletes the + key from the hash table hashtest + tseq.c + Reads the key/data pairs in the file hashtest and writes them + to standard out. + tread2.c + Takes + butes of cache (ncached). + Reads key/data pairs from standard in and looks them up + in the file hashtest. + tverify.c + Reads key/data pairs from standard in, looks them up + in the file hashtest, and verifies that the data is + correct. + +NOTES: + +The file search.h is provided for using the hsearch compatible interface +on BSD systems. On System V derived systems, search.h should appear in +/usr/include. + +The man page ../man/db.3 explains the interface to the hashing system. +The file hash.ps is a postscript copy of a paper explaining +the history, implementation, and performance of the hash package. + +"bugs" or idiosyncracies + +If you have a lot of overflows, it is possible to run out of overflow +pages. Currently, this will cause a message to be printed on stderr. +Eventually, this will be indicated by a return error code. + +If you are using the ndbm interface and exit without flushing or closing the +file, you may lose updates since the package buffers all writes. Also, +the db interface only creates a single database file. To avoid overwriting +the user's original file, the suffix ".db" is appended to the file name +passed to dbm_open. Additionally, if your code "knows" about the historic +.dir and .pag files, it will break. + +There is a fundamental difference between this package and the old hsearch. +Hsearch requires the user to maintain the keys and data in the application's +allocated memory while hash takes care of all storage management. The down +side is that the byte strings passed in the ENTRY structure must be null +terminated (both the keys and the data). diff --git a/main/db1-ast/hash/extern.h b/main/db1-ast/hash/extern.h new file mode 100644 index 000000000..4f1f23d67 --- /dev/null +++ b/main/db1-ast/hash/extern.h @@ -0,0 +1,65 @@ +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)extern.h 8.4 (Berkeley) 6/16/94 + */ + +BUFHEAD *__add_ovflpage __P((HTAB *, BUFHEAD *)); +int __addel __P((HTAB *, BUFHEAD *, const DBT *, const DBT *)); +int __big_delete __P((HTAB *, BUFHEAD *)); +int __big_insert __P((HTAB *, BUFHEAD *, const DBT *, const DBT *)); +int __big_keydata __P((HTAB *, BUFHEAD *, DBT *, DBT *, int)); +int __big_return __P((HTAB *, BUFHEAD *, int, DBT *, int)); +int __big_split __P((HTAB *, BUFHEAD *, BUFHEAD *, BUFHEAD *, + int, u_int32_t, SPLIT_RETURN *)); +int __buf_free __P((HTAB *, int, int)); +void __buf_init __P((HTAB *, int)); +u_int32_t __call_hash __P((HTAB *, char *, int)); +int __delpair __P((HTAB *, BUFHEAD *, int)); +int __expand_table __P((HTAB *)); +int __find_bigpair __P((HTAB *, BUFHEAD *, int, char *, int)); +u_int16_t __find_last_page __P((HTAB *, BUFHEAD **)); +void __free_ovflpage __P((HTAB *, BUFHEAD *)); +BUFHEAD *__get_buf __P((HTAB *, u_int32_t, BUFHEAD *, int)); +int __get_page __P((HTAB *, char *, u_int32_t, int, int, int)); +int __ibitmap __P((HTAB *, int, int, int)); +u_int32_t __hash_log2 __P((u_int32_t)); +int __put_page __P((HTAB *, char *, u_int32_t, int, int)); +void __reclaim_buf __P((HTAB *, BUFHEAD *)); +int __split_page __P((HTAB *, u_int32_t, u_int32_t)); + +/* Default hash routine. */ +extern u_int32_t (*__default_hash) __P((const void *, size_t)); + +#ifdef HASH_STATISTICS +extern int hash_accesses, hash_collisions, hash_expansions, hash_overflows; +#endif diff --git a/main/db1-ast/hash/hash.c b/main/db1-ast/hash/hash.c new file mode 100644 index 000000000..99592eac5 --- /dev/null +++ b/main/db1-ast/hash/hash.c @@ -0,0 +1,999 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Margo Seltzer. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)hash.c 8.9 (Berkeley) 6/16/94"; +#endif /* LIBC_SCCS and not lint */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#ifdef DEBUG +#include +#endif + +#include +#include "hash.h" +#include "page.h" +#include "extern.h" + +static int alloc_segs __P((HTAB *, int)); +static int flush_meta __P((HTAB *)); +static int hash_access __P((HTAB *, ACTION, DBT *, DBT *)); +static int hash_close __P((DB *)); +static int hash_delete __P((const DB *, const DBT *, u_int32_t)); +static int hash_fd __P((const DB *)); +static int hash_get __P((const DB *, const DBT *, DBT *, u_int32_t)); +static int hash_put __P((const DB *, DBT *, const DBT *, u_int32_t)); +static void *hash_realloc __P((SEGMENT **, int, int)); +static int hash_seq __P((const DB *, DBT *, DBT *, u_int32_t)); +static int hash_sync __P((const DB *, u_int32_t)); +static int hdestroy __P((HTAB *)); +static HTAB *init_hash __P((HTAB *, const char *, HASHINFO *)); +static int init_htab __P((HTAB *, int)); +#if BYTE_ORDER == LITTLE_ENDIAN +static void swap_header __P((HTAB *)); +static void swap_header_copy __P((HASHHDR *, HASHHDR *)); +#endif + +/* Fast arithmetic, relying on powers of 2, */ +#define MOD(x, y) ((x) & ((y) - 1)) + +#define RETURN_ERROR(ERR, LOC) { save_errno = ERR; goto LOC; } + +/* Return values */ +#define SUCCESS (0) +#define ERROR (-1) +#define ABNORMAL (1) + +#ifdef HASH_STATISTICS +int hash_accesses, hash_collisions, hash_expansions, hash_overflows; +#endif + +/************************** INTERFACE ROUTINES ***************************/ +/* OPEN/CLOSE */ + +extern DB * +__hash_open(file, flags, mode, info, dflags) + const char *file; + int flags, mode, dflags; + const HASHINFO *info; /* Special directives for create */ +{ + HTAB *hashp; + struct stat statbuf; + DB *dbp; + int bpages, hdrsize, new_table, nsegs, save_errno; + + if ((flags & O_ACCMODE) == O_WRONLY) { + errno = EINVAL; + return (NULL); + } + + if (!(hashp = (HTAB *)calloc(1, sizeof(HTAB)))) + return (NULL); + hashp->fp = -1; + + /* + * Even if user wants write only, we need to be able to read + * the actual file, so we need to open it read/write. But, the + * field in the hashp structure needs to be accurate so that + * we can check accesses. + */ + hashp->flags = flags; + + new_table = 0; + if (!file || (flags & O_TRUNC) || + (stat(file, &statbuf) && (errno == ENOENT))) { + if (errno == ENOENT) + errno = 0; /* Just in case someone looks at errno */ + new_table = 1; + } + if (file) { + if ((hashp->fp = open(file, flags, mode)) == -1) + RETURN_ERROR(errno, error0); + (void)fcntl(hashp->fp, F_SETFD, 1); + } + if (new_table) { + if (!(hashp = init_hash(hashp, file, (HASHINFO *)info))) + RETURN_ERROR(errno, error1); + } else { + /* Table already exists */ + if (info && info->hash) + hashp->hash = info->hash; + else + hashp->hash = __default_hash; + + hdrsize = read(hashp->fp, &hashp->hdr, sizeof(HASHHDR)); +#if BYTE_ORDER == LITTLE_ENDIAN + swap_header(hashp); +#endif + if (hdrsize == -1) + RETURN_ERROR(errno, error1); + if (hdrsize != sizeof(HASHHDR)) + RETURN_ERROR(EFTYPE, error1); + /* Verify file type, versions and hash function */ + if (hashp->MAGIC != HASHMAGIC) + RETURN_ERROR(EFTYPE, error1); +#define OLDHASHVERSION 1 + if (hashp->VERSION != HASHVERSION && + hashp->VERSION != OLDHASHVERSION) + RETURN_ERROR(EFTYPE, error1); + if (hashp->hash(CHARKEY, sizeof(CHARKEY)) + != (u_int32_t) hashp->H_CHARKEY) + RETURN_ERROR(EFTYPE, error1); + /* + * Figure out how many segments we need. Max_Bucket is the + * maximum bucket number, so the number of buckets is + * max_bucket + 1. + */ + nsegs = (hashp->MAX_BUCKET + 1 + hashp->SGSIZE - 1) / + hashp->SGSIZE; + hashp->nsegs = 0; + if (alloc_segs(hashp, nsegs)) + /* + * If alloc_segs fails, table will have been destroyed + * and errno will have been set. + */ + return (NULL); + /* Read in bitmaps */ + bpages = (hashp->SPARES[hashp->OVFL_POINT] + + (hashp->BSIZE << BYTE_SHIFT) - 1) >> + (hashp->BSHIFT + BYTE_SHIFT); + + hashp->nmaps = bpages; + (void)memset(&hashp->mapp[0], 0, bpages * sizeof(u_int32_t *)); + } + + /* Initialize Buffer Manager */ + if (info && info->cachesize) + __buf_init(hashp, info->cachesize); + else + __buf_init(hashp, DEF_BUFSIZE); + + hashp->new_file = new_table; + hashp->save_file = file && (hashp->flags & O_ACCMODE) != O_RDONLY; + hashp->cbucket = -1; + if (!(dbp = (DB *)malloc(sizeof(DB)))) { + save_errno = errno; + hdestroy(hashp); + errno = save_errno; + return (NULL); + } + dbp->internal = hashp; + dbp->close = hash_close; + dbp->del = hash_delete; + dbp->fd = hash_fd; + dbp->get = hash_get; + dbp->put = hash_put; + dbp->seq = hash_seq; + dbp->sync = hash_sync; + dbp->type = DB_HASH; + +#ifdef DEBUG + (void)fprintf(stderr, +"%s\n%s%x\n%s%d\n%s%d\n%s%d\n%s%d\n%s%d\n%s%d\n%s%d\n%s%d\n%s%d\n%s%x\n%s%x\n%s%d\n%s%d\n", + "init_htab:", + "TABLE POINTER ", hashp, + "BUCKET SIZE ", hashp->BSIZE, + "BUCKET SHIFT ", hashp->BSHIFT, + "DIRECTORY SIZE ", hashp->DSIZE, + "SEGMENT SIZE ", hashp->SGSIZE, + "SEGMENT SHIFT ", hashp->SSHIFT, + "FILL FACTOR ", hashp->FFACTOR, + "MAX BUCKET ", hashp->MAX_BUCKET, + "OVFL POINT ", hashp->OVFL_POINT, + "LAST FREED ", hashp->LAST_FREED, + "HIGH MASK ", hashp->HIGH_MASK, + "LOW MASK ", hashp->LOW_MASK, + "NSEGS ", hashp->nsegs, + "NKEYS ", hashp->NKEYS); +#endif +#ifdef HASH_STATISTICS + hash_overflows = hash_accesses = hash_collisions = hash_expansions = 0; +#endif + return (dbp); + +error1: + if (hashp != NULL) + (void)close(hashp->fp); + +error0: + free(hashp); + errno = save_errno; + return (NULL); +} + +static int +hash_close(dbp) + DB *dbp; +{ + HTAB *hashp; + int retval; + + if (!dbp) + return (ERROR); + + hashp = (HTAB *)dbp->internal; + retval = hdestroy(hashp); + free(dbp); + return (retval); +} + +static int +hash_fd(dbp) + const DB *dbp; +{ + HTAB *hashp; + + if (!dbp) + return (ERROR); + + hashp = (HTAB *)dbp->internal; + if (hashp->fp == -1) { + errno = ENOENT; + return (-1); + } + return (hashp->fp); +} + +/************************** LOCAL CREATION ROUTINES **********************/ +static HTAB * +init_hash(hashp, file, info) + HTAB *hashp; + const char *file; + HASHINFO *info; +{ +#ifdef _STATBUF_ST_BLKSIZE + struct stat statbuf; +#endif + int nelem; + + nelem = 1; + hashp->NKEYS = 0; + hashp->LORDER = BYTE_ORDER; + hashp->BSIZE = DEF_BUCKET_SIZE; + hashp->BSHIFT = DEF_BUCKET_SHIFT; + hashp->SGSIZE = DEF_SEGSIZE; + hashp->SSHIFT = DEF_SEGSIZE_SHIFT; + hashp->DSIZE = DEF_DIRSIZE; + hashp->FFACTOR = DEF_FFACTOR; + hashp->hash = __default_hash; + memset(hashp->SPARES, 0, sizeof(hashp->SPARES)); + memset(hashp->BITMAPS, 0, sizeof (hashp->BITMAPS)); + + /* Fix bucket size to be optimal for file system */ +#ifdef _STATBUF_ST_BLKSIZE + if (file != NULL) { + if (stat(file, &statbuf)) + return (NULL); + hashp->BSIZE = statbuf.st_blksize; + hashp->BSHIFT = __hash_log2(hashp->BSIZE); + } +#endif + + if (info) { + if (info->bsize) { + /* Round pagesize up to power of 2 */ + hashp->BSHIFT = __hash_log2(info->bsize); + hashp->BSIZE = 1 << hashp->BSHIFT; + if (hashp->BSIZE > MAX_BSIZE) { + errno = EINVAL; + return (NULL); + } + } + if (info->ffactor) + hashp->FFACTOR = info->ffactor; + if (info->hash) + hashp->hash = info->hash; + if (info->nelem) + nelem = info->nelem; + if (info->lorder) { + if (info->lorder != BIG_ENDIAN && + info->lorder != LITTLE_ENDIAN) { + errno = EINVAL; + return (NULL); + } + hashp->LORDER = info->lorder; + } + } + /* init_htab should destroy the table and set errno if it fails */ + if (init_htab(hashp, nelem)) + return (NULL); + else + return (hashp); +} +/* + * This calls alloc_segs which may run out of memory. Alloc_segs will destroy + * the table and set errno, so we just pass the error information along. + * + * Returns 0 on No Error + */ +static int +init_htab(hashp, nelem) + HTAB *hashp; + int nelem; +{ + register int nbuckets, nsegs; + int l2; + + /* + * Divide number of elements by the fill factor and determine a + * desired number of buckets. Allocate space for the next greater + * power of two number of buckets. + */ + nelem = (nelem - 1) / hashp->FFACTOR + 1; + + l2 = __hash_log2(MAX(nelem, 2)); + nbuckets = 1 << l2; + + hashp->SPARES[l2] = l2 + 1; + hashp->SPARES[l2 + 1] = l2 + 1; + hashp->OVFL_POINT = l2; + hashp->LAST_FREED = 2; + + /* First bitmap page is at: splitpoint l2 page offset 1 */ + if (__ibitmap(hashp, OADDR_OF(l2, 1), l2 + 1, 0)) + return (-1); + + hashp->MAX_BUCKET = hashp->LOW_MASK = nbuckets - 1; + hashp->HIGH_MASK = (nbuckets << 1) - 1; + hashp->HDRPAGES = ((MAX(sizeof(HASHHDR), MINHDRSIZE) - 1) >> + hashp->BSHIFT) + 1; + + nsegs = (nbuckets - 1) / hashp->SGSIZE + 1; + nsegs = 1 << __hash_log2(nsegs); + + if (nsegs > hashp->DSIZE) + hashp->DSIZE = nsegs; + return (alloc_segs(hashp, nsegs)); +} + +/********************** DESTROY/CLOSE ROUTINES ************************/ + +/* + * Flushes any changes to the file if necessary and destroys the hashp + * structure, freeing all allocated space. + */ +static int +hdestroy(hashp) + HTAB *hashp; +{ + int i, save_errno; + + save_errno = 0; + +#ifdef HASH_STATISTICS + (void)fprintf(stderr, "hdestroy: accesses %ld collisions %ld\n", + hash_accesses, hash_collisions); + (void)fprintf(stderr, "hdestroy: expansions %ld\n", + hash_expansions); + (void)fprintf(stderr, "hdestroy: overflows %ld\n", + hash_overflows); + (void)fprintf(stderr, "keys %ld maxp %d segmentcount %d\n", + hashp->NKEYS, hashp->MAX_BUCKET, hashp->nsegs); + + for (i = 0; i < NCACHED; i++) + (void)fprintf(stderr, + "spares[%d] = %d\n", i, hashp->SPARES[i]); +#endif + /* + * Call on buffer manager to free buffers, and if required, + * write them to disk. + */ + if (__buf_free(hashp, 1, hashp->save_file)) + save_errno = errno; + if (hashp->dir) { + free(*hashp->dir); /* Free initial segments */ + /* Free extra segments */ + while (hashp->exsegs--) + free(hashp->dir[--hashp->nsegs]); + free(hashp->dir); + } + if (flush_meta(hashp) && !save_errno) + save_errno = errno; + /* Free Bigmaps */ + for (i = 0; i < hashp->nmaps; i++) + if (hashp->mapp[i]) + free(hashp->mapp[i]); + + if (hashp->fp != -1) + (void)close(hashp->fp); + + free(hashp); + + if (save_errno) { + errno = save_errno; + return (ERROR); + } + return (SUCCESS); +} +/* + * Write modified pages to disk + * + * Returns: + * 0 == OK + * -1 ERROR + */ +static int +hash_sync(dbp, flags) + const DB *dbp; + u_int32_t flags; +{ + HTAB *hashp; + + if (flags != 0) { + errno = EINVAL; + return (ERROR); + } + + if (!dbp) + return (ERROR); + + hashp = (HTAB *)dbp->internal; + if (!hashp->save_file) + return (0); + if (__buf_free(hashp, 0, 1) || flush_meta(hashp)) + return (ERROR); + hashp->new_file = 0; + return (0); +} + +/* + * Returns: + * 0 == OK + * -1 indicates that errno should be set + */ +static int +flush_meta(hashp) + HTAB *hashp; +{ + HASHHDR *whdrp; +#if BYTE_ORDER == LITTLE_ENDIAN + HASHHDR whdr; +#endif + int fp, i, wsize; + + if (!hashp->save_file) + return (0); + hashp->MAGIC = HASHMAGIC; + hashp->VERSION = HASHVERSION; + hashp->H_CHARKEY = hashp->hash(CHARKEY, sizeof(CHARKEY)); + + fp = hashp->fp; + whdrp = &hashp->hdr; +#if BYTE_ORDER == LITTLE_ENDIAN + whdrp = &whdr; + swap_header_copy(&hashp->hdr, whdrp); +#endif + if ((lseek(fp, (off_t)0, SEEK_SET) == -1) || + ((wsize = write(fp, whdrp, sizeof(HASHHDR))) == -1)) + return (-1); + else + if (wsize != sizeof(HASHHDR)) { + errno = EFTYPE; + hashp->errnum = errno; + return (-1); + } + for (i = 0; i < NCACHED; i++) + if (hashp->mapp[i]) + if (__put_page(hashp, (char *)hashp->mapp[i], + hashp->BITMAPS[i], 0, 1)) + return (-1); + return (0); +} + +/*******************************SEARCH ROUTINES *****************************/ +/* + * All the access routines return + * + * Returns: + * 0 on SUCCESS + * 1 to indicate an external ERROR (i.e. key not found, etc) + * -1 to indicate an internal ERROR (i.e. out of memory, etc) + */ +static int +hash_get(dbp, key, data, flag) + const DB *dbp; + const DBT *key; + DBT *data; + u_int32_t flag; +{ + HTAB *hashp; + + hashp = (HTAB *)dbp->internal; + if (flag) { + hashp->errnum = errno = EINVAL; + return (ERROR); + } + return (hash_access(hashp, HASH_GET, (DBT *)key, data)); +} + +static int +hash_put(dbp, key, data, flag) + const DB *dbp; + DBT *key; + const DBT *data; + u_int32_t flag; +{ + HTAB *hashp; + + hashp = (HTAB *)dbp->internal; + if (flag && flag != R_NOOVERWRITE) { + hashp->errnum = errno = EINVAL; + return (ERROR); + } + if ((hashp->flags & O_ACCMODE) == O_RDONLY) { + hashp->errnum = errno = EPERM; + return (ERROR); + } + return (hash_access(hashp, flag == R_NOOVERWRITE ? + HASH_PUTNEW : HASH_PUT, (DBT *)key, (DBT *)data)); +} + +static int +hash_delete(dbp, key, flag) + const DB *dbp; + const DBT *key; + u_int32_t flag; /* Ignored */ +{ + HTAB *hashp; + + hashp = (HTAB *)dbp->internal; + if (flag && flag != R_CURSOR) { + hashp->errnum = errno = EINVAL; + return (ERROR); + } + if ((hashp->flags & O_ACCMODE) == O_RDONLY) { + hashp->errnum = errno = EPERM; + return (ERROR); + } + return (hash_access(hashp, HASH_DELETE, (DBT *)key, NULL)); +} + +/* + * Assume that hashp has been set in wrapper routine. + */ +static int +hash_access(hashp, action, key, val) + HTAB *hashp; + ACTION action; + DBT *key, *val; +{ + register BUFHEAD *rbufp; + BUFHEAD *bufp, *save_bufp; + register u_int16_t *bp; + register int n, ndx, off, size; + register char *kp; + u_int16_t pageno; + +#ifdef HASH_STATISTICS + hash_accesses++; +#endif + + off = hashp->BSIZE; + size = key->size; + kp = (char *)key->data; + rbufp = __get_buf(hashp, __call_hash(hashp, kp, size), NULL, 0); + if (!rbufp) + return (ERROR); + save_bufp = rbufp; + + /* Pin the bucket chain */ + rbufp->flags |= BUF_PIN; + for (bp = (u_int16_t *)rbufp->page, n = *bp++, ndx = 1; ndx < n;) + if (bp[1] >= REAL_KEY) { + /* Real key/data pair */ + if (size == off - *bp && + memcmp(kp, rbufp->page + *bp, size) == 0) + goto found; + off = bp[1]; +#ifdef HASH_STATISTICS + hash_collisions++; +#endif + bp += 2; + ndx += 2; + } else if (bp[1] == OVFLPAGE) { + rbufp = __get_buf(hashp, *bp, rbufp, 0); + if (!rbufp) { + save_bufp->flags &= ~BUF_PIN; + return (ERROR); + } + /* FOR LOOP INIT */ + bp = (u_int16_t *)rbufp->page; + n = *bp++; + ndx = 1; + off = hashp->BSIZE; + } else if (bp[1] < REAL_KEY) { + if ((ndx = + __find_bigpair(hashp, rbufp, ndx, kp, size)) > 0) + goto found; + if (ndx == -2) { + bufp = rbufp; + if (!(pageno = + __find_last_page(hashp, &bufp))) { + ndx = 0; + rbufp = bufp; + break; /* FOR */ + } + rbufp = __get_buf(hashp, pageno, bufp, 0); + if (!rbufp) { + save_bufp->flags &= ~BUF_PIN; + return (ERROR); + } + /* FOR LOOP INIT */ + bp = (u_int16_t *)rbufp->page; + n = *bp++; + ndx = 1; + off = hashp->BSIZE; + } else { + save_bufp->flags &= ~BUF_PIN; + return (ERROR); + } + } + + /* Not found */ + switch (action) { + case HASH_PUT: + case HASH_PUTNEW: + if (__addel(hashp, rbufp, key, val)) { + save_bufp->flags &= ~BUF_PIN; + return (ERROR); + } else { + save_bufp->flags &= ~BUF_PIN; + return (SUCCESS); + } + case HASH_GET: + case HASH_DELETE: + default: + save_bufp->flags &= ~BUF_PIN; + return (ABNORMAL); + } + +found: + switch (action) { + case HASH_PUTNEW: + save_bufp->flags &= ~BUF_PIN; + return (ABNORMAL); + case HASH_GET: + bp = (u_int16_t *)rbufp->page; + if (bp[ndx + 1] < REAL_KEY) { + if (__big_return(hashp, rbufp, ndx, val, 0)) + return (ERROR); + } else { + val->data = (u_char *)rbufp->page + (int)bp[ndx + 1]; + val->size = bp[ndx] - bp[ndx + 1]; + } + break; + case HASH_PUT: + if ((__delpair(hashp, rbufp, ndx)) || + (__addel(hashp, rbufp, key, val))) { + save_bufp->flags &= ~BUF_PIN; + return (ERROR); + } + break; + case HASH_DELETE: + if (__delpair(hashp, rbufp, ndx)) + return (ERROR); + break; + default: + abort(); + } + save_bufp->flags &= ~BUF_PIN; + return (SUCCESS); +} + +static int +hash_seq(dbp, key, data, flag) + const DB *dbp; + DBT *key, *data; + u_int32_t flag; +{ + register u_int32_t bucket; + register BUFHEAD *bufp; + HTAB *hashp; + u_int16_t *bp, ndx; + + hashp = (HTAB *)dbp->internal; + if (flag && flag != R_FIRST && flag != R_NEXT) { + hashp->errnum = errno = EINVAL; + return (ERROR); + } +#ifdef HASH_STATISTICS + hash_accesses++; +#endif + if ((hashp->cbucket < 0) || (flag == R_FIRST)) { + hashp->cbucket = 0; + hashp->cndx = 1; + hashp->cpage = NULL; + } + + for (bp = NULL; !bp || !bp[0]; ) { + if (!(bufp = hashp->cpage)) { + for (bucket = hashp->cbucket; + bucket <= (u_int32_t) hashp->MAX_BUCKET; + bucket++, hashp->cndx = 1) { + bufp = __get_buf(hashp, bucket, NULL, 0); + if (!bufp) + return (ERROR); + hashp->cpage = bufp; + bp = (u_int16_t *)bufp->page; + if (bp[0]) + break; + } + hashp->cbucket = bucket; + if (hashp->cbucket > hashp->MAX_BUCKET) { + hashp->cbucket = -1; + return (ABNORMAL); + } + } else + bp = (u_int16_t *)hashp->cpage->page; + +#ifdef DEBUG + assert(bp); + assert(bufp); +#endif + while (bp[hashp->cndx + 1] == OVFLPAGE) { + bufp = hashp->cpage = + __get_buf(hashp, bp[hashp->cndx], bufp, 0); + if (!bufp) + return (ERROR); + bp = (u_int16_t *)(bufp->page); + hashp->cndx = 1; + } + if (!bp[0]) { + hashp->cpage = NULL; + ++hashp->cbucket; + } + } + ndx = hashp->cndx; + if (bp[ndx + 1] < REAL_KEY) { + if (__big_keydata(hashp, bufp, key, data, 1)) + return (ERROR); + } else { + key->data = (u_char *)hashp->cpage->page + bp[ndx]; + key->size = (ndx > 1 ? bp[ndx - 1] : hashp->BSIZE) - bp[ndx]; + data->data = (u_char *)hashp->cpage->page + bp[ndx + 1]; + data->size = bp[ndx] - bp[ndx + 1]; + ndx += 2; + if (ndx > bp[0]) { + hashp->cpage = NULL; + hashp->cbucket++; + hashp->cndx = 1; + } else + hashp->cndx = ndx; + } + return (SUCCESS); +} + +/********************************* UTILITIES ************************/ + +/* + * Returns: + * 0 ==> OK + * -1 ==> Error + */ +extern int +__expand_table(hashp) + HTAB *hashp; +{ + u_int32_t old_bucket, new_bucket; + int dirsize, new_segnum, spare_ndx; + +#ifdef HASH_STATISTICS + hash_expansions++; +#endif + new_bucket = ++hashp->MAX_BUCKET; + old_bucket = (hashp->MAX_BUCKET & hashp->LOW_MASK); + + new_segnum = new_bucket >> hashp->SSHIFT; + + /* Check if we need a new segment */ + if (new_segnum >= hashp->nsegs) { + /* Check if we need to expand directory */ + if (new_segnum >= hashp->DSIZE) { + /* Reallocate directory */ + dirsize = hashp->DSIZE * sizeof(SEGMENT *); + if (!hash_realloc(&hashp->dir, dirsize, dirsize << 1)) + return (-1); + hashp->DSIZE = dirsize << 1; + } + if ((hashp->dir[new_segnum] = + (SEGMENT)calloc(hashp->SGSIZE, sizeof(SEGMENT))) == NULL) + return (-1); + hashp->exsegs++; + hashp->nsegs++; + } + /* + * If the split point is increasing (MAX_BUCKET's log base 2 + * * increases), we need to copy the current contents of the spare + * split bucket to the next bucket. + */ + spare_ndx = __hash_log2(hashp->MAX_BUCKET + 1); + if (spare_ndx > hashp->OVFL_POINT) { + hashp->SPARES[spare_ndx] = hashp->SPARES[hashp->OVFL_POINT]; + hashp->OVFL_POINT = spare_ndx; + } + + if (new_bucket > (u_int32_t) hashp->HIGH_MASK) { + /* Starting a new doubling */ + hashp->LOW_MASK = hashp->HIGH_MASK; + hashp->HIGH_MASK = new_bucket | hashp->LOW_MASK; + } + /* Relocate records to the new bucket */ + return (__split_page(hashp, old_bucket, new_bucket)); +} + +/* + * If realloc guarantees that the pointer is not destroyed if the realloc + * fails, then this routine can go away. + */ +static void * +hash_realloc(p_ptr, oldsize, newsize) + SEGMENT **p_ptr; + int oldsize, newsize; +{ + register void *p; + + if ((p = malloc(newsize))) { + memmove(p, *p_ptr, oldsize); + memset((char *)p + oldsize, 0, newsize - oldsize); + free(*p_ptr); + *p_ptr = p; + } + return (p); +} + +extern u_int32_t +__call_hash(hashp, k, len) + HTAB *hashp; + char *k; + int len; +{ + int n, bucket; + + n = hashp->hash(k, len); + bucket = n & hashp->HIGH_MASK; + if (bucket > hashp->MAX_BUCKET) + bucket = bucket & hashp->LOW_MASK; + return (bucket); +} + +/* + * Allocate segment table. On error, destroy the table and set errno. + * + * Returns 0 on success + */ +static int +alloc_segs(hashp, nsegs) + HTAB *hashp; + int nsegs; +{ + register int i; + register SEGMENT store; + + int save_errno; + + if ((hashp->dir = + (SEGMENT *)calloc(hashp->DSIZE, sizeof(SEGMENT *))) == NULL) { + save_errno = errno; + (void)hdestroy(hashp); + errno = save_errno; + return (-1); + } + /* Allocate segments */ + if ((store = + (SEGMENT)calloc(nsegs << hashp->SSHIFT, sizeof(SEGMENT))) == NULL) { + save_errno = errno; + (void)hdestroy(hashp); + errno = save_errno; + return (-1); + } + for (i = 0; i < nsegs; i++, hashp->nsegs++) + hashp->dir[i] = &store[i << hashp->SSHIFT]; + return (0); +} + +#if BYTE_ORDER == LITTLE_ENDIAN +/* + * Hashp->hdr needs to be byteswapped. + */ +static void +swap_header_copy(srcp, destp) + HASHHDR *srcp, *destp; +{ + int i; + + P_32_COPY(srcp->magic, destp->magic); + P_32_COPY(srcp->version, destp->version); + P_32_COPY(srcp->lorder, destp->lorder); + P_32_COPY(srcp->bsize, destp->bsize); + P_32_COPY(srcp->bshift, destp->bshift); + P_32_COPY(srcp->dsize, destp->dsize); + P_32_COPY(srcp->ssize, destp->ssize); + P_32_COPY(srcp->sshift, destp->sshift); + P_32_COPY(srcp->ovfl_point, destp->ovfl_point); + P_32_COPY(srcp->last_freed, destp->last_freed); + P_32_COPY(srcp->max_bucket, destp->max_bucket); + P_32_COPY(srcp->high_mask, destp->high_mask); + P_32_COPY(srcp->low_mask, destp->low_mask); + P_32_COPY(srcp->ffactor, destp->ffactor); + P_32_COPY(srcp->nkeys, destp->nkeys); + P_32_COPY(srcp->hdrpages, destp->hdrpages); + P_32_COPY(srcp->h_charkey, destp->h_charkey); + for (i = 0; i < NCACHED; i++) { + P_32_COPY(srcp->spares[i], destp->spares[i]); + P_16_COPY(srcp->bitmaps[i], destp->bitmaps[i]); + } +} + +static void +swap_header(hashp) + HTAB *hashp; +{ + HASHHDR *hdrp; + int i; + + hdrp = &hashp->hdr; + + M_32_SWAP(hdrp->magic); + M_32_SWAP(hdrp->version); + M_32_SWAP(hdrp->lorder); + M_32_SWAP(hdrp->bsize); + M_32_SWAP(hdrp->bshift); + M_32_SWAP(hdrp->dsize); + M_32_SWAP(hdrp->ssize); + M_32_SWAP(hdrp->sshift); + M_32_SWAP(hdrp->ovfl_point); + M_32_SWAP(hdrp->last_freed); + M_32_SWAP(hdrp->max_bucket); + M_32_SWAP(hdrp->high_mask); + M_32_SWAP(hdrp->low_mask); + M_32_SWAP(hdrp->ffactor); + M_32_SWAP(hdrp->nkeys); + M_32_SWAP(hdrp->hdrpages); + M_32_SWAP(hdrp->h_charkey); + for (i = 0; i < NCACHED; i++) { + M_32_SWAP(hdrp->spares[i]); + M_16_SWAP(hdrp->bitmaps[i]); + } +} +#endif diff --git a/main/db1-ast/hash/hash.h b/main/db1-ast/hash/hash.h new file mode 100644 index 000000000..d07db6f07 --- /dev/null +++ b/main/db1-ast/hash/hash.h @@ -0,0 +1,293 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Margo Seltzer. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)hash.h 8.3 (Berkeley) 5/31/94 + */ + +/* Operations */ +typedef enum { + HASH_GET, HASH_PUT, HASH_PUTNEW, HASH_DELETE, HASH_FIRST, HASH_NEXT +} ACTION; + +/* Buffer Management structures */ +typedef struct _bufhead BUFHEAD; + +struct _bufhead { + BUFHEAD *prev; /* LRU links */ + BUFHEAD *next; /* LRU links */ + BUFHEAD *ovfl; /* Overflow page buffer header */ + u_int32_t addr; /* Address of this page */ + char *page; /* Actual page data */ + char flags; +#define BUF_MOD 0x0001 +#define BUF_DISK 0x0002 +#define BUF_BUCKET 0x0004 +#define BUF_PIN 0x0008 +}; + +#define IS_BUCKET(X) ((X) & BUF_BUCKET) + +typedef BUFHEAD **SEGMENT; + +/* Hash Table Information */ +typedef struct hashhdr { /* Disk resident portion */ + int magic; /* Magic NO for hash tables */ + int version; /* Version ID */ + u_int32_t lorder; /* Byte Order */ + int bsize; /* Bucket/Page Size */ + int bshift; /* Bucket shift */ + int dsize; /* Directory Size */ + int ssize; /* Segment Size */ + int sshift; /* Segment shift */ + int ovfl_point; /* Where overflow pages are being + * allocated */ + int last_freed; /* Last overflow page freed */ + int max_bucket; /* ID of Maximum bucket in use */ + int high_mask; /* Mask to modulo into entire table */ + int low_mask; /* Mask to modulo into lower half of + * table */ + int ffactor; /* Fill factor */ + int nkeys; /* Number of keys in hash table */ + int hdrpages; /* Size of table header */ + int h_charkey; /* value of hash(CHARKEY) */ +#define NCACHED 32 /* number of bit maps and spare + * points */ + int spares[NCACHED];/* spare pages for overflow */ + u_int16_t bitmaps[NCACHED]; /* address of overflow page + * bitmaps */ +} HASHHDR; + +typedef struct htab { /* Memory resident data structure */ + HASHHDR hdr; /* Header */ + int nsegs; /* Number of allocated segments */ + int exsegs; /* Number of extra allocated + * segments */ + u_int32_t /* Hash function */ + (*hash)__P((const void *, size_t)); + int flags; /* Flag values */ + int fp; /* File pointer */ + char *tmp_buf; /* Temporary Buffer for BIG data */ + char *tmp_key; /* Temporary Buffer for BIG keys */ + BUFHEAD *cpage; /* Current page */ + int cbucket; /* Current bucket */ + int cndx; /* Index of next item on cpage */ + int errnum; /* Error Number -- for DBM + * compatibility */ + int new_file; /* Indicates if fd is backing store + * or no */ + int save_file; /* Indicates whether we need to flush + * file at + * exit */ + u_int32_t *mapp[NCACHED]; /* Pointers to page maps */ + int nmaps; /* Initial number of bitmaps */ + int nbufs; /* Number of buffers left to + * allocate */ + BUFHEAD bufhead; /* Header of buffer lru list */ + SEGMENT *dir; /* Hash Bucket directory */ +} HTAB; + +/* + * Constants + */ +#define MAX_BSIZE 65536 /* 2^16 */ +#define MIN_BUFFERS 6 +#define MINHDRSIZE 512 +#define DEF_BUFSIZE 65536 /* 64 K */ +#define DEF_BUCKET_SIZE 4096 +#define DEF_BUCKET_SHIFT 12 /* log2(BUCKET) */ +#define DEF_SEGSIZE 256 +#define DEF_SEGSIZE_SHIFT 8 /* log2(SEGSIZE) */ +#define DEF_DIRSIZE 256 +#define DEF_FFACTOR 65536 +#define MIN_FFACTOR 4 +#define SPLTMAX 8 +#define CHARKEY "%$sniglet^&" +#define NUMKEY 1038583 +#define BYTE_SHIFT 3 +#define INT_TO_BYTE 2 +#define INT_BYTE_SHIFT 5 +#define ALL_SET ((u_int32_t)0xFFFFFFFF) +#define ALL_CLEAR 0 + +#define PTROF(X) ((BUFHEAD *)((ptrdiff_t)(X)&~0x3)) +#define ISMOD(X) ((u_int32_t)(ptrdiff_t)(X)&0x1) +#define DOMOD(X) ((X) = (char *)((ptrdiff_t)(X)|0x1)) +#define ISDISK(X) ((u_int32_t)(ptrdiff_t)(X)&0x2) +#define DODISK(X) ((X) = (char *)((ptrdiff_t)(X)|0x2)) + +#define BITS_PER_MAP 32 + +/* Given the address of the beginning of a big map, clear/set the nth bit */ +#define CLRBIT(A, N) ((A)[(N)/BITS_PER_MAP] &= ~(1<<((N)%BITS_PER_MAP))) +#define SETBIT(A, N) ((A)[(N)/BITS_PER_MAP] |= (1<<((N)%BITS_PER_MAP))) +#define ISSET(A, N) ((A)[(N)/BITS_PER_MAP] & (1<<((N)%BITS_PER_MAP))) + +/* Overflow management */ +/* + * Overflow page numbers are allocated per split point. At each doubling of + * the table, we can allocate extra pages. So, an overflow page number has + * the top 5 bits indicate which split point and the lower 11 bits indicate + * which page at that split point is indicated (pages within split points are + * numberered starting with 1). + */ + +#define SPLITSHIFT 11 +#define SPLITMASK 0x7FF +#define SPLITNUM(N) (((u_int32_t)(N)) >> SPLITSHIFT) +#define OPAGENUM(N) ((N) & SPLITMASK) +#define OADDR_OF(S,O) ((u_int32_t)((u_int32_t)(S) << SPLITSHIFT) + (O)) + +#define BUCKET_TO_PAGE(B) \ + (B) + hashp->HDRPAGES + ((B) ? hashp->SPARES[__hash_log2((B)+1)-1] : 0) +#define OADDR_TO_PAGE(B) \ + BUCKET_TO_PAGE ( (1 << SPLITNUM((B))) -1 ) + OPAGENUM((B)); + +/* + * page.h contains a detailed description of the page format. + * + * Normally, keys and data are accessed from offset tables in the top of + * each page which point to the beginning of the key and data. There are + * four flag values which may be stored in these offset tables which indicate + * the following: + * + * + * OVFLPAGE Rather than a key data pair, this pair contains + * the address of an overflow page. The format of + * the pair is: + * OVERFLOW_PAGE_NUMBER OVFLPAGE + * + * PARTIAL_KEY This must be the first key/data pair on a page + * and implies that page contains only a partial key. + * That is, the key is too big to fit on a single page + * so it starts on this page and continues on the next. + * The format of the page is: + * KEY_OFF PARTIAL_KEY OVFL_PAGENO OVFLPAGE + * + * KEY_OFF -- offset of the beginning of the key + * PARTIAL_KEY -- 1 + * OVFL_PAGENO - page number of the next overflow page + * OVFLPAGE -- 0 + * + * FULL_KEY This must be the first key/data pair on the page. It + * is used in two cases. + * + * Case 1: + * There is a complete key on the page but no data + * (because it wouldn't fit). The next page contains + * the data. + * + * Page format it: + * KEY_OFF FULL_KEY OVFL_PAGENO OVFL_PAGE + * + * KEY_OFF -- offset of the beginning of the key + * FULL_KEY -- 2 + * OVFL_PAGENO - page number of the next overflow page + * OVFLPAGE -- 0 + * + * Case 2: + * This page contains no key, but part of a large + * data field, which is continued on the next page. + * + * Page format it: + * DATA_OFF FULL_KEY OVFL_PAGENO OVFL_PAGE + * + * KEY_OFF -- offset of the beginning of the data on + * this page + * FULL_KEY -- 2 + * OVFL_PAGENO - page number of the next overflow page + * OVFLPAGE -- 0 + * + * FULL_KEY_DATA + * This must be the first key/data pair on the page. + * There are two cases: + * + * Case 1: + * This page contains a key and the beginning of the + * data field, but the data field is continued on the + * next page. + * + * Page format is: + * KEY_OFF FULL_KEY_DATA OVFL_PAGENO DATA_OFF + * + * KEY_OFF -- offset of the beginning of the key + * FULL_KEY_DATA -- 3 + * OVFL_PAGENO - page number of the next overflow page + * DATA_OFF -- offset of the beginning of the data + * + * Case 2: + * This page contains the last page of a big data pair. + * There is no key, only the tail end of the data + * on this page. + * + * Page format is: + * DATA_OFF FULL_KEY_DATA + * + * DATA_OFF -- offset of the beginning of the data on + * this page + * FULL_KEY_DATA -- 3 + * OVFL_PAGENO - page number of the next overflow page + * OVFLPAGE -- 0 + * + * OVFL_PAGENO and OVFLPAGE are optional (they are + * not present if there is no next page). + */ + +#define OVFLPAGE 0 +#define PARTIAL_KEY 1 +#define FULL_KEY 2 +#define FULL_KEY_DATA 3 +#define REAL_KEY 4 + +/* Short hands for accessing structure */ +#define BSIZE hdr.bsize +#define BSHIFT hdr.bshift +#define DSIZE hdr.dsize +#define SGSIZE hdr.ssize +#define SSHIFT hdr.sshift +#define LORDER hdr.lorder +#define OVFL_POINT hdr.ovfl_point +#define LAST_FREED hdr.last_freed +#define MAX_BUCKET hdr.max_bucket +#define FFACTOR hdr.ffactor +#define HIGH_MASK hdr.high_mask +#define LOW_MASK hdr.low_mask +#define NKEYS hdr.nkeys +#define HDRPAGES hdr.hdrpages +#define SPARES hdr.spares +#define BITMAPS hdr.bitmaps +#define VERSION hdr.version +#define MAGIC hdr.magic +#define NEXT_FREE hdr.next_free +#define H_CHARKEY hdr.h_charkey diff --git a/main/db1-ast/hash/hash_bigkey.c b/main/db1-ast/hash/hash_bigkey.c new file mode 100644 index 000000000..94c64083f --- /dev/null +++ b/main/db1-ast/hash/hash_bigkey.c @@ -0,0 +1,668 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Margo Seltzer. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)hash_bigkey.c 8.3 (Berkeley) 5/31/94"; +#endif /* LIBC_SCCS and not lint */ + +/* + * PACKAGE: hash + * DESCRIPTION: + * Big key/data handling for the hashing package. + * + * ROUTINES: + * External + * __big_keydata + * __big_split + * __big_insert + * __big_return + * __big_delete + * __find_last_page + * Internal + * collect_key + * collect_data + */ + +#include + +#include +#include +#include +#include + +#ifdef DEBUG +#include +#endif + +#include +#include "hash.h" +#include "page.h" +#include "extern.h" + +static int collect_key __P((HTAB *, BUFHEAD *, int, DBT *, int)); +static int collect_data __P((HTAB *, BUFHEAD *, int, int)); + +/* + * Big_insert + * + * You need to do an insert and the key/data pair is too big + * + * Returns: + * 0 ==> OK + *-1 ==> ERROR + */ +extern int +__big_insert(hashp, bufp, key, val) + HTAB *hashp; + BUFHEAD *bufp; + const DBT *key, *val; +{ + register u_int16_t *p; + int key_size, n, val_size; + u_int16_t space, move_bytes, off; + char *cp, *key_data, *val_data; + + cp = bufp->page; /* Character pointer of p. */ + p = (u_int16_t *)cp; + + key_data = (char *)key->data; + key_size = key->size; + val_data = (char *)val->data; + val_size = val->size; + + /* First move the Key */ + for (space = FREESPACE(p) - BIGOVERHEAD; key_size; + space = FREESPACE(p) - BIGOVERHEAD) { + move_bytes = MIN(space, key_size); + off = OFFSET(p) - move_bytes; + memmove(cp + off, key_data, move_bytes); + key_size -= move_bytes; + key_data += move_bytes; + n = p[0]; + p[++n] = off; + p[0] = ++n; + FREESPACE(p) = off - PAGE_META(n); + OFFSET(p) = off; + p[n] = PARTIAL_KEY; + bufp = __add_ovflpage(hashp, bufp); + if (!bufp) + return (-1); + n = p[0]; + if (!key_size) { + if (FREESPACE(p)) { + move_bytes = MIN(FREESPACE(p), val_size); + off = OFFSET(p) - move_bytes; + p[n] = off; + memmove(cp + off, val_data, move_bytes); + val_data += move_bytes; + val_size -= move_bytes; + p[n - 2] = FULL_KEY_DATA; + FREESPACE(p) = FREESPACE(p) - move_bytes; + OFFSET(p) = off; + } else + p[n - 2] = FULL_KEY; + } + p = (u_int16_t *)bufp->page; + cp = bufp->page; + bufp->flags |= BUF_MOD; + } + + /* Now move the data */ + for (space = FREESPACE(p) - BIGOVERHEAD; val_size; + space = FREESPACE(p) - BIGOVERHEAD) { + move_bytes = MIN(space, val_size); + /* + * Here's the hack to make sure that if the data ends on the + * same page as the key ends, FREESPACE is at least one. + */ + if ((int) space == val_size && (size_t) val_size == val->size) + move_bytes--; + off = OFFSET(p) - move_bytes; + memmove(cp + off, val_data, move_bytes); + val_size -= move_bytes; + val_data += move_bytes; + n = p[0]; + p[++n] = off; + p[0] = ++n; + FREESPACE(p) = off - PAGE_META(n); + OFFSET(p) = off; + if (val_size) { + p[n] = FULL_KEY; + bufp = __add_ovflpage(hashp, bufp); + if (!bufp) + return (-1); + cp = bufp->page; + p = (u_int16_t *)cp; + } else + p[n] = FULL_KEY_DATA; + bufp->flags |= BUF_MOD; + } + return (0); +} + +/* + * Called when bufp's page contains a partial key (index should be 1) + * + * All pages in the big key/data pair except bufp are freed. We cannot + * free bufp because the page pointing to it is lost and we can't get rid + * of its pointer. + * + * Returns: + * 0 => OK + *-1 => ERROR + */ +extern int +__big_delete(hashp, bufp) + HTAB *hashp; + BUFHEAD *bufp; +{ + register BUFHEAD *last_bfp, *rbufp; + u_int16_t *bp, pageno; + int key_done, n; + + rbufp = bufp; + last_bfp = NULL; + bp = (u_int16_t *)bufp->page; + pageno = 0; + key_done = 0; + + while (!key_done || (bp[2] != FULL_KEY_DATA)) { + if (bp[2] == FULL_KEY || bp[2] == FULL_KEY_DATA) + key_done = 1; + + /* + * If there is freespace left on a FULL_KEY_DATA page, then + * the data is short and fits entirely on this page, and this + * is the last page. + */ + if (bp[2] == FULL_KEY_DATA && FREESPACE(bp)) + break; + pageno = bp[bp[0] - 1]; + rbufp->flags |= BUF_MOD; + rbufp = __get_buf(hashp, pageno, rbufp, 0); + if (last_bfp) + __free_ovflpage(hashp, last_bfp); + last_bfp = rbufp; + if (!rbufp) + return (-1); /* Error. */ + bp = (u_int16_t *)rbufp->page; + } + + /* + * If we get here then rbufp points to the last page of the big + * key/data pair. Bufp points to the first one -- it should now be + * empty pointing to the next page after this pair. Can't free it + * because we don't have the page pointing to it. + */ + + /* This is information from the last page of the pair. */ + n = bp[0]; + pageno = bp[n - 1]; + + /* Now, bp is the first page of the pair. */ + bp = (u_int16_t *)bufp->page; + if (n > 2) { + /* There is an overflow page. */ + bp[1] = pageno; + bp[2] = OVFLPAGE; + bufp->ovfl = rbufp->ovfl; + } else + /* This is the last page. */ + bufp->ovfl = NULL; + n -= 2; + bp[0] = n; + FREESPACE(bp) = hashp->BSIZE - PAGE_META(n); + OFFSET(bp) = hashp->BSIZE - 1; + + bufp->flags |= BUF_MOD; + if (rbufp) + __free_ovflpage(hashp, rbufp); + if (last_bfp && last_bfp != rbufp) + __free_ovflpage(hashp, last_bfp); + + hashp->NKEYS--; + return (0); +} +/* + * Returns: + * 0 = key not found + * -1 = get next overflow page + * -2 means key not found and this is big key/data + * -3 error + */ +extern int +__find_bigpair(hashp, bufp, ndx, key, size) + HTAB *hashp; + BUFHEAD *bufp; + int ndx; + char *key; + int size; +{ + register u_int16_t *bp; + register char *p; + int ksize; + u_int16_t bytes; + char *kkey; + + bp = (u_int16_t *)bufp->page; + p = bufp->page; + ksize = size; + kkey = key; + + for (bytes = hashp->BSIZE - bp[ndx]; + bytes <= size && bp[ndx + 1] == PARTIAL_KEY; + bytes = hashp->BSIZE - bp[ndx]) { + if (memcmp(p + bp[ndx], kkey, bytes)) + return (-2); + kkey += bytes; + ksize -= bytes; + bufp = __get_buf(hashp, bp[ndx + 2], bufp, 0); + if (!bufp) + return (-3); + p = bufp->page; + bp = (u_int16_t *)p; + ndx = 1; + } + + if (bytes != ksize || memcmp(p + bp[ndx], kkey, bytes)) { +#ifdef HASH_STATISTICS + ++hash_collisions; +#endif + return (-2); + } else + return (ndx); +} + +/* + * Given the buffer pointer of the first overflow page of a big pair, + * find the end of the big pair + * + * This will set bpp to the buffer header of the last page of the big pair. + * It will return the pageno of the overflow page following the last page + * of the pair; 0 if there isn't any (i.e. big pair is the last key in the + * bucket) + */ +extern u_int16_t +__find_last_page(hashp, bpp) + HTAB *hashp; + BUFHEAD **bpp; +{ + BUFHEAD *bufp; + u_int16_t *bp, pageno; + int n; + + bufp = *bpp; + bp = (u_int16_t *)bufp->page; + for (;;) { + n = bp[0]; + + /* + * This is the last page if: the tag is FULL_KEY_DATA and + * either only 2 entries OVFLPAGE marker is explicit there + * is freespace on the page. + */ + if (bp[2] == FULL_KEY_DATA && + ((n == 2) || (bp[n] == OVFLPAGE) || (FREESPACE(bp)))) + break; + + pageno = bp[n - 1]; + bufp = __get_buf(hashp, pageno, bufp, 0); + if (!bufp) + return (0); /* Need to indicate an error! */ + bp = (u_int16_t *)bufp->page; + } + + *bpp = bufp; + if (bp[0] > 2) + return (bp[3]); + else + return (0); +} + +/* + * Return the data for the key/data pair that begins on this page at this + * index (index should always be 1). + */ +extern int +__big_return(hashp, bufp, ndx, val, set_current) + HTAB *hashp; + BUFHEAD *bufp; + int ndx; + DBT *val; + int set_current; +{ + BUFHEAD *save_p; + u_int16_t *bp, len, off, save_addr; + char *tp; + + bp = (u_int16_t *)bufp->page; + while (bp[ndx + 1] == PARTIAL_KEY) { + bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); + if (!bufp) + return (-1); + bp = (u_int16_t *)bufp->page; + ndx = 1; + } + + if (bp[ndx + 1] == FULL_KEY) { + bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); + if (!bufp) + return (-1); + bp = (u_int16_t *)bufp->page; + save_p = bufp; + save_addr = save_p->addr; + off = bp[1]; + len = 0; + } else + if (!FREESPACE(bp)) { + /* + * This is a hack. We can't distinguish between + * FULL_KEY_DATA that contains complete data or + * incomplete data, so we require that if the data + * is complete, there is at least 1 byte of free + * space left. + */ + off = bp[bp[0]]; + len = bp[1] - off; + save_p = bufp; + save_addr = bufp->addr; + bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); + if (!bufp) + return (-1); + bp = (u_int16_t *)bufp->page; + } else { + /* The data is all on one page. */ + tp = (char *)bp; + off = bp[bp[0]]; + val->data = (u_char *)tp + off; + val->size = bp[1] - off; + if (set_current) { + if (bp[0] == 2) { /* No more buckets in + * chain */ + hashp->cpage = NULL; + hashp->cbucket++; + hashp->cndx = 1; + } else { + hashp->cpage = __get_buf(hashp, + bp[bp[0] - 1], bufp, 0); + if (!hashp->cpage) + return (-1); + hashp->cndx = 1; + if (!((u_int16_t *) + hashp->cpage->page)[0]) { + hashp->cbucket++; + hashp->cpage = NULL; + } + } + } + return (0); + } + + val->size = collect_data(hashp, bufp, (int)len, set_current); + if (val->size == (size_t) -1) + return (-1); + if (save_p->addr != save_addr) { + /* We are pretty short on buffers. */ + errno = EINVAL; /* OUT OF BUFFERS */ + return (-1); + } + memmove(hashp->tmp_buf, (save_p->page) + off, len); + val->data = (u_char *)hashp->tmp_buf; + return (0); +} +/* + * Count how big the total datasize is by recursing through the pages. Then + * allocate a buffer and copy the data as you recurse up. + */ +static int +collect_data(hashp, bufp, len, set) + HTAB *hashp; + BUFHEAD *bufp; + int len, set; +{ + register u_int16_t *bp; + register char *p; + BUFHEAD *xbp; + u_int16_t save_addr; + int mylen, totlen; + + p = bufp->page; + bp = (u_int16_t *)p; + mylen = hashp->BSIZE - bp[1]; + save_addr = bufp->addr; + + if (bp[2] == FULL_KEY_DATA) { /* End of Data */ + totlen = len + mylen; + if (hashp->tmp_buf) + free(hashp->tmp_buf); + if ((hashp->tmp_buf = (char *)malloc(totlen)) == NULL) + return (-1); + if (set) { + hashp->cndx = 1; + if (bp[0] == 2) { /* No more buckets in chain */ + hashp->cpage = NULL; + hashp->cbucket++; + } else { + hashp->cpage = + __get_buf(hashp, bp[bp[0] - 1], bufp, 0); + if (!hashp->cpage) + return (-1); + else if (!((u_int16_t *)hashp->cpage->page)[0]) { + hashp->cbucket++; + hashp->cpage = NULL; + } + } + } + } else { + xbp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); + if (!xbp || ((totlen = + collect_data(hashp, xbp, len + mylen, set)) < 1)) + return (-1); + } + if (bufp->addr != save_addr) { + errno = EINVAL; /* Out of buffers. */ + return (-1); + } + memmove(&hashp->tmp_buf[len], (bufp->page) + bp[1], mylen); + return (totlen); +} + +/* + * Fill in the key and data for this big pair. + */ +extern int +__big_keydata(hashp, bufp, key, val, set) + HTAB *hashp; + BUFHEAD *bufp; + DBT *key, *val; + int set; +{ + key->size = collect_key(hashp, bufp, 0, val, set); + if (key->size == (size_t) -1) + return (-1); + key->data = (u_char *)hashp->tmp_key; + return (0); +} + +/* + * Count how big the total key size is by recursing through the pages. Then + * collect the data, allocate a buffer and copy the key as you recurse up. + */ +static int +collect_key(hashp, bufp, len, val, set) + HTAB *hashp; + BUFHEAD *bufp; + int len; + DBT *val; + int set; +{ + BUFHEAD *xbp; + char *p; + int mylen, totlen; + u_int16_t *bp, save_addr; + + p = bufp->page; + bp = (u_int16_t *)p; + mylen = hashp->BSIZE - bp[1]; + + save_addr = bufp->addr; + totlen = len + mylen; + if (bp[2] == FULL_KEY || bp[2] == FULL_KEY_DATA) { /* End of Key. */ + if (hashp->tmp_key != NULL) + free(hashp->tmp_key); + if ((hashp->tmp_key = (char *)malloc(totlen)) == NULL) + return (-1); + if (__big_return(hashp, bufp, 1, val, set)) + return (-1); + } else { + xbp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); + if (!xbp || ((totlen = + collect_key(hashp, xbp, totlen, val, set)) < 1)) + return (-1); + } + if (bufp->addr != save_addr) { + errno = EINVAL; /* MIS -- OUT OF BUFFERS */ + return (-1); + } + memmove(&hashp->tmp_key[len], (bufp->page) + bp[1], mylen); + return (totlen); +} + +/* + * Returns: + * 0 => OK + * -1 => error + */ +extern int +__big_split(hashp, op, np, big_keyp, addr, obucket, ret) + HTAB *hashp; + BUFHEAD *op; /* Pointer to where to put keys that go in old bucket */ + BUFHEAD *np; /* Pointer to new bucket page */ + /* Pointer to first page containing the big key/data */ + BUFHEAD *big_keyp; + int addr; /* Address of big_keyp */ + u_int32_t obucket;/* Old Bucket */ + SPLIT_RETURN *ret; +{ + register BUFHEAD *tmpp; + register u_int16_t *tp; + BUFHEAD *bp; + DBT key, val; + u_int32_t change; + u_int16_t free_space, n, off; + + bp = big_keyp; + + /* Now figure out where the big key/data goes */ + if (__big_keydata(hashp, big_keyp, &key, &val, 0)) + return (-1); + change = (__call_hash(hashp, key.data, key.size) != obucket); + + if ((ret->next_addr = __find_last_page(hashp, &big_keyp))) { + if (!(ret->nextp = + __get_buf(hashp, ret->next_addr, big_keyp, 0))) + return (-1);; + } else + ret->nextp = NULL; + + /* Now make one of np/op point to the big key/data pair */ +#ifdef DEBUG + assert(np->ovfl == NULL); +#endif + if (change) + tmpp = np; + else + tmpp = op; + + tmpp->flags |= BUF_MOD; +#ifdef DEBUG1 + (void)fprintf(stderr, + "BIG_SPLIT: %d->ovfl was %d is now %d\n", tmpp->addr, + (tmpp->ovfl ? tmpp->ovfl->addr : 0), (bp ? bp->addr : 0)); +#endif + tmpp->ovfl = bp; /* one of op/np point to big_keyp */ + tp = (u_int16_t *)tmpp->page; +#ifdef DEBUG + assert(FREESPACE(tp) >= OVFLSIZE); +#endif + n = tp[0]; + off = OFFSET(tp); + free_space = FREESPACE(tp); + tp[++n] = (u_int16_t)addr; + tp[++n] = OVFLPAGE; + tp[0] = n; + OFFSET(tp) = off; + FREESPACE(tp) = free_space - OVFLSIZE; + + /* + * Finally, set the new and old return values. BIG_KEYP contains a + * pointer to the last page of the big key_data pair. Make sure that + * big_keyp has no following page (2 elements) or create an empty + * following page. + */ + + ret->newp = np; + ret->oldp = op; + + tp = (u_int16_t *)big_keyp->page; + big_keyp->flags |= BUF_MOD; + if (tp[0] > 2) { + /* + * There may be either one or two offsets on this page. If + * there is one, then the overflow page is linked on normally + * and tp[4] is OVFLPAGE. If there are two, tp[4] contains + * the second offset and needs to get stuffed in after the + * next overflow page is added. + */ + n = tp[4]; + free_space = FREESPACE(tp); + off = OFFSET(tp); + tp[0] -= 2; + FREESPACE(tp) = free_space + OVFLSIZE; + OFFSET(tp) = off; + tmpp = __add_ovflpage(hashp, big_keyp); + if (!tmpp) + return (-1); + tp[4] = n; + } else + tmpp = big_keyp; + + if (change) + ret->newp = tmpp; + else + ret->oldp = tmpp; + return (0); +} diff --git a/main/db1-ast/hash/hash_buf.c b/main/db1-ast/hash/hash_buf.c new file mode 100644 index 000000000..f47a7b08e --- /dev/null +++ b/main/db1-ast/hash/hash_buf.c @@ -0,0 +1,355 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Margo Seltzer. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)hash_buf.c 8.5 (Berkeley) 7/15/94"; +#endif /* LIBC_SCCS and not lint */ + +/* + * PACKAGE: hash + * + * DESCRIPTION: + * Contains buffer management + * + * ROUTINES: + * External + * __buf_init + * __get_buf + * __buf_free + * __reclaim_buf + * Internal + * newbuf + */ + +#include + +#include +#include +#include +#include + +#ifdef DEBUG +#include +#endif + +#include +#include "hash.h" +#include "page.h" +#include "extern.h" + +static BUFHEAD *newbuf __P((HTAB *, u_int32_t, BUFHEAD *)); + +/* Unlink B from its place in the lru */ +#define BUF_REMOVE(B) { \ + (B)->prev->next = (B)->next; \ + (B)->next->prev = (B)->prev; \ +} + +/* Insert B after P */ +#define BUF_INSERT(B, P) { \ + (B)->next = (P)->next; \ + (B)->prev = (P); \ + (P)->next = (B); \ + (B)->next->prev = (B); \ +} + +#define MRU hashp->bufhead.next +#define LRU hashp->bufhead.prev + +#define MRU_INSERT(B) BUF_INSERT((B), &hashp->bufhead) +#define LRU_INSERT(B) BUF_INSERT((B), LRU) + +/* + * We are looking for a buffer with address "addr". If prev_bp is NULL, then + * address is a bucket index. If prev_bp is not NULL, then it points to the + * page previous to an overflow page that we are trying to find. + * + * CAVEAT: The buffer header accessed via prev_bp's ovfl field may no longer + * be valid. Therefore, you must always verify that its address matches the + * address you are seeking. + */ +extern BUFHEAD * +__get_buf(hashp, addr, prev_bp, newpage) + HTAB *hashp; + u_int32_t addr; + BUFHEAD *prev_bp; + int newpage; /* If prev_bp set, indicates a new overflow page. */ +{ + register BUFHEAD *bp; + register u_int32_t is_disk_mask; + register int is_disk, segment_ndx = 0; + SEGMENT segp = 0; + + is_disk = 0; + is_disk_mask = 0; + if (prev_bp) { + bp = prev_bp->ovfl; + if (!bp || (bp->addr != addr)) + bp = NULL; + if (!newpage) + is_disk = BUF_DISK; + } else { + /* Grab buffer out of directory */ + segment_ndx = addr & (hashp->SGSIZE - 1); + + /* valid segment ensured by __call_hash() */ + segp = hashp->dir[addr >> hashp->SSHIFT]; +#ifdef DEBUG + assert(segp != NULL); +#endif + bp = PTROF(segp[segment_ndx]); + is_disk_mask = ISDISK(segp[segment_ndx]); + is_disk = is_disk_mask || !hashp->new_file; + } + + if (!bp) { + bp = newbuf(hashp, addr, prev_bp); + if (!bp || + __get_page(hashp, bp->page, addr, !prev_bp, is_disk, 0)) + return (NULL); + if (!prev_bp) + segp[segment_ndx] = + (BUFHEAD *)((ptrdiff_t)bp | is_disk_mask); + } else { + BUF_REMOVE(bp); + MRU_INSERT(bp); + } + return (bp); +} + +/* + * We need a buffer for this page. Either allocate one, or evict a resident + * one (if we have as many buffers as we're allowed) and put this one in. + * + * If newbuf finds an error (returning NULL), it also sets errno. + */ +static BUFHEAD * +newbuf(hashp, addr, prev_bp) + HTAB *hashp; + u_int32_t addr; + BUFHEAD *prev_bp; +{ + register BUFHEAD *bp; /* The buffer we're going to use */ + register BUFHEAD *xbp; /* Temp pointer */ + register BUFHEAD *next_xbp; + SEGMENT segp; + int segment_ndx; + u_int16_t oaddr, *shortp; + + oaddr = 0; + bp = LRU; + /* + * If LRU buffer is pinned, the buffer pool is too small. We need to + * allocate more buffers. + */ + if (hashp->nbufs || (bp->flags & BUF_PIN)) { + /* Allocate a new one */ + if ((bp = (BUFHEAD *)malloc(sizeof(BUFHEAD))) == NULL) + return (NULL); +#ifdef PURIFY + memset(bp, 0xff, sizeof(BUFHEAD)); +#endif + if ((bp->page = (char *)malloc(hashp->BSIZE)) == NULL) { + free(bp); + return (NULL); + } +#ifdef PURIFY + memset(bp->page, 0xff, hashp->BSIZE); +#endif + if (hashp->nbufs) + hashp->nbufs--; + } else { + /* Kick someone out */ + BUF_REMOVE(bp); + /* + * If this is an overflow page with addr 0, it's already been + * flushed back in an overflow chain and initialized. + */ + if ((bp->addr != 0) || (bp->flags & BUF_BUCKET)) { + /* + * Set oaddr before __put_page so that you get it + * before bytes are swapped. + */ + shortp = (u_int16_t *)bp->page; + if (shortp[0]) + oaddr = shortp[shortp[0] - 1]; + if ((bp->flags & BUF_MOD) && __put_page(hashp, bp->page, + bp->addr, (int)IS_BUCKET(bp->flags), 0)) + return (NULL); + /* + * Update the pointer to this page (i.e. invalidate it). + * + * If this is a new file (i.e. we created it at open + * time), make sure that we mark pages which have been + * written to disk so we retrieve them from disk later, + * rather than allocating new pages. + */ + if (IS_BUCKET(bp->flags)) { + segment_ndx = bp->addr & (hashp->SGSIZE - 1); + segp = hashp->dir[bp->addr >> hashp->SSHIFT]; +#ifdef DEBUG + assert(segp != NULL); +#endif + + if (hashp->new_file && + ((bp->flags & BUF_MOD) || + ISDISK(segp[segment_ndx]))) + segp[segment_ndx] = (BUFHEAD *)BUF_DISK; + else + segp[segment_ndx] = NULL; + } + /* + * Since overflow pages can only be access by means of + * their bucket, free overflow pages associated with + * this bucket. + */ + for (xbp = bp; xbp->ovfl;) { + next_xbp = xbp->ovfl; + xbp->ovfl = 0; + xbp = next_xbp; + + /* Check that ovfl pointer is up date. */ + if (IS_BUCKET(xbp->flags) || + (oaddr != xbp->addr)) + break; + + shortp = (u_int16_t *)xbp->page; + if (shortp[0]) + /* set before __put_page */ + oaddr = shortp[shortp[0] - 1]; + if ((xbp->flags & BUF_MOD) && __put_page(hashp, + xbp->page, xbp->addr, 0, 0)) + return (NULL); + xbp->addr = 0; + xbp->flags = 0; + BUF_REMOVE(xbp); + LRU_INSERT(xbp); + } + } + } + + /* Now assign this buffer */ + bp->addr = addr; +#ifdef DEBUG1 + (void)fprintf(stderr, "NEWBUF1: %d->ovfl was %d is now %d\n", + bp->addr, (bp->ovfl ? bp->ovfl->addr : 0), 0); +#endif + bp->ovfl = NULL; + if (prev_bp) { + /* + * If prev_bp is set, this is an overflow page, hook it in to + * the buffer overflow links. + */ +#ifdef DEBUG1 + (void)fprintf(stderr, "NEWBUF2: %d->ovfl was %d is now %d\n", + prev_bp->addr, (prev_bp->ovfl ? bp->ovfl->addr : 0), + (bp ? bp->addr : 0)); +#endif + prev_bp->ovfl = bp; + bp->flags = 0; + } else + bp->flags = BUF_BUCKET; + MRU_INSERT(bp); + return (bp); +} + +extern void +__buf_init(hashp, nbytes) + HTAB *hashp; + int nbytes; +{ + BUFHEAD *bfp; + int npages; + + bfp = &(hashp->bufhead); + npages = (nbytes + hashp->BSIZE - 1) >> hashp->BSHIFT; + npages = MAX(npages, MIN_BUFFERS); + + hashp->nbufs = npages; + bfp->next = bfp; + bfp->prev = bfp; + /* + * This space is calloc'd so these are already null. + * + * bfp->ovfl = NULL; + * bfp->flags = 0; + * bfp->page = NULL; + * bfp->addr = 0; + */ +} + +extern int +__buf_free(hashp, do_free, to_disk) + HTAB *hashp; + int do_free, to_disk; +{ + BUFHEAD *bp; + + /* Need to make sure that buffer manager has been initialized */ + if (!LRU) + return (0); + for (bp = LRU; bp != &hashp->bufhead;) { + /* Check that the buffer is valid */ + if (bp->addr || IS_BUCKET(bp->flags)) { + if (to_disk && (bp->flags & BUF_MOD) && + __put_page(hashp, bp->page, + bp->addr, IS_BUCKET(bp->flags), 0)) + return (-1); + } + /* Check if we are freeing stuff */ + if (do_free) { + if (bp->page) + free(bp->page); + BUF_REMOVE(bp); + free(bp); + bp = LRU; + } else + bp = bp->prev; + } + return (0); +} + +extern void +__reclaim_buf(hashp, bp) + HTAB *hashp; + BUFHEAD *bp; +{ + bp->ovfl = 0; + bp->addr = 0; + bp->flags = 0; + BUF_REMOVE(bp); + LRU_INSERT(bp); +} diff --git a/main/db1-ast/hash/hash_func.c b/main/db1-ast/hash/hash_func.c new file mode 100644 index 000000000..4ec5def8c --- /dev/null +++ b/main/db1-ast/hash/hash_func.c @@ -0,0 +1,225 @@ +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Margo Seltzer. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)hash_func.c 8.2 (Berkeley) 2/21/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include +#include "hash.h" +#include "page.h" +#include "extern.h" + +/* only one of these can be defined */ +/* #define HASH1_EJB 1 */ +/* #define HASH2_PHONG 1 */ +/* #define HASH3_SDBM 1 */ +#define HASH4_TOREK 1 + +static u_int32_t hashfunc __P((const void *, size_t)); + +/* Global default hash function */ +u_int32_t (*__default_hash) __P((const void *, size_t)) = hashfunc; + +/* + * HASH FUNCTIONS + * + * Assume that we've already split the bucket to which this key hashes, + * calculate that bucket, and check that in fact we did already split it. + * + * This came from ejb's hsearch. + */ + +#ifdef HASH1_EJB + +#define PRIME1 37 +#define PRIME2 1048583 + +static u_int32_t +hashfunc(keyarg, len) + const void *keyarg; + register size_t len; +{ + register const u_char *key; + register u_int32_t h; + + /* Convert string to integer */ + for (key = keyarg, h = 0; len--;) + h = h * PRIME1 ^ (*key++ - ' '); + h %= PRIME2; + return (h); +} + +#endif + +#ifdef HASH2_PHONG +/* + * Phong's linear congruential hash + */ +#define dcharhash(h, c) ((h) = 0x63c63cd9*(h) + 0x9c39c33d + (c)) + +static u_int32_t +hashfunc(keyarg, len) + const void *keyarg; + size_t len; +{ + register const u_char *e, *key; + register u_int32_t h; + register u_char c; + + key = keyarg; + e = key + len; + for (h = 0; key != e;) { + c = *key++; + if (!c && key > e) + break; + dcharhash(h, c); + } + return (h); +} +#endif + +#ifdef HASH3_SDBM +/* + * This is INCREDIBLY ugly, but fast. We break the string up into 8 byte + * units. On the first time through the loop we get the "leftover bytes" + * (strlen % 8). On every other iteration, we perform 8 HASHC's so we handle + * all 8 bytes. Essentially, this saves us 7 cmp & branch instructions. If + * this routine is heavily used enough, it's worth the ugly coding. + * + * OZ's original sdbm hash + */ +static u_int32_t +hashfunc(keyarg, len) + const void *keyarg; + register size_t len; +{ + register const u_char *key; + register size_t loop; + register u_int32_t h; + +#define HASHC h = *key++ + 65599 * h + + h = 0; + key = keyarg; + if (len > 0) { + loop = (len + 8 - 1) >> 3; + + switch (len & (8 - 1)) { + case 0: + do { + HASHC; + /* FALLTHROUGH */ + case 7: + HASHC; + /* FALLTHROUGH */ + case 6: + HASHC; + /* FALLTHROUGH */ + case 5: + HASHC; + /* FALLTHROUGH */ + case 4: + HASHC; + /* FALLTHROUGH */ + case 3: + HASHC; + /* FALLTHROUGH */ + case 2: + HASHC; + /* FALLTHROUGH */ + case 1: + HASHC; + } while (--loop); + } + } + return (h); +} +#endif + +#ifdef HASH4_TOREK +/* Hash function from Chris Torek. */ +static u_int32_t +hashfunc(keyarg, len) + const void *keyarg; + register size_t len; +{ + register const u_char *key; + register size_t loop; + register u_int32_t h; + +#define HASH4a h = (h << 5) - h + *key++; +#define HASH4b h = (h << 5) + h + *key++; +#define HASH4 HASH4b + + h = 0; + key = keyarg; + if (len > 0) { + loop = (len + 8 - 1) >> 3; + + switch (len & (8 - 1)) { + case 0: + do { + HASH4; + /* FALLTHROUGH */ + case 7: + HASH4; + /* FALLTHROUGH */ + case 6: + HASH4; + /* FALLTHROUGH */ + case 5: + HASH4; + /* FALLTHROUGH */ + case 4: + HASH4; + /* FALLTHROUGH */ + case 3: + HASH4; + /* FALLTHROUGH */ + case 2: + HASH4; + /* FALLTHROUGH */ + case 1: + HASH4; + } while (--loop); + } + } + return (h); +} +#endif diff --git a/main/db1-ast/hash/hash_log2.c b/main/db1-ast/hash/hash_log2.c new file mode 100644 index 000000000..6bcf9c114 --- /dev/null +++ b/main/db1-ast/hash/hash_log2.c @@ -0,0 +1,56 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Margo Seltzer. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)hash_log2.c 8.2 (Berkeley) 5/31/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include + +u_int32_t __hash_log2 __P((u_int32_t)); + +u_int32_t +__hash_log2(num) + u_int32_t num; +{ + register u_int32_t i, limit; + + limit = 1; + for (i = 0; limit < num; limit = limit << 1, i++); + return (i); +} diff --git a/main/db1-ast/hash/hash_page.c b/main/db1-ast/hash/hash_page.c new file mode 100644 index 000000000..1429dd6c3 --- /dev/null +++ b/main/db1-ast/hash/hash_page.c @@ -0,0 +1,944 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Margo Seltzer. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)hash_page.c 8.7 (Berkeley) 8/16/94"; +#endif /* LIBC_SCCS and not lint */ + +/* + * PACKAGE: hashing + * + * DESCRIPTION: + * Page manipulation for hashing package. + * + * ROUTINES: + * + * External + * __get_page + * __add_ovflpage + * Internal + * overflow_page + * open_temp + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#ifdef DEBUG +#include +#endif + +#include +#include "hash.h" +#include "page.h" +#include "extern.h" + +static u_int32_t *fetch_bitmap __P((HTAB *, int)); +static u_int32_t first_free __P((u_int32_t)); +static int open_temp __P((HTAB *)); +static u_int16_t overflow_page __P((HTAB *)); +static void putpair __P((char *, const DBT *, const DBT *)); +static void squeeze_key __P((u_int16_t *, const DBT *, const DBT *)); +static int ugly_split + __P((HTAB *, u_int32_t, BUFHEAD *, BUFHEAD *, int, int)); + +#define PAGE_INIT(P) { \ + ((u_int16_t *)(P))[0] = 0; \ + ((u_int16_t *)(P))[1] = hashp->BSIZE - 3 * sizeof(u_int16_t); \ + ((u_int16_t *)(P))[2] = hashp->BSIZE; \ +} + +/* + * This is called AFTER we have verified that there is room on the page for + * the pair (PAIRFITS has returned true) so we go right ahead and start moving + * stuff on. + */ +static void +putpair(p, key, val) + char *p; + const DBT *key, *val; +{ + register u_int16_t *bp, n, off; + + bp = (u_int16_t *)p; + + /* Enter the key first. */ + n = bp[0]; + + off = OFFSET(bp) - key->size; + memmove(p + off, key->data, key->size); + bp[++n] = off; + + /* Now the data. */ + off -= val->size; + memmove(p + off, val->data, val->size); + bp[++n] = off; + + /* Adjust page info. */ + bp[0] = n; + bp[n + 1] = off - ((n + 3) * sizeof(u_int16_t)); + bp[n + 2] = off; +} + +/* + * Returns: + * 0 OK + * -1 error + */ +extern int +__delpair(hashp, bufp, ndx) + HTAB *hashp; + BUFHEAD *bufp; + register int ndx; +{ + register u_int16_t *bp, newoff; + register int n; + u_int16_t pairlen; + + bp = (u_int16_t *)bufp->page; + n = bp[0]; + + if (bp[ndx + 1] < REAL_KEY) + return (__big_delete(hashp, bufp)); + if (ndx != 1) + newoff = bp[ndx - 1]; + else + newoff = hashp->BSIZE; + pairlen = newoff - bp[ndx + 1]; + + if (ndx != (n - 1)) { + /* Hard Case -- need to shuffle keys */ + register int i; + register char *src = bufp->page + (int)OFFSET(bp); + register char *dst = src + (int)pairlen; + memmove(dst, src, bp[ndx + 1] - OFFSET(bp)); + + /* Now adjust the pointers */ + for (i = ndx + 2; i <= n; i += 2) { + if (bp[i + 1] == OVFLPAGE) { + bp[i - 2] = bp[i]; + bp[i - 1] = bp[i + 1]; + } else { + bp[i - 2] = bp[i] + pairlen; + bp[i - 1] = bp[i + 1] + pairlen; + } + } + } + /* Finally adjust the page data */ + bp[n] = OFFSET(bp) + pairlen; + bp[n - 1] = bp[n + 1] + pairlen + 2 * sizeof(u_int16_t); + bp[0] = n - 2; + hashp->NKEYS--; + + bufp->flags |= BUF_MOD; + return (0); +} +/* + * Returns: + * 0 ==> OK + * -1 ==> Error + */ +extern int +__split_page(hashp, obucket, nbucket) + HTAB *hashp; + u_int32_t obucket, nbucket; +{ + register BUFHEAD *new_bufp, *old_bufp; + register u_int16_t *ino; + register char *np; + DBT key, val; + int n, ndx, retval; + u_int16_t copyto, diff, off, moved; + char *op; + + copyto = (u_int16_t)hashp->BSIZE; + off = (u_int16_t)hashp->BSIZE; + old_bufp = __get_buf(hashp, obucket, NULL, 0); + if (old_bufp == NULL) + return (-1); + new_bufp = __get_buf(hashp, nbucket, NULL, 0); + if (new_bufp == NULL) + return (-1); + + old_bufp->flags |= (BUF_MOD | BUF_PIN); + new_bufp->flags |= (BUF_MOD | BUF_PIN); + + ino = (u_int16_t *)(op = old_bufp->page); + np = new_bufp->page; + + moved = 0; + + for (n = 1, ndx = 1; n < ino[0]; n += 2) { + if (ino[n + 1] < REAL_KEY) { + retval = ugly_split(hashp, obucket, old_bufp, new_bufp, + (int)copyto, (int)moved); + old_bufp->flags &= ~BUF_PIN; + new_bufp->flags &= ~BUF_PIN; + return (retval); + + } + key.data = (u_char *)op + ino[n]; + key.size = off - ino[n]; + + if (__call_hash(hashp, key.data, key.size) == obucket) { + /* Don't switch page */ + diff = copyto - off; + if (diff) { + copyto = ino[n + 1] + diff; + memmove(op + copyto, op + ino[n + 1], + off - ino[n + 1]); + ino[ndx] = copyto + ino[n] - ino[n + 1]; + ino[ndx + 1] = copyto; + } else + copyto = ino[n + 1]; + ndx += 2; + } else { + /* Switch page */ + val.data = (u_char *)op + ino[n + 1]; + val.size = ino[n] - ino[n + 1]; + putpair(np, &key, &val); + moved += 2; + } + + off = ino[n + 1]; + } + + /* Now clean up the page */ + ino[0] -= moved; + FREESPACE(ino) = copyto - sizeof(u_int16_t) * (ino[0] + 3); + OFFSET(ino) = copyto; + +#ifdef DEBUG3 + (void)fprintf(stderr, "split %d/%d\n", + ((u_int16_t *)np)[0] / 2, + ((u_int16_t *)op)[0] / 2); +#endif + /* unpin both pages */ + old_bufp->flags &= ~BUF_PIN; + new_bufp->flags &= ~BUF_PIN; + return (0); +} + +/* + * Called when we encounter an overflow or big key/data page during split + * handling. This is special cased since we have to begin checking whether + * the key/data pairs fit on their respective pages and because we may need + * overflow pages for both the old and new pages. + * + * The first page might be a page with regular key/data pairs in which case + * we have a regular overflow condition and just need to go on to the next + * page or it might be a big key/data pair in which case we need to fix the + * big key/data pair. + * + * Returns: + * 0 ==> success + * -1 ==> failure + */ +static int +ugly_split(hashp, obucket, old_bufp, new_bufp, copyto, moved) + HTAB *hashp; + u_int32_t obucket; /* Same as __split_page. */ + BUFHEAD *old_bufp, *new_bufp; + int copyto; /* First byte on page which contains key/data values. */ + int moved; /* Number of pairs moved to new page. */ +{ + register BUFHEAD *bufp; /* Buffer header for ino */ + register u_int16_t *ino; /* Page keys come off of */ + register u_int16_t *np; /* New page */ + register u_int16_t *op; /* Page keys go on to if they aren't moving */ + + BUFHEAD *last_bfp; /* Last buf header OVFL needing to be freed */ + DBT key, val; + SPLIT_RETURN ret; + u_int16_t n, off, ov_addr, scopyto; + char *cino; /* Character value of ino */ + + bufp = old_bufp; + ino = (u_int16_t *)old_bufp->page; + np = (u_int16_t *)new_bufp->page; + op = (u_int16_t *)old_bufp->page; + last_bfp = NULL; + scopyto = (u_int16_t)copyto; /* ANSI */ + + n = ino[0] - 1; + while (n < ino[0]) { + if (ino[2] < REAL_KEY && ino[2] != OVFLPAGE) { + if (__big_split(hashp, old_bufp, + new_bufp, bufp, bufp->addr, obucket, &ret)) + return (-1); + old_bufp = ret.oldp; + if (!old_bufp) + return (-1); + op = (u_int16_t *)old_bufp->page; + new_bufp = ret.newp; + if (!new_bufp) + return (-1); + np = (u_int16_t *)new_bufp->page; + bufp = ret.nextp; + if (!bufp) + return (0); + cino = (char *)bufp->page; + ino = (u_int16_t *)cino; + last_bfp = ret.nextp; + } else if (ino[n + 1] == OVFLPAGE) { + ov_addr = ino[n]; + /* + * Fix up the old page -- the extra 2 are the fields + * which contained the overflow information. + */ + ino[0] -= (moved + 2); + FREESPACE(ino) = + scopyto - sizeof(u_int16_t) * (ino[0] + 3); + OFFSET(ino) = scopyto; + + bufp = __get_buf(hashp, ov_addr, bufp, 0); + if (!bufp) + return (-1); + + ino = (u_int16_t *)bufp->page; + n = 1; + scopyto = hashp->BSIZE; + moved = 0; + + if (last_bfp) + __free_ovflpage(hashp, last_bfp); + last_bfp = bufp; + } + /* Move regular sized pairs of there are any */ + off = hashp->BSIZE; + for (n = 1; (n < ino[0]) && (ino[n + 1] >= REAL_KEY); n += 2) { + cino = (char *)ino; + key.data = (u_char *)cino + ino[n]; + key.size = off - ino[n]; + val.data = (u_char *)cino + ino[n + 1]; + val.size = ino[n] - ino[n + 1]; + off = ino[n + 1]; + + if (__call_hash(hashp, key.data, key.size) == obucket) { + /* Keep on old page */ + if (PAIRFITS(op, (&key), (&val))) + putpair((char *)op, &key, &val); + else { + old_bufp = + __add_ovflpage(hashp, old_bufp); + if (!old_bufp) + return (-1); + op = (u_int16_t *)old_bufp->page; + putpair((char *)op, &key, &val); + } + old_bufp->flags |= BUF_MOD; + } else { + /* Move to new page */ + if (PAIRFITS(np, (&key), (&val))) + putpair((char *)np, &key, &val); + else { + new_bufp = + __add_ovflpage(hashp, new_bufp); + if (!new_bufp) + return (-1); + np = (u_int16_t *)new_bufp->page; + putpair((char *)np, &key, &val); + } + new_bufp->flags |= BUF_MOD; + } + } + } + if (last_bfp) + __free_ovflpage(hashp, last_bfp); + return (0); +} + +/* + * Add the given pair to the page + * + * Returns: + * 0 ==> OK + * 1 ==> failure + */ +extern int +__addel(hashp, bufp, key, val) + HTAB *hashp; + BUFHEAD *bufp; + const DBT *key, *val; +{ + register u_int16_t *bp, *sop; + int do_expand; + + bp = (u_int16_t *)bufp->page; + do_expand = 0; + while (bp[0] && (bp[2] < REAL_KEY || bp[bp[0]] < REAL_KEY)) + /* Exception case */ + if (bp[2] == FULL_KEY_DATA && bp[0] == 2) + /* This is the last page of a big key/data pair + and we need to add another page */ + break; + else if (bp[2] < REAL_KEY && bp[bp[0]] != OVFLPAGE) { + bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); + if (!bufp) + return (-1); + bp = (u_int16_t *)bufp->page; + } else + /* Try to squeeze key on this page */ + if (FREESPACE(bp) > PAIRSIZE(key, val)) { + squeeze_key(bp, key, val); + return (0); + } else { + bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); + if (!bufp) + return (-1); + bp = (u_int16_t *)bufp->page; + } + + if (PAIRFITS(bp, key, val)) + putpair(bufp->page, key, val); + else { + do_expand = 1; + bufp = __add_ovflpage(hashp, bufp); + if (!bufp) + return (-1); + sop = (u_int16_t *)bufp->page; + + if (PAIRFITS(sop, key, val)) + putpair((char *)sop, key, val); + else + if (__big_insert(hashp, bufp, key, val)) + return (-1); + } + bufp->flags |= BUF_MOD; + /* + * If the average number of keys per bucket exceeds the fill factor, + * expand the table. + */ + hashp->NKEYS++; + if (do_expand || + (hashp->NKEYS / (hashp->MAX_BUCKET + 1) > hashp->FFACTOR)) + return (__expand_table(hashp)); + return (0); +} + +/* + * + * Returns: + * pointer on success + * NULL on error + */ +extern BUFHEAD * +__add_ovflpage(hashp, bufp) + HTAB *hashp; + BUFHEAD *bufp; +{ + register u_int16_t *sp; + u_int16_t ndx, ovfl_num; +#ifdef DEBUG1 + int tmp1, tmp2; +#endif + sp = (u_int16_t *)bufp->page; + + /* Check if we are dynamically determining the fill factor */ + if (hashp->FFACTOR == DEF_FFACTOR) { + hashp->FFACTOR = sp[0] >> 1; + if (hashp->FFACTOR < MIN_FFACTOR) + hashp->FFACTOR = MIN_FFACTOR; + } + bufp->flags |= BUF_MOD; + ovfl_num = overflow_page(hashp); +#ifdef DEBUG1 + tmp1 = bufp->addr; + tmp2 = bufp->ovfl ? bufp->ovfl->addr : 0; +#endif + if (!ovfl_num || !(bufp->ovfl = __get_buf(hashp, ovfl_num, bufp, 1))) + return (NULL); + bufp->ovfl->flags |= BUF_MOD; +#ifdef DEBUG1 + (void)fprintf(stderr, "ADDOVFLPAGE: %d->ovfl was %d is now %d\n", + tmp1, tmp2, bufp->ovfl->addr); +#endif + ndx = sp[0]; + /* + * Since a pair is allocated on a page only if there's room to add + * an overflow page, we know that the OVFL information will fit on + * the page. + */ + sp[ndx + 4] = OFFSET(sp); + sp[ndx + 3] = FREESPACE(sp) - OVFLSIZE; + sp[ndx + 1] = ovfl_num; + sp[ndx + 2] = OVFLPAGE; + sp[0] = ndx + 2; +#ifdef HASH_STATISTICS + hash_overflows++; +#endif + return (bufp->ovfl); +} + +/* + * Returns: + * 0 indicates SUCCESS + * -1 indicates FAILURE + */ +extern int +__get_page(hashp, p, bucket, is_bucket, is_disk, is_bitmap) + HTAB *hashp; + char *p; + u_int32_t bucket; + int is_bucket, is_disk, is_bitmap; +{ + register int fd, page, size; + int rsize; + u_int16_t *bp; + + fd = hashp->fp; + size = hashp->BSIZE; + + if ((fd == -1) || !is_disk) { + PAGE_INIT(p); + return (0); + } + if (is_bucket) + page = BUCKET_TO_PAGE(bucket); + else + page = OADDR_TO_PAGE(bucket); + if ((lseek(fd, (off_t)page << hashp->BSHIFT, SEEK_SET) == -1) || + ((rsize = read(fd, p, size)) == -1)) + return (-1); + bp = (u_int16_t *)p; + if (!rsize) + bp[0] = 0; /* We hit the EOF, so initialize a new page */ + else + if (rsize != size) { + errno = EFTYPE; + return (-1); + } + if (!is_bitmap && !bp[0]) { + PAGE_INIT(p); + } else + if (hashp->LORDER != BYTE_ORDER) { + register int i, max; + + if (is_bitmap) { + max = hashp->BSIZE >> 2; /* divide by 4 */ + for (i = 0; i < max; i++) + M_32_SWAP(((int *)p)[i]); + } else { + M_16_SWAP(bp[0]); + max = bp[0] + 2; + for (i = 1; i <= max; i++) + M_16_SWAP(bp[i]); + } + } + return (0); +} + +/* + * Write page p to disk + * + * Returns: + * 0 ==> OK + * -1 ==>failure + */ +extern int +__put_page(hashp, p, bucket, is_bucket, is_bitmap) + HTAB *hashp; + char *p; + u_int32_t bucket; + int is_bucket, is_bitmap; +{ + register int fd, page, size; + int wsize; + + size = hashp->BSIZE; + if ((hashp->fp == -1) && open_temp(hashp)) + return (-1); + fd = hashp->fp; + + if (hashp->LORDER != BYTE_ORDER) { + register int i; + register int max; + + if (is_bitmap) { + max = hashp->BSIZE >> 2; /* divide by 4 */ + for (i = 0; i < max; i++) + M_32_SWAP(((int *)p)[i]); + } else { + max = ((u_int16_t *)p)[0] + 2; + for (i = 0; i <= max; i++) + M_16_SWAP(((u_int16_t *)p)[i]); + } + } + if (is_bucket) + page = BUCKET_TO_PAGE(bucket); + else + page = OADDR_TO_PAGE(bucket); + if ((lseek(fd, (off_t)page << hashp->BSHIFT, SEEK_SET) == -1) || + ((wsize = write(fd, p, size)) == -1)) + /* Errno is set */ + return (-1); + if (wsize != size) { + errno = EFTYPE; + return (-1); + } + return (0); +} + +#define BYTE_MASK ((1 << INT_BYTE_SHIFT) -1) +/* + * Initialize a new bitmap page. Bitmap pages are left in memory + * once they are read in. + */ +extern int +__ibitmap(hashp, pnum, nbits, ndx) + HTAB *hashp; + int pnum, nbits, ndx; +{ + u_int32_t *ip; + int clearbytes, clearints; + + if ((ip = (u_int32_t *)malloc(hashp->BSIZE)) == NULL) + return (1); + hashp->nmaps++; + clearints = ((nbits - 1) >> INT_BYTE_SHIFT) + 1; + clearbytes = clearints << INT_TO_BYTE; + (void)memset((char *)ip, 0, clearbytes); + (void)memset(((char *)ip) + clearbytes, 0xFF, + hashp->BSIZE - clearbytes); + ip[clearints - 1] = ALL_SET << (nbits & BYTE_MASK); + SETBIT(ip, 0); + hashp->BITMAPS[ndx] = (u_int16_t)pnum; + hashp->mapp[ndx] = ip; + return (0); +} + +static u_int32_t +first_free(map) + u_int32_t map; +{ + register u_int32_t i, mask; + + mask = 0x1; + for (i = 0; i < BITS_PER_MAP; i++) { + if (!(mask & map)) + return (i); + mask = mask << 1; + } + return (i); +} + +static u_int16_t +overflow_page(hashp) + HTAB *hashp; +{ + register u_int32_t *freep = 0; + register int max_free, offset, splitnum; + u_int16_t addr; + int bit, first_page, free_bit, free_page, i, in_use_bits, j; +#ifdef DEBUG2 + int tmp1, tmp2; +#endif + splitnum = hashp->OVFL_POINT; + max_free = hashp->SPARES[splitnum]; + + free_page = (max_free - 1) >> (hashp->BSHIFT + BYTE_SHIFT); + free_bit = (max_free - 1) & ((hashp->BSIZE << BYTE_SHIFT) - 1); + + /* Look through all the free maps to find the first free block */ + first_page = hashp->LAST_FREED >>(hashp->BSHIFT + BYTE_SHIFT); + for ( i = first_page; i <= free_page; i++ ) { + if (!(freep = (u_int32_t *)hashp->mapp[i]) && + !(freep = fetch_bitmap(hashp, i))) + return (0); + if (i == free_page) + in_use_bits = free_bit; + else + in_use_bits = (hashp->BSIZE << BYTE_SHIFT) - 1; + + if (i == first_page) { + bit = hashp->LAST_FREED & + ((hashp->BSIZE << BYTE_SHIFT) - 1); + j = bit / BITS_PER_MAP; + bit = bit & ~(BITS_PER_MAP - 1); + } else { + bit = 0; + j = 0; + } + for (; bit <= in_use_bits; j++, bit += BITS_PER_MAP) + if (freep[j] != ALL_SET) + goto found; + } + + /* No Free Page Found */ + hashp->LAST_FREED = hashp->SPARES[splitnum]; + hashp->SPARES[splitnum]++; + offset = hashp->SPARES[splitnum] - + (splitnum ? hashp->SPARES[splitnum - 1] : 0); + +#define OVMSG "HASH: Out of overflow pages. Increase page size\n" + if (offset > SPLITMASK) { + if (++splitnum >= NCACHED) { + (void)write(STDERR_FILENO, OVMSG, sizeof(OVMSG) - 1); + return (0); + } + hashp->OVFL_POINT = splitnum; + hashp->SPARES[splitnum] = hashp->SPARES[splitnum-1]; + hashp->SPARES[splitnum-1]--; + offset = 1; + } + + /* Check if we need to allocate a new bitmap page */ + if (free_bit == (hashp->BSIZE << BYTE_SHIFT) - 1) { + free_page++; + if (free_page >= NCACHED) { + (void)write(STDERR_FILENO, OVMSG, sizeof(OVMSG) - 1); + return (0); + } + /* + * This is tricky. The 1 indicates that you want the new page + * allocated with 1 clear bit. Actually, you are going to + * allocate 2 pages from this map. The first is going to be + * the map page, the second is the overflow page we were + * looking for. The init_bitmap routine automatically, sets + * the first bit of itself to indicate that the bitmap itself + * is in use. We would explicitly set the second bit, but + * don't have to if we tell init_bitmap not to leave it clear + * in the first place. + */ + if (__ibitmap(hashp, + (int)OADDR_OF(splitnum, offset), 1, free_page)) + return (0); + hashp->SPARES[splitnum]++; +#ifdef DEBUG2 + free_bit = 2; +#endif + offset++; + if (offset > SPLITMASK) { + if (++splitnum >= NCACHED) { + (void)write(STDERR_FILENO, OVMSG, + sizeof(OVMSG) - 1); + return (0); + } + hashp->OVFL_POINT = splitnum; + hashp->SPARES[splitnum] = hashp->SPARES[splitnum-1]; + hashp->SPARES[splitnum-1]--; + offset = 0; + } + } else { + /* + * Free_bit addresses the last used bit. Bump it to address + * the first available bit. + */ + free_bit++; + SETBIT(freep, free_bit); + } + + /* Calculate address of the new overflow page */ + addr = OADDR_OF(splitnum, offset); +#ifdef DEBUG2 + (void)fprintf(stderr, "OVERFLOW_PAGE: ADDR: %d BIT: %d PAGE %d\n", + addr, free_bit, free_page); +#endif + return (addr); + +found: + bit = bit + first_free(freep[j]); + SETBIT(freep, bit); +#ifdef DEBUG2 + tmp1 = bit; + tmp2 = i; +#endif + /* + * Bits are addressed starting with 0, but overflow pages are addressed + * beginning at 1. Bit is a bit addressnumber, so we need to increment + * it to convert it to a page number. + */ + bit = 1 + bit + (i * (hashp->BSIZE << BYTE_SHIFT)); + if (bit >= hashp->LAST_FREED) + hashp->LAST_FREED = bit - 1; + + /* Calculate the split number for this page */ + for (i = 0; (i < splitnum) && (bit > hashp->SPARES[i]); i++); + offset = (i ? bit - hashp->SPARES[i - 1] : bit); + if (offset >= SPLITMASK) + return (0); /* Out of overflow pages */ + addr = OADDR_OF(i, offset); +#ifdef DEBUG2 + (void)fprintf(stderr, "OVERFLOW_PAGE: ADDR: %d BIT: %d PAGE %d\n", + addr, tmp1, tmp2); +#endif + + /* Allocate and return the overflow page */ + return (addr); +} + +/* + * Mark this overflow page as free. + */ +extern void +__free_ovflpage(hashp, obufp) + HTAB *hashp; + BUFHEAD *obufp; +{ + register u_int16_t addr; + u_int32_t *freep; + int bit_address, free_page, free_bit; + u_int16_t ndx; + + addr = obufp->addr; +#ifdef DEBUG1 + (void)fprintf(stderr, "Freeing %d\n", addr); +#endif + ndx = (((u_int16_t)addr) >> SPLITSHIFT); + bit_address = + (ndx ? hashp->SPARES[ndx - 1] : 0) + (addr & SPLITMASK) - 1; + if (bit_address < hashp->LAST_FREED) + hashp->LAST_FREED = bit_address; + free_page = (bit_address >> (hashp->BSHIFT + BYTE_SHIFT)); + free_bit = bit_address & ((hashp->BSIZE << BYTE_SHIFT) - 1); + + if (!(freep = hashp->mapp[free_page])) + freep = fetch_bitmap(hashp, free_page); +#ifdef DEBUG + /* + * This had better never happen. It means we tried to read a bitmap + * that has already had overflow pages allocated off it, and we + * failed to read it from the file. + */ + if (!freep) + assert(0); +#endif + CLRBIT(freep, free_bit); +#ifdef DEBUG2 + (void)fprintf(stderr, "FREE_OVFLPAGE: ADDR: %d BIT: %d PAGE %d\n", + obufp->addr, free_bit, free_page); +#endif + __reclaim_buf(hashp, obufp); +} + +/* + * Returns: + * 0 success + * -1 failure + */ +static int +open_temp(hashp) + HTAB *hashp; +{ + sigset_t set, oset; + static char namestr[] = "_hashXXXXXX"; + + /* Block signals; make sure file goes away at process exit. */ + (void)sigfillset(&set); + (void)sigprocmask(SIG_BLOCK, &set, &oset); + if ((hashp->fp = mkstemp(namestr)) != -1) { + (void)unlink(namestr); + (void)fcntl(hashp->fp, F_SETFD, 1); + } + (void)sigprocmask(SIG_SETMASK, &oset, (sigset_t *)NULL); + return (hashp->fp != -1 ? 0 : -1); +} + +/* + * We have to know that the key will fit, but the last entry on the page is + * an overflow pair, so we need to shift things. + */ +static void +squeeze_key(sp, key, val) + u_int16_t *sp; + const DBT *key, *val; +{ + register char *p; + u_int16_t free_space, n, off, pageno; + + p = (char *)sp; + n = sp[0]; + free_space = FREESPACE(sp); + off = OFFSET(sp); + + pageno = sp[n - 1]; + off -= key->size; + sp[n - 1] = off; + memmove(p + off, key->data, key->size); + off -= val->size; + sp[n] = off; + memmove(p + off, val->data, val->size); + sp[0] = n + 2; + sp[n + 1] = pageno; + sp[n + 2] = OVFLPAGE; + FREESPACE(sp) = free_space - PAIRSIZE(key, val); + OFFSET(sp) = off; +} + +static u_int32_t * +fetch_bitmap(hashp, ndx) + HTAB *hashp; + int ndx; +{ + if (ndx >= hashp->nmaps) + return (NULL); + if ((hashp->mapp[ndx] = (u_int32_t *)malloc(hashp->BSIZE)) == NULL) + return (NULL); + if (__get_page(hashp, + (char *)hashp->mapp[ndx], hashp->BITMAPS[ndx], 0, 1, 1)) { + free(hashp->mapp[ndx]); + return (NULL); + } + return (hashp->mapp[ndx]); +} + +#ifdef DEBUG4 +int +print_chain(addr) + int addr; +{ + BUFHEAD *bufp; + short *bp, oaddr; + + (void)fprintf(stderr, "%d ", addr); + bufp = __get_buf(hashp, addr, NULL, 0); + bp = (short *)bufp->page; + while (bp[0] && ((bp[bp[0]] == OVFLPAGE) || + ((bp[0] > 2) && bp[2] < REAL_KEY))) { + oaddr = bp[bp[0] - 1]; + (void)fprintf(stderr, "%d ", (int)oaddr); + bufp = __get_buf(hashp, (int)oaddr, bufp, 0); + bp = (short *)bufp->page; + } + (void)fprintf(stderr, "\n"); +} +#endif diff --git a/main/db1-ast/hash/hsearch.c b/main/db1-ast/hash/hsearch.c new file mode 100644 index 000000000..cc8f7a4aa --- /dev/null +++ b/main/db1-ast/hash/hsearch.c @@ -0,0 +1,107 @@ +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Margo Seltzer. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)hsearch.c 8.4 (Berkeley) 7/21/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include +#include + +#include +#include "search.h" + +static DB *dbp = NULL; +static ENTRY retval; + +extern int +hcreate(nel) + u_int nel; +{ + HASHINFO info; + + info.nelem = nel; + info.bsize = 256; + info.ffactor = 8; + info.cachesize = 0; + info.hash = NULL; + info.lorder = 0; + dbp = (DB *)__hash_open(NULL, O_CREAT | O_RDWR, 0600, &info, 0); + return ((int)dbp); +} + +extern ENTRY * +hsearch(item, action) + ENTRY item; + ACTION action; +{ + DBT key, val; + int status; + + if (!dbp) + return (NULL); + key.data = (u_char *)item.key; + key.size = strlen(item.key) + 1; + + if (action == ENTER) { + val.data = (u_char *)item.data; + val.size = strlen(item.data) + 1; + status = (dbp->put)(dbp, &key, &val, R_NOOVERWRITE); + if (status) + return (NULL); + } else { + /* FIND */ + status = (dbp->get)(dbp, &key, &val, 0); + if (status) + return (NULL); + else + item.data = (char *)val.data; + } + retval.key = item.key; + retval.data = item.data; + return (&retval); +} + +extern void +hdestroy() +{ + if (dbp) { + (void)(dbp->close)(dbp); + dbp = NULL; + } +} diff --git a/main/db1-ast/hash/ndbm.c b/main/db1-ast/hash/ndbm.c new file mode 100644 index 000000000..d702f737a --- /dev/null +++ b/main/db1-ast/hash/ndbm.c @@ -0,0 +1,235 @@ +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Margo Seltzer. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)ndbm.c 8.4 (Berkeley) 7/21/94"; +#endif /* LIBC_SCCS and not lint */ + +/* + * This package provides a dbm compatible interface to the new hashing + * package described in db(3). + */ + +#include + +#include +#include +#include + +#include +#include "hash.h" + +/* + * Returns: + * *DBM on success + * NULL on failure + */ +extern DBM * +dbm_open(file, flags, mode) + const char *file; + int flags, mode; +{ + DBM *db; + HASHINFO info; + const size_t len = strlen(file) + sizeof (DBM_SUFFIX); +#ifdef __GNUC__ + char path[len]; +#else + char *path = malloc(len); + if (path == NULL) + return NULL; +#endif + + info.bsize = 4096; + info.ffactor = 40; + info.nelem = 1; + info.cachesize = 0; + info.hash = NULL; + info.lorder = 0; + (void)strncpy(path, file, len - 1); + (void)strncat(path, DBM_SUFFIX, len - strlen(path) - 1); + db = (DBM *)__hash_open(path, flags, mode, &info, 0); +#ifndef __GNUC__ + free(path); +#endif + return db; +} + +extern void +dbm_close(db) + DBM *db; +{ + (void)(db->close)(db); +} + +/* + * Returns: + * DATUM on success + * NULL on failure + */ +extern datum +dbm_fetch(db, key) + DBM *db; + datum key; +{ + datum retdata; + int status; + DBT dbtkey, dbtretdata; + + dbtkey.data = key.dptr; + dbtkey.size = key.dsize; + status = (db->get)(db, &dbtkey, &dbtretdata, 0); + if (status) { + dbtretdata.data = NULL; + dbtretdata.size = 0; + } + retdata.dptr = dbtretdata.data; + retdata.dsize = dbtretdata.size; + return (retdata); +} + +/* + * Returns: + * DATUM on success + * NULL on failure + */ +extern datum +dbm_firstkey(db) + DBM *db; +{ + int status; + datum retkey; + DBT dbtretkey, dbtretdata; + + status = (db->seq)(db, &dbtretkey, &dbtretdata, R_FIRST); + if (status) + dbtretkey.data = NULL; + retkey.dptr = dbtretkey.data; + retkey.dsize = dbtretkey.size; + return (retkey); +} + +/* + * Returns: + * DATUM on success + * NULL on failure + */ +extern datum +dbm_nextkey(db) + DBM *db; +{ + int status; + datum retkey; + DBT dbtretkey, dbtretdata; + + status = (db->seq)(db, &dbtretkey, &dbtretdata, R_NEXT); + if (status) + dbtretkey.data = NULL; + retkey.dptr = dbtretkey.data; + retkey.dsize = dbtretkey.size; + return (retkey); +} +/* + * Returns: + * 0 on success + * <0 failure + */ +extern int +dbm_delete(db, key) + DBM *db; + datum key; +{ + int status; + DBT dbtkey; + + dbtkey.data = key.dptr; + dbtkey.size = key.dsize; + status = (db->del)(db, &dbtkey, 0); + if (status) + return (-1); + else + return (0); +} + +/* + * Returns: + * 0 on success + * <0 failure + * 1 if DBM_INSERT and entry exists + */ +extern int +dbm_store(db, key, data, flags) + DBM *db; + datum key, data; + int flags; +{ + DBT dbtkey, dbtdata; + + dbtkey.data = key.dptr; + dbtkey.size = key.dsize; + dbtdata.data = data.dptr; + dbtdata.size = data.dsize; + return ((db->put)(db, &dbtkey, &dbtdata, + (flags == DBM_INSERT) ? R_NOOVERWRITE : 0)); +} + +extern int +dbm_error(db) + DBM *db; +{ + HTAB *hp; + + hp = (HTAB *)db->internal; + return (hp->errnum); +} + +extern int +dbm_clearerr(db) + DBM *db; +{ + HTAB *hp; + + hp = (HTAB *)db->internal; + hp->errnum = 0; + return (0); +} + +extern int +dbm_dirfno(db) + DBM *db; +{ + return(((HTAB *)db->internal)->fp); +} diff --git a/main/db1-ast/hash/page.h b/main/db1-ast/hash/page.h new file mode 100644 index 000000000..0fc0d5a3e --- /dev/null +++ b/main/db1-ast/hash/page.h @@ -0,0 +1,92 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Margo Seltzer. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)page.h 8.2 (Berkeley) 5/31/94 + */ + +/* + * Definitions for hashing page file format. + */ + +/* + * routines dealing with a data page + * + * page format: + * +------------------------------+ + * p | n | keyoff | datoff | keyoff | + * +------------+--------+--------+ + * | datoff | free | ptr | --> | + * +--------+---------------------+ + * | F R E E A R E A | + * +--------------+---------------+ + * | <---- - - - | data | + * +--------+-----+----+----------+ + * | key | data | key | + * +--------+----------+----------+ + * + * Pointer to the free space is always: p[p[0] + 2] + * Amount of free space on the page is: p[p[0] + 1] + */ + +/* + * How many bytes required for this pair? + * 2 shorts in the table at the top of the page + room for the + * key and room for the data + * + * We prohibit entering a pair on a page unless there is also room to append + * an overflow page. The reason for this it that you can get in a situation + * where a single key/data pair fits on a page, but you can't append an + * overflow page and later you'd have to split the key/data and handle like + * a big pair. + * You might as well do this up front. + */ + +#define PAIRSIZE(K,D) (2*sizeof(u_int16_t) + (K)->size + (D)->size) +#define BIGOVERHEAD (4*sizeof(u_int16_t)) +#define KEYSIZE(K) (4*sizeof(u_int16_t) + (K)->size); +#define OVFLSIZE (2*sizeof(u_int16_t)) +#define FREESPACE(P) ((P)[(P)[0]+1]) +#define OFFSET(P) ((P)[(P)[0]+2]) +#define PAIRFITS(P,K,D) \ + (((P)[2] >= REAL_KEY) && \ + (PAIRSIZE((K),(D)) + OVFLSIZE) <= FREESPACE((P))) +#define PAGE_META(N) (((N)+3) * sizeof(u_int16_t)) + +typedef struct { + BUFHEAD *newp; + BUFHEAD *oldp; + BUFHEAD *nextp; + u_int16_t next_addr; +} SPLIT_RETURN; diff --git a/main/db1-ast/hash/search.h b/main/db1-ast/hash/search.h new file mode 100644 index 000000000..4d3b9143e --- /dev/null +++ b/main/db1-ast/hash/search.h @@ -0,0 +1,51 @@ +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Margo Seltzer. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)search.h 8.1 (Berkeley) 6/4/93 + */ + +/* Backward compatibility to hsearch interface. */ +typedef struct entry { + char *key; + char *data; +} ENTRY; + +typedef enum { + FIND, ENTER +} ACTION; + +int hcreate __P((unsigned int)); +void hdestroy __P((void)); +ENTRY *hsearch __P((ENTRY, ACTION)); diff --git a/main/db1-ast/include/circ-queue.h b/main/db1-ast/include/circ-queue.h new file mode 100644 index 000000000..33ba9115b --- /dev/null +++ b/main/db1-ast/include/circ-queue.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + * $FreeBSD: ports/misc/44bsd-more/files/queue.h,v 1.1 2001/01/06 03:41:36 hoek Exp $ + */ + +/* + * Circular queue definitions. + */ +#define CIRCLEQ_HEAD(name, type) \ +struct name { \ + struct type *cqh_first; /* first element */ \ + struct type *cqh_last; /* last element */ \ +} + +#define CIRCLEQ_ENTRY(type) \ +struct { \ + struct type *cqe_next; /* next element */ \ + struct type *cqe_prev; /* previous element */ \ +} + +/* + * Circular queue functions. + */ +#define CIRCLEQ_EMPTY(head) ((head)->cqh_first == (void *)(head)) + +#define CIRCLEQ_FIRST(head) ((head)->cqh_first) + +#define CIRCLEQ_FOREACH(var, head, field) \ + for((var) = (head)->cqh_first; \ + (var) != (void *)(head); \ + (var) = (var)->field.cqe_next) + +#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ + for((var) = (head)->cqh_last; \ + (var) != (void *)(head); \ + (var) = (var)->field.cqe_prev) + +#define CIRCLEQ_INIT(head) do { \ + (head)->cqh_first = (void *)(head); \ + (head)->cqh_last = (void *)(head); \ +} while (0) + +#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm)->field.cqe_next; \ + (elm)->field.cqe_prev = (listelm); \ + if ((listelm)->field.cqe_next == (void *)(head)) \ + (head)->cqh_last = (elm); \ + else \ + (listelm)->field.cqe_next->field.cqe_prev = (elm); \ + (listelm)->field.cqe_next = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm); \ + (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ + if ((listelm)->field.cqe_prev == (void *)(head)) \ + (head)->cqh_first = (elm); \ + else \ + (listelm)->field.cqe_prev->field.cqe_next = (elm); \ + (listelm)->field.cqe_prev = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.cqe_next = (head)->cqh_first; \ + (elm)->field.cqe_prev = (void *)(head); \ + if ((head)->cqh_last == (void *)(head)) \ + (head)->cqh_last = (elm); \ + else \ + (head)->cqh_first->field.cqe_prev = (elm); \ + (head)->cqh_first = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.cqe_next = (void *)(head); \ + (elm)->field.cqe_prev = (head)->cqh_last; \ + if ((head)->cqh_first == (void *)(head)) \ + (head)->cqh_first = (elm); \ + else \ + (head)->cqh_last->field.cqe_next = (elm); \ + (head)->cqh_last = (elm); \ +} while (0) + +#define CIRCLEQ_LAST(head) ((head)->cqh_last) + +#define CIRCLEQ_NEXT(elm,field) ((elm)->field.cqe_next) + +#define CIRCLEQ_PREV(elm,field) ((elm)->field.cqe_prev) + +#define CIRCLEQ_REMOVE(head, elm, field) do { \ + if ((elm)->field.cqe_next == (void *)(head)) \ + (head)->cqh_last = (elm)->field.cqe_prev; \ + else \ + (elm)->field.cqe_next->field.cqe_prev = \ + (elm)->field.cqe_prev; \ + if ((elm)->field.cqe_prev == (void *)(head)) \ + (head)->cqh_first = (elm)->field.cqe_next; \ + else \ + (elm)->field.cqe_prev->field.cqe_next = \ + (elm)->field.cqe_next; \ +} while (0) diff --git a/main/db1-ast/include/compat.h b/main/db1-ast/include/compat.h new file mode 100644 index 000000000..706e58265 --- /dev/null +++ b/main/db1-ast/include/compat.h @@ -0,0 +1,49 @@ +/* Values for building 4.4 BSD db routines in the GNU C library. */ + +#ifndef _compat_h_ +#define _compat_h_ + +#include + +/* + * If you can't provide lock values in the open(2) call. Note, this + * allows races to happen. + */ +#ifndef O_EXLOCK /* 4.4BSD extension. */ +#define O_EXLOCK 0 +#endif + +#ifndef O_SHLOCK /* 4.4BSD extension. */ +#define O_SHLOCK 0 +#endif + +#include + +#ifndef EFTYPE +#define EFTYPE EINVAL /* POSIX 1003.1 format errno. */ +#endif + +#include +#include + +#ifndef _POSIX_VDISABLE /* POSIX 1003.1 disabling char. */ +#define _POSIX_VDISABLE 0 /* Some systems used 0. */ +#endif + +#include + +#ifndef TCSASOFT /* 4.4BSD extension. */ +#define TCSASOFT 0 +#endif + +#include + +#ifndef MAX /* Usually found in . */ +#define MAX(_a,_b) ((_a)<(_b)?(_b):(_a)) +#endif +#ifndef MIN /* Usually found in . */ +#define MIN(_a,_b) ((_a)<(_b)?(_a):(_b)) +#endif + + +#endif /* compat.h */ diff --git a/main/db1-ast/include/db.h b/main/db1-ast/include/db.h new file mode 100644 index 000000000..a58724bdd --- /dev/null +++ b/main/db1-ast/include/db.h @@ -0,0 +1,250 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)db.h 8.7 (Berkeley) 6/16/94 + */ + +#ifndef _DB_H +#define _DB_H 1 + +#include +#include + +#include + +#ifdef __DBINTERFACE_PRIVATE +#include +#endif + +#ifdef SOLARIS +#include "solaris-compat/compat.h" +#endif + +#define RET_ERROR -1 /* Return values. */ +#define RET_SUCCESS 0 +#define RET_SPECIAL 1 + +#ifndef __BIT_TYPES_DEFINED__ +#define __BIT_TYPES_DEFINED__ +#if (!defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__APPLE__)) +typedef __signed char int8_t; +typedef short int16_t; +typedef int int32_t; +typedef unsigned char u_int8_t; +typedef unsigned short u_int16_t; +typedef unsigned int u_int32_t; +#ifdef WE_DONT_NEED_QUADS +typedef long long int64_t; +typedef unsigned long long u_int64_t; +#endif +#endif /* __FreeBSD__ */ +#endif + +#ifdef SOLARIS +#define __P(p) p +#define __BEGIN_DECLS +#define __END_DECLS +#endif + +#define MAX_PAGE_NUMBER 0xffffffff /* >= # of pages in a file */ +typedef u_int32_t pgno_t; +#define MAX_PAGE_OFFSET 65535 /* >= # of bytes in a page */ +typedef u_int16_t indx_t; +#define MAX_REC_NUMBER 0xffffffff /* >= # of records in a tree */ +typedef u_int32_t recno_t; + +/* Key/data structure -- a Data-Base Thang. */ +typedef struct { + void *data; /* data */ + size_t size; /* data length */ +} DBT; + +/* Routine flags. */ +#define R_CURSOR 1 /* del, put, seq */ +#define __R_UNUSED 2 /* UNUSED */ +#define R_FIRST 3 /* seq */ +#define R_IAFTER 4 /* put (RECNO) */ +#define R_IBEFORE 5 /* put (RECNO) */ +#define R_LAST 6 /* seq (BTREE, RECNO) */ +#define R_NEXT 7 /* seq */ +#define R_NOOVERWRITE 8 /* put */ +#define R_PREV 9 /* seq (BTREE, RECNO) */ +#define R_SETCURSOR 10 /* put (RECNO) */ +#define R_RECNOSYNC 11 /* sync (RECNO) */ + +typedef enum { DB_BTREE, DB_HASH, DB_RECNO } DBTYPE; + +/* + * !!! + * The following flags are included in the dbopen(3) call as part of the + * open(2) flags. In order to avoid conflicts with the open flags, start + * at the top of the 16 or 32-bit number space and work our way down. If + * the open flags were significantly expanded in the future, it could be + * a problem. Wish I'd left another flags word in the dbopen call. + * + * !!! + * None of this stuff is implemented yet. The only reason that it's here + * is so that the access methods can skip copying the key/data pair when + * the DB_LOCK flag isn't set. + */ +#if UINT_MAX > 65535 +#define DB_LOCK 0x20000000 /* Do locking. */ +#define DB_SHMEM 0x40000000 /* Use shared memory. */ +#define DB_TXN 0x80000000 /* Do transactions. */ +#else +#define DB_LOCK 0x2000 /* Do locking. */ +#define DB_SHMEM 0x4000 /* Use shared memory. */ +#define DB_TXN 0x8000 /* Do transactions. */ +#endif + +/* Access method description structure. */ +typedef struct __db { + DBTYPE type; /* Underlying db type. */ + int (*close) __P((struct __db *)); + int (*del) __P((const struct __db *, const DBT *, u_int)); + int (*get) __P((const struct __db *, const DBT *, DBT *, u_int)); + int (*put) __P((const struct __db *, DBT *, const DBT *, u_int)); + int (*seq) __P((const struct __db *, DBT *, DBT *, u_int)); + int (*sync) __P((const struct __db *, u_int)); + void *internal; /* Access method private. */ + int (*fd) __P((const struct __db *)); +} DB; + +#define BTREEMAGIC 0x053162 +#define BTREEVERSION 3 + +/* Structure used to pass parameters to the btree routines. */ +typedef struct { +#define R_DUP 0x01 /* duplicate keys */ + u_long flags; + u_int cachesize; /* bytes to cache */ + int maxkeypage; /* maximum keys per page */ + int minkeypage; /* minimum keys per page */ + u_int psize; /* page size */ + int (*compare) /* comparison function */ + __P((const DBT *, const DBT *)); + size_t (*prefix) /* prefix function */ + __P((const DBT *, const DBT *)); + int lorder; /* byte order */ +} BTREEINFO; + +#define HASHMAGIC 0x061561 +#define HASHVERSION 2 + +/* Structure used to pass parameters to the hashing routines. */ +typedef struct { + u_int bsize; /* bucket size */ + u_int ffactor; /* fill factor */ + u_int nelem; /* number of elements */ + u_int cachesize; /* bytes to cache */ + u_int32_t /* hash function */ + (*hash) __P((const void *, size_t)); + int lorder; /* byte order */ +} HASHINFO; + +/* Structure used to pass parameters to the record routines. */ +typedef struct { +#define R_FIXEDLEN 0x01 /* fixed-length records */ +#define R_NOKEY 0x02 /* key not required */ +#define R_SNAPSHOT 0x04 /* snapshot the input */ + u_long flags; + u_int cachesize; /* bytes to cache */ + u_int psize; /* page size */ + int lorder; /* byte order */ + size_t reclen; /* record length (fixed-length records) */ + u_char bval; /* delimiting byte (variable-length records */ + char *bfname; /* btree file name */ +} RECNOINFO; + +#ifdef __DBINTERFACE_PRIVATE +/* + * Little endian <==> big endian 32-bit swap macros. + * M_32_SWAP swap a memory location + * P_32_SWAP swap a referenced memory location + * P_32_COPY swap from one location to another + */ +#define M_32_SWAP(a) { \ + u_int32_t _tmp = a; \ + ((char *)&a)[0] = ((char *)&_tmp)[3]; \ + ((char *)&a)[1] = ((char *)&_tmp)[2]; \ + ((char *)&a)[2] = ((char *)&_tmp)[1]; \ + ((char *)&a)[3] = ((char *)&_tmp)[0]; \ +} +#define P_32_SWAP(a) { \ + u_int32_t _tmp = *(u_int32_t *)a; \ + ((char *)a)[0] = ((char *)&_tmp)[3]; \ + ((char *)a)[1] = ((char *)&_tmp)[2]; \ + ((char *)a)[2] = ((char *)&_tmp)[1]; \ + ((char *)a)[3] = ((char *)&_tmp)[0]; \ +} +#define P_32_COPY(a, b) { \ + ((char *)&(b))[0] = ((char *)&(a))[3]; \ + ((char *)&(b))[1] = ((char *)&(a))[2]; \ + ((char *)&(b))[2] = ((char *)&(a))[1]; \ + ((char *)&(b))[3] = ((char *)&(a))[0]; \ +} + +/* + * Little endian <==> big endian 16-bit swap macros. + * M_16_SWAP swap a memory location + * P_16_SWAP swap a referenced memory location + * P_16_COPY swap from one location to another + */ +#define M_16_SWAP(a) { \ + u_int16_t _tmp = a; \ + ((char *)&a)[0] = ((char *)&_tmp)[1]; \ + ((char *)&a)[1] = ((char *)&_tmp)[0]; \ +} +#define P_16_SWAP(a) { \ + u_int16_t _tmp = *(u_int16_t *)a; \ + ((char *)a)[0] = ((char *)&_tmp)[1]; \ + ((char *)a)[1] = ((char *)&_tmp)[0]; \ +} +#define P_16_COPY(a, b) { \ + ((char *)&(b))[0] = ((char *)&(a))[1]; \ + ((char *)&(b))[1] = ((char *)&(a))[0]; \ +} +#endif + +__BEGIN_DECLS +DB *__dbopen __P((const char *, int, int, DBTYPE, const void *)); +DB *dbopen __P((const char *, int, int, DBTYPE, const void *)); + +#ifdef __DBINTERFACE_PRIVATE +DB *__bt_open __P((const char *, int, int, const BTREEINFO *, int)); +DB *__hash_open __P((const char *, int, int, const HASHINFO *, int)); +DB *__rec_open __P((const char *, int, int, const RECNOINFO *, int)); +void __dbpanic __P((DB *dbp)); +#endif +__END_DECLS + +#endif /* db.h */ diff --git a/main/db1-ast/include/mpool.h b/main/db1-ast/include/mpool.h new file mode 100644 index 000000000..0cfc5741c --- /dev/null +++ b/main/db1-ast/include/mpool.h @@ -0,0 +1,115 @@ +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)mpool.h 8.2 (Berkeley) 7/14/94 + */ + +#ifndef _MPOOL_H +#define _MPOOL_H 1 + +#include +#ifndef CIRCLEQ_HEAD +#include +#endif + +/* + * The memory pool scheme is a simple one. Each in-memory page is referenced + * by a bucket which is threaded in up to two of three ways. All active pages + * are threaded on a hash chain (hashed by page number) and an lru chain. + * Inactive pages are threaded on a free chain. Each reference to a memory + * pool is handed an opaque MPOOL cookie which stores all of this information. + */ +#define HASHSIZE 128 +#define HASHKEY(pgno) ((pgno - 1) % HASHSIZE) + +/* The BKT structures are the elements of the queues. */ +typedef struct _bkt { + CIRCLEQ_ENTRY(_bkt) hq; /* hash queue */ + CIRCLEQ_ENTRY(_bkt) q; /* lru queue */ + void *page; /* page */ + pgno_t pgno; /* page number */ + +#define MPOOL_DIRTY 0x01 /* page needs to be written */ +#define MPOOL_PINNED 0x02 /* page is pinned into memory */ + u_int8_t flags; /* flags */ +} BKT; + +typedef struct MPOOL { + CIRCLEQ_HEAD(_lqh, _bkt) lqh; /* lru queue head */ + /* hash queue array */ + CIRCLEQ_HEAD(_hqh, _bkt) hqh[HASHSIZE]; + pgno_t curcache; /* current number of cached pages */ + pgno_t maxcache; /* max number of cached pages */ + pgno_t npages; /* number of pages in the file */ + u_long pagesize; /* file page size */ + int fd; /* file descriptor */ + /* page in conversion routine */ + void (*pgin) __P((void *, pgno_t, void *)); + /* page out conversion routine */ + void (*pgout) __P((void *, pgno_t, void *)); + void *pgcookie; /* cookie for page in/out routines */ +#ifdef STATISTICS + u_long cachehit; + u_long cachemiss; + u_long pagealloc; + u_long pageflush; + u_long pageget; + u_long pagenew; + u_long pageput; + u_long pageread; + u_long pagewrite; +#endif +} MPOOL; + +__BEGIN_DECLS +MPOOL *__mpool_open __P((void *, int, pgno_t, pgno_t)); +MPOOL *mpool_open __P((void *, int, pgno_t, pgno_t)); +void __mpool_filter __P((MPOOL *, void (*)(void *, pgno_t, void *), + void (*)(void *, pgno_t, void *), void *)); +void mpool_filter __P((MPOOL *, void (*)(void *, pgno_t, void *), + void (*)(void *, pgno_t, void *), void *)); +void *__mpool_new __P((MPOOL *, pgno_t *)); +void *mpool_new __P((MPOOL *, pgno_t *)); +void *__mpool_get __P((MPOOL *, pgno_t, u_int)); +void *mpool_get __P((MPOOL *, pgno_t, u_int)); +int __mpool_put __P((MPOOL *, void *, u_int)); +int mpool_put __P((MPOOL *, void *, u_int)); +int __mpool_sync __P((MPOOL *)); +int mpool_sync __P((MPOOL *)); +int __mpool_close __P((MPOOL *)); +int mpool_close __P((MPOOL *)); +#ifdef STATISTICS +void mpool_stat __P((MPOOL *)); +#endif +__END_DECLS + +#endif /* mpool.h */ diff --git a/main/db1-ast/include/ndbm.h b/main/db1-ast/include/ndbm.h new file mode 100644 index 000000000..db14dff2e --- /dev/null +++ b/main/db1-ast/include/ndbm.h @@ -0,0 +1,79 @@ +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Margo Seltzer. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)ndbm.h 8.1 (Berkeley) 6/2/93 + */ + +#ifndef _NDBM_H +#define _NDBM_H 1 + +#include + +/* Map dbm interface onto db(3). */ +#define DBM_RDONLY O_RDONLY + +/* Flags to dbm_store(). */ +#define DBM_INSERT 0 +#define DBM_REPLACE 1 + +/* + * The db(3) support for ndbm(3) always appends this suffix to the + * file name to avoid overwriting the user's original database. + */ +#define DBM_SUFFIX ".db" + +typedef struct { + char *dptr; + int dsize; +} datum; + +typedef DB DBM; +#define dbm_pagfno(a) DBM_PAGFNO_NOT_AVAILABLE + +__BEGIN_DECLS +void dbm_close __P((DBM *)); +int dbm_delete __P((DBM *, datum)); +datum dbm_fetch __P((DBM *, datum)); +datum dbm_firstkey __P((DBM *)); +long dbm_forder __P((DBM *, datum)); +datum dbm_nextkey __P((DBM *)); +DBM *dbm_open __P((const char *, int, int)); +int dbm_store __P((DBM *, datum, datum, int)); +int dbm_dirfno __P((DBM *)); +int dbm_error __P((DBM *)); +int dbm_clearerr __P((DBM *)); +__END_DECLS + +#endif /* ndbm.h */ diff --git a/main/db1-ast/libdb.map b/main/db1-ast/libdb.map new file mode 100644 index 000000000..87e34c430 --- /dev/null +++ b/main/db1-ast/libdb.map @@ -0,0 +1,11 @@ +GLIBC_2.0 { + global: + # the real DB entry point. + dbopen; __dbopen; + + # The compatibility functions. + dbm_clearerr; dbm_close; dbm_delete; dbm_dirfno; dbm_error; + dbm_fetch; dbm_firstkey; dbm_nextkey; dbm_open; dbm_store; + local: + *; +}; diff --git a/main/db1-ast/mpool/README b/main/db1-ast/mpool/README new file mode 100644 index 000000000..0f01fbcdb --- /dev/null +++ b/main/db1-ast/mpool/README @@ -0,0 +1,7 @@ +# @(#)README 8.1 (Berkeley) 6/4/93 + +These are the current memory pool routines. +They aren't ready for prime time, yet, and +the interface is expected to change. + +--keith diff --git a/main/db1-ast/mpool/mpool.c b/main/db1-ast/mpool/mpool.c new file mode 100644 index 000000000..eca24e518 --- /dev/null +++ b/main/db1-ast/mpool/mpool.c @@ -0,0 +1,498 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)mpool.c 8.5 (Berkeley) 7/26/94"; +#endif /* LIBC_SCCS and not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define __MPOOLINTERFACE_PRIVATE +#include + +#undef __APPLE__ + +#ifndef __APPLE__ +#define mpool_open __mpool_open +#define mpool_filter __mpool_filter +#define mpool_new __mpool_new +#define mpool_get __mpool_get +#define mpool_put __mpool_put +#define mpool_sync __mpool_sync +#define mpool_close __mpool_close +#endif + +static BKT *mpool_bkt __P((MPOOL *)); +static BKT *mpool_look __P((MPOOL *, pgno_t)); +static int mpool_write __P((MPOOL *, BKT *)); + +/* + * mpool_open -- + * Initialize a memory pool. + */ +MPOOL * +mpool_open(key, fd, pagesize, maxcache) + void *key; + int fd; + pgno_t pagesize, maxcache; +{ + struct stat sb; + MPOOL *mp; + int entry; + + /* + * Get information about the file. + * + * XXX + * We don't currently handle pipes, although we should. + */ + if (fstat(fd, &sb)) + return (NULL); + if (!S_ISREG(sb.st_mode)) { + errno = ESPIPE; + return (NULL); + } + + /* Allocate and initialize the MPOOL cookie. */ + if ((mp = (MPOOL *)calloc(1, sizeof(MPOOL))) == NULL) + return (NULL); + CIRCLEQ_INIT(&mp->lqh); + for (entry = 0; entry < HASHSIZE; ++entry) + CIRCLEQ_INIT(&mp->hqh[entry]); + mp->maxcache = maxcache; + mp->npages = sb.st_size / pagesize; + mp->pagesize = pagesize; + mp->fd = fd; + return (mp); +} + +/* + * mpool_filter -- + * Initialize input/output filters. + */ +void +mpool_filter(mp, pgin, pgout, pgcookie) + MPOOL *mp; + void (*pgin) __P((void *, pgno_t, void *)); + void (*pgout) __P((void *, pgno_t, void *)); + void *pgcookie; +{ + mp->pgin = pgin; + mp->pgout = pgout; + mp->pgcookie = pgcookie; +} + +/* + * mpool_new -- + * Get a new page of memory. + */ +void * +mpool_new(mp, pgnoaddr) + MPOOL *mp; + pgno_t *pgnoaddr; +{ + struct _hqh *head; + BKT *bp; + + if (mp->npages == MAX_PAGE_NUMBER) { + (void)fprintf(stderr, "mpool_new: page allocation overflow.\n"); + abort(); + } +#ifdef STATISTICS + ++mp->pagenew; +#endif + /* + * Get a BKT from the cache. Assign a new page number, attach + * it to the head of the hash chain, the tail of the lru chain, + * and return. + */ + if ((bp = mpool_bkt(mp)) == NULL) + return (NULL); + *pgnoaddr = bp->pgno = mp->npages++; + bp->flags = MPOOL_PINNED; + + head = &mp->hqh[HASHKEY(bp->pgno)]; + CIRCLEQ_INSERT_HEAD(head, bp, hq); + CIRCLEQ_INSERT_TAIL(&mp->lqh, bp, q); + return (bp->page); +} + +/* + * mpool_get + * Get a page. + */ +void * +mpool_get(mp, pgno, flags) + MPOOL *mp; + pgno_t pgno; + u_int flags; /* XXX not used? */ +{ + struct _hqh *head; + BKT *bp; + off_t off; + int nr; + + /* Check for attempt to retrieve a non-existent page. */ + if (pgno >= mp->npages) { + errno = EINVAL; + return (NULL); + } + +#ifdef STATISTICS + ++mp->pageget; +#endif + + /* Check for a page that is cached. */ + if ((bp = mpool_look(mp, pgno)) != NULL) { +#ifdef DEBUG + if (bp->flags & MPOOL_PINNED) { + (void)fprintf(stderr, + "mpool_get: page %d already pinned\n", bp->pgno); + abort(); + } +#endif + /* + * Move the page to the head of the hash chain and the tail + * of the lru chain. + */ + head = &mp->hqh[HASHKEY(bp->pgno)]; + CIRCLEQ_REMOVE(head, bp, hq); + CIRCLEQ_INSERT_HEAD(head, bp, hq); + CIRCLEQ_REMOVE(&mp->lqh, bp, q); + CIRCLEQ_INSERT_TAIL(&mp->lqh, bp, q); + + /* Return a pinned page. */ + bp->flags |= MPOOL_PINNED; + return (bp->page); + } + + /* Get a page from the cache. */ + if ((bp = mpool_bkt(mp)) == NULL) + return (NULL); + + /* Read in the contents. */ +#ifdef STATISTICS + ++mp->pageread; +#endif + off = mp->pagesize * pgno; + if (lseek(mp->fd, off, SEEK_SET) != off) + return (NULL); + if ((u_long) (nr = read(mp->fd, bp->page, mp->pagesize)) + != mp->pagesize) { + if (nr >= 0) + errno = EFTYPE; + return (NULL); + } + + /* Set the page number, pin the page. */ + bp->pgno = pgno; + bp->flags = MPOOL_PINNED; + + /* + * Add the page to the head of the hash chain and the tail + * of the lru chain. + */ + head = &mp->hqh[HASHKEY(bp->pgno)]; + CIRCLEQ_INSERT_HEAD(head, bp, hq); + CIRCLEQ_INSERT_TAIL(&mp->lqh, bp, q); + + /* Run through the user's filter. */ + if (mp->pgin != NULL) + (mp->pgin)(mp->pgcookie, bp->pgno, bp->page); + + return (bp->page); +} + +/* + * mpool_put + * Return a page. + */ +int +mpool_put(mp, page, flags) + MPOOL *mp; + void *page; + u_int flags; +{ + BKT *bp; + +#ifdef STATISTICS + ++mp->pageput; +#endif + bp = (BKT *)((char *)page - sizeof(BKT)); +#ifdef DEBUG + if (!(bp->flags & MPOOL_PINNED)) { + (void)fprintf(stderr, + "mpool_put: page %d not pinned\n", bp->pgno); + abort(); + } +#endif + bp->flags &= ~MPOOL_PINNED; + bp->flags |= flags & MPOOL_DIRTY; + return (RET_SUCCESS); +} + +/* + * mpool_close + * Close the buffer pool. + */ +int +mpool_close(mp) + MPOOL *mp; +{ + BKT *bp; + + /* Free up any space allocated to the lru pages. */ + while ((bp = mp->lqh.cqh_first) != (void *)&mp->lqh) { + CIRCLEQ_REMOVE(&mp->lqh, mp->lqh.cqh_first, q); + free(bp); + } + + /* Free the MPOOL cookie. */ + free(mp); + return (RET_SUCCESS); +} + +/* + * mpool_sync + * Sync the pool to disk. + */ +int +mpool_sync(mp) + MPOOL *mp; +{ + BKT *bp; + + /* Walk the lru chain, flushing any dirty pages to disk. */ + for (bp = mp->lqh.cqh_first; + bp != (void *)&mp->lqh; bp = bp->q.cqe_next) + if (bp->flags & MPOOL_DIRTY && + mpool_write(mp, bp) == RET_ERROR) + return (RET_ERROR); + + /* Sync the file descriptor. */ + return (fsync(mp->fd) ? RET_ERROR : RET_SUCCESS); +} + +#define __APPLE__ + +#ifndef __APPLE__ +#undef mpool_open +#undef mpool_filter +#undef mpool_new +#undef mpool_get +#undef mpool_put +#undef mpool_close +#undef mpool_sync + +#define weak_alias(original, alias) \ + asm (".weak " #alias "\n" #alias " = " #original); +weak_alias (__mpool_open, mpool_open) +weak_alias (__mpool_filter, mpool_filter) +weak_alias (__mpool_new, mpool_new) +weak_alias (__mpool_get, mpool_get) +weak_alias (__mpool_put, mpool_put) +weak_alias (__mpool_close, mpool_close) +weak_alias (__mpool_sync, mpool_sync) +#endif + +/* + * mpool_bkt + * Get a page from the cache (or create one). + */ +static BKT * +mpool_bkt(mp) + MPOOL *mp; +{ + struct _hqh *head; + BKT *bp; + + /* If under the max cached, always create a new page. */ + if (mp->curcache < mp->maxcache) + goto new; + + /* + * If the cache is max'd out, walk the lru list for a buffer we + * can flush. If we find one, write it (if necessary) and take it + * off any lists. If we don't find anything we grow the cache anyway. + * The cache never shrinks. + */ + for (bp = mp->lqh.cqh_first; + bp != (void *)&mp->lqh; bp = bp->q.cqe_next) + if (!(bp->flags & MPOOL_PINNED)) { + /* Flush if dirty. */ + if (bp->flags & MPOOL_DIRTY && + mpool_write(mp, bp) == RET_ERROR) + return (NULL); +#ifdef STATISTICS + ++mp->pageflush; +#endif + /* Remove from the hash and lru queues. */ + head = &mp->hqh[HASHKEY(bp->pgno)]; + CIRCLEQ_REMOVE(head, bp, hq); + CIRCLEQ_REMOVE(&mp->lqh, bp, q); +#ifdef DEBUG + { void *spage; + spage = bp->page; + memset(bp, 0xff, sizeof(BKT) + mp->pagesize); + bp->page = spage; + } +#endif + return (bp); + } + +new: if ((bp = (BKT *)malloc(sizeof(BKT) + mp->pagesize)) == NULL) + return (NULL); +#ifdef STATISTICS + ++mp->pagealloc; +#endif +#if defined(DEBUG) || defined(PURIFY) + memset(bp, 0xff, sizeof(BKT) + mp->pagesize); +#endif + bp->page = (char *)bp + sizeof(BKT); + ++mp->curcache; + return (bp); +} + +/* + * mpool_write + * Write a page to disk. + */ +static int +mpool_write(mp, bp) + MPOOL *mp; + BKT *bp; +{ + off_t off; + +#ifdef STATISTICS + ++mp->pagewrite; +#endif + + /* Run through the user's filter. */ + if (mp->pgout) + (mp->pgout)(mp->pgcookie, bp->pgno, bp->page); + + off = mp->pagesize * bp->pgno; + if (lseek(mp->fd, off, SEEK_SET) != off) + return (RET_ERROR); + if ((u_long) write(mp->fd, bp->page, mp->pagesize) != mp->pagesize) + return (RET_ERROR); + + bp->flags &= ~MPOOL_DIRTY; + return (RET_SUCCESS); +} + +/* + * mpool_look + * Lookup a page in the cache. + */ +static BKT * +mpool_look(mp, pgno) + MPOOL *mp; + pgno_t pgno; +{ + struct _hqh *head; + BKT *bp; + + head = &mp->hqh[HASHKEY(pgno)]; + for (bp = head->cqh_first; bp != (void *)head; bp = bp->hq.cqe_next) + if (bp->pgno == pgno) { +#ifdef STATISTICS + ++mp->cachehit; +#endif + return (bp); + } +#ifdef STATISTICS + ++mp->cachemiss; +#endif + return (NULL); +} + +#ifdef STATISTICS +/* + * mpool_stat + * Print out cache statistics. + */ +void +mpool_stat(mp) + MPOOL *mp; +{ + BKT *bp; + int cnt; + char *sep; + + (void)fprintf(stderr, "%lu pages in the file\n", mp->npages); + (void)fprintf(stderr, + "page size %lu, cacheing %lu pages of %lu page max cache\n", + mp->pagesize, mp->curcache, mp->maxcache); + (void)fprintf(stderr, "%lu page puts, %lu page gets, %lu page new\n", + mp->pageput, mp->pageget, mp->pagenew); + (void)fprintf(stderr, "%lu page allocs, %lu page flushes\n", + mp->pagealloc, mp->pageflush); + if (mp->cachehit + mp->cachemiss) + (void)fprintf(stderr, + "%.0f%% cache hit rate (%lu hits, %lu misses)\n", + ((double)mp->cachehit / (mp->cachehit + mp->cachemiss)) + * 100, mp->cachehit, mp->cachemiss); + (void)fprintf(stderr, "%lu page reads, %lu page writes\n", + mp->pageread, mp->pagewrite); + + sep = ""; + cnt = 0; + for (bp = mp->lqh.cqh_first; + bp != (void *)&mp->lqh; bp = bp->q.cqe_next) { + (void)fprintf(stderr, "%s%d", sep, bp->pgno); + if (bp->flags & MPOOL_DIRTY) + (void)fprintf(stderr, "d"); + if (bp->flags & MPOOL_PINNED) + (void)fprintf(stderr, "P"); + if (++cnt == 10) { + sep = "\n"; + cnt = 0; + } else + sep = ", "; + + } + (void)fprintf(stderr, "\n"); +} +#endif diff --git a/main/db1-ast/recno/extern.h b/main/db1-ast/recno/extern.h new file mode 100644 index 000000000..feed43445 --- /dev/null +++ b/main/db1-ast/recno/extern.h @@ -0,0 +1,54 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)extern.h 8.3 (Berkeley) 6/4/94 + */ + +#include "../btree/extern.h" + +int __rec_close __P((DB *)); +int __rec_delete __P((const DB *, const DBT *, u_int)); +int __rec_dleaf __P((BTREE *, PAGE *, u_int32_t)); +int __rec_fd __P((const DB *)); +int __rec_fmap __P((BTREE *, recno_t)); +int __rec_fout __P((BTREE *)); +int __rec_fpipe __P((BTREE *, recno_t)); +int __rec_get __P((const DB *, const DBT *, DBT *, u_int)); +int __rec_iput __P((BTREE *, recno_t, const DBT *, u_int)); +int __rec_put __P((const DB *dbp, DBT *, const DBT *, u_int)); +int __rec_ret __P((BTREE *, EPG *, recno_t, DBT *, DBT *)); +EPG *__rec_search __P((BTREE *, recno_t, enum SRCHOP)); +int __rec_seq __P((const DB *, DBT *, DBT *, u_int)); +int __rec_sync __P((const DB *, u_int)); +int __rec_vmap __P((BTREE *, recno_t)); +int __rec_vout __P((BTREE *)); +int __rec_vpipe __P((BTREE *, recno_t)); diff --git a/main/db1-ast/recno/rec_close.c b/main/db1-ast/recno/rec_close.c new file mode 100644 index 000000000..8a3c4d77d --- /dev/null +++ b/main/db1-ast/recno/rec_close.c @@ -0,0 +1,183 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)rec_close.c 8.6 (Berkeley) 8/18/94"; +#endif /* LIBC_SCCS and not lint */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include "recno.h" + +/* + * __REC_CLOSE -- Close a recno tree. + * + * Parameters: + * dbp: pointer to access method + * + * Returns: + * RET_ERROR, RET_SUCCESS + */ +int +__rec_close(dbp) + DB *dbp; +{ + BTREE *t; + int status; + + t = dbp->internal; + + /* Toss any page pinned across calls. */ + if (t->bt_pinned != NULL) { + mpool_put(t->bt_mp, t->bt_pinned, 0); + t->bt_pinned = NULL; + } + + if (__rec_sync(dbp, 0) == RET_ERROR) + return (RET_ERROR); + + /* Committed to closing. */ + status = RET_SUCCESS; + if (F_ISSET(t, R_MEMMAPPED) && munmap(t->bt_smap, t->bt_msize)) + status = RET_ERROR; + + if (!F_ISSET(t, R_INMEM)) { + if (F_ISSET(t, R_CLOSEFP)) { + if (fclose(t->bt_rfp)) + status = RET_ERROR; + } else + if (close(t->bt_rfd)) + status = RET_ERROR; + } + + if (__bt_close(dbp) == RET_ERROR) + status = RET_ERROR; + + return (status); +} + +/* + * __REC_SYNC -- sync the recno tree to disk. + * + * Parameters: + * dbp: pointer to access method + * + * Returns: + * RET_SUCCESS, RET_ERROR. + */ +int +__rec_sync(dbp, flags) + const DB *dbp; + u_int flags; +{ + struct iovec iov[2]; + BTREE *t; + DBT data, key; + off_t off; + recno_t scursor, trec; + int status; + + t = dbp->internal; + + /* Toss any page pinned across calls. */ + if (t->bt_pinned != NULL) { + mpool_put(t->bt_mp, t->bt_pinned, 0); + t->bt_pinned = NULL; + } + + if (flags == R_RECNOSYNC) + return (__bt_sync(dbp, 0)); + + if (F_ISSET(t, R_RDONLY | R_INMEM) || !F_ISSET(t, R_MODIFIED)) + return (RET_SUCCESS); + + /* Read any remaining records into the tree. */ + if (!F_ISSET(t, R_EOF) && t->bt_irec(t, MAX_REC_NUMBER) == RET_ERROR) + return (RET_ERROR); + + /* Rewind the file descriptor. */ + if (lseek(t->bt_rfd, (off_t)0, SEEK_SET) != 0) + return (RET_ERROR); + + /* Save the cursor. */ + scursor = t->bt_cursor.rcursor; + + key.size = sizeof(recno_t); + key.data = &trec; + + if (F_ISSET(t, R_FIXLEN)) { + /* + * We assume that fixed length records are all fixed length. + * Any that aren't are either EINVAL'd or corrected by the + * record put code. + */ + status = (dbp->seq)(dbp, &key, &data, R_FIRST); + while (status == RET_SUCCESS) { + if ((size_t) write(t->bt_rfd, data.data, data.size) != data.size) + return (RET_ERROR); + status = (dbp->seq)(dbp, &key, &data, R_NEXT); + } + } else { + iov[1].iov_base = &t->bt_bval; + iov[1].iov_len = 1; + + status = (dbp->seq)(dbp, &key, &data, R_FIRST); + while (status == RET_SUCCESS) { + iov[0].iov_base = data.data; + iov[0].iov_len = data.size; + if ((size_t) writev(t->bt_rfd, iov, 2) != data.size + 1) + return (RET_ERROR); + status = (dbp->seq)(dbp, &key, &data, R_NEXT); + } + } + + /* Restore the cursor. */ + t->bt_cursor.rcursor = scursor; + + if (status == RET_ERROR) + return (RET_ERROR); + if ((off = lseek(t->bt_rfd, (off_t)0, SEEK_CUR)) == -1) + return (RET_ERROR); + if (ftruncate(t->bt_rfd, off)) + return (RET_ERROR); + F_CLR(t, R_MODIFIED); + return (RET_SUCCESS); +} diff --git a/main/db1-ast/recno/rec_delete.c b/main/db1-ast/recno/rec_delete.c new file mode 100644 index 000000000..a16593d4e --- /dev/null +++ b/main/db1-ast/recno/rec_delete.c @@ -0,0 +1,197 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Olson. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)rec_delete.c 8.7 (Berkeley) 7/14/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include +#include +#include + +#include +#include "recno.h" + +static int rec_rdelete __P((BTREE *, recno_t)); + +/* + * __REC_DELETE -- Delete the item(s) referenced by a key. + * + * Parameters: + * dbp: pointer to access method + * key: key to delete + * flags: R_CURSOR if deleting what the cursor references + * + * Returns: + * RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key not found. + */ +int +__rec_delete(dbp, key, flags) + const DB *dbp; + const DBT *key; + u_int flags; +{ + BTREE *t; + recno_t nrec; + int status; + + t = dbp->internal; + + /* Toss any page pinned across calls. */ + if (t->bt_pinned != NULL) { + mpool_put(t->bt_mp, t->bt_pinned, 0); + t->bt_pinned = NULL; + } + + switch(flags) { + case 0: + if ((nrec = *(recno_t *)key->data) == 0) + goto einval; + if (nrec > t->bt_nrecs) + return (RET_SPECIAL); + --nrec; + status = rec_rdelete(t, nrec); + break; + case R_CURSOR: + if (!F_ISSET(&t->bt_cursor, CURS_INIT)) + goto einval; + if (t->bt_nrecs == 0) + return (RET_SPECIAL); + status = rec_rdelete(t, t->bt_cursor.rcursor - 1); + if (status == RET_SUCCESS) + --t->bt_cursor.rcursor; + break; + default: +einval: errno = EINVAL; + return (RET_ERROR); + } + + if (status == RET_SUCCESS) + F_SET(t, B_MODIFIED | R_MODIFIED); + return (status); +} + +/* + * REC_RDELETE -- Delete the data matching the specified key. + * + * Parameters: + * tree: tree + * nrec: record to delete + * + * Returns: + * RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key not found. + */ +static int +rec_rdelete(t, nrec) + BTREE *t; + recno_t nrec; +{ + EPG *e; + PAGE *h; + int status; + + /* Find the record; __rec_search pins the page. */ + if ((e = __rec_search(t, nrec, SDELETE)) == NULL) + return (RET_ERROR); + + /* Delete the record. */ + h = e->page; + status = __rec_dleaf(t, h, e->index); + if (status != RET_SUCCESS) { + mpool_put(t->bt_mp, h, 0); + return (status); + } + mpool_put(t->bt_mp, h, MPOOL_DIRTY); + return (RET_SUCCESS); +} + +/* + * __REC_DLEAF -- Delete a single record from a recno leaf page. + * + * Parameters: + * t: tree + * index: index on current page to delete + * + * Returns: + * RET_SUCCESS, RET_ERROR. + */ +int +__rec_dleaf(t, h, index) + BTREE *t; + PAGE *h; + u_int32_t index; +{ + RLEAF *rl; + indx_t *ip, cnt, offset; + u_int32_t nbytes; + char *from; + void *to; + + /* + * Delete a record from a recno leaf page. Internal records are never + * deleted from internal pages, regardless of the records that caused + * them to be added being deleted. Pages made empty by deletion are + * not reclaimed. They are, however, made available for reuse. + * + * Pack the remaining entries at the end of the page, shift the indices + * down, overwriting the deleted record and its index. If the record + * uses overflow pages, make them available for reuse. + */ + to = rl = GETRLEAF(h, index); + if (rl->flags & P_BIGDATA && __ovfl_delete(t, rl->bytes) == RET_ERROR) + return (RET_ERROR); + nbytes = NRLEAF(rl); + + /* + * Compress the key/data pairs. Compress and adjust the [BR]LEAF + * offsets. Reset the headers. + */ + from = (char *)h + h->upper; + memmove(from + nbytes, from, (char *)to - from); + h->upper += nbytes; + + offset = h->linp[index]; + for (cnt = &h->linp[index] - (ip = &h->linp[0]); cnt--; ++ip) + if (ip[0] < offset) + ip[0] += nbytes; + for (cnt = &h->linp[NEXTINDEX(h)] - ip; --cnt; ++ip) + ip[0] = ip[1] < offset ? ip[1] + nbytes : ip[1]; + h->lower -= sizeof(indx_t); + --t->bt_nrecs; + return (RET_SUCCESS); +} diff --git a/main/db1-ast/recno/rec_get.c b/main/db1-ast/recno/rec_get.c new file mode 100644 index 000000000..47dd773fb --- /dev/null +++ b/main/db1-ast/recno/rec_get.c @@ -0,0 +1,311 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)rec_get.c 8.9 (Berkeley) 8/18/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include "recno.h" + +/* + * __REC_GET -- Get a record from the btree. + * + * Parameters: + * dbp: pointer to access method + * key: key to find + * data: data to return + * flag: currently unused + * + * Returns: + * RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key not found. + */ +int +__rec_get(dbp, key, data, flags) + const DB *dbp; + const DBT *key; + DBT *data; + u_int flags; +{ + BTREE *t; + EPG *e; + recno_t nrec; + int status; + + t = dbp->internal; + + /* Toss any page pinned across calls. */ + if (t->bt_pinned != NULL) { + mpool_put(t->bt_mp, t->bt_pinned, 0); + t->bt_pinned = NULL; + } + + /* Get currently doesn't take any flags, and keys of 0 are illegal. */ + if (flags || (nrec = *(recno_t *)key->data) == 0) { + errno = EINVAL; + return (RET_ERROR); + } + + /* + * If we haven't seen this record yet, try to find it in the + * original file. + */ + if (nrec > t->bt_nrecs) { + if (F_ISSET(t, R_EOF | R_INMEM)) + return (RET_SPECIAL); + if ((status = t->bt_irec(t, nrec)) != RET_SUCCESS) + return (status); + } + + --nrec; + if ((e = __rec_search(t, nrec, SEARCH)) == NULL) + return (RET_ERROR); + + status = __rec_ret(t, e, 0, NULL, data); + if (F_ISSET(t, B_DB_LOCK)) + mpool_put(t->bt_mp, e->page, 0); + else + t->bt_pinned = e->page; + return (status); +} + +/* + * __REC_FPIPE -- Get fixed length records from a pipe. + * + * Parameters: + * t: tree + * cnt: records to read + * + * Returns: + * RET_ERROR, RET_SUCCESS + */ +int +__rec_fpipe(t, top) + BTREE *t; + recno_t top; +{ + DBT data; + recno_t nrec; + size_t len; + int ch; + u_char *p; + + if (t->bt_rdata.size < t->bt_reclen) { + t->bt_rdata.data = t->bt_rdata.data == NULL ? + malloc(t->bt_reclen) : + realloc(t->bt_rdata.data, t->bt_reclen); + if (t->bt_rdata.data == NULL) + return (RET_ERROR); + t->bt_rdata.size = t->bt_reclen; + } + data.data = t->bt_rdata.data; + data.size = t->bt_reclen; + + for (nrec = t->bt_nrecs; nrec < top;) { + len = t->bt_reclen; + for (p = t->bt_rdata.data;; *p++ = ch) + if ((ch = getc(t->bt_rfp)) == EOF || !--len) { + if (ch != EOF) + *p = ch; + if (len != 0) + memset(p, t->bt_bval, len); + if (__rec_iput(t, + nrec, &data, 0) != RET_SUCCESS) + return (RET_ERROR); + ++nrec; + break; + } + if (ch == EOF) + break; + } + if (nrec < top) { + F_SET(t, R_EOF); + return (RET_SPECIAL); + } + return (RET_SUCCESS); +} + +/* + * __REC_VPIPE -- Get variable length records from a pipe. + * + * Parameters: + * t: tree + * cnt: records to read + * + * Returns: + * RET_ERROR, RET_SUCCESS + */ +int +__rec_vpipe(t, top) + BTREE *t; + recno_t top; +{ + DBT data; + recno_t nrec; + indx_t len; + size_t sz; + int bval, ch; + u_char *p; + + bval = t->bt_bval; + for (nrec = t->bt_nrecs; nrec < top; ++nrec) { + for (p = t->bt_rdata.data, + sz = t->bt_rdata.size;; *p++ = ch, --sz) { + if ((ch = getc(t->bt_rfp)) == EOF || ch == bval) { + data.data = t->bt_rdata.data; + data.size = p - (u_char *)t->bt_rdata.data; + if (ch == EOF && data.size == 0) + break; + if (__rec_iput(t, nrec, &data, 0) + != RET_SUCCESS) + return (RET_ERROR); + break; + } + if (sz == 0) { + len = p - (u_char *)t->bt_rdata.data; + t->bt_rdata.size += (sz = 256); + t->bt_rdata.data = t->bt_rdata.data == NULL ? + malloc(t->bt_rdata.size) : + realloc(t->bt_rdata.data, t->bt_rdata.size); + if (t->bt_rdata.data == NULL) + return (RET_ERROR); + p = (u_char *)t->bt_rdata.data + len; + } + } + if (ch == EOF) + break; + } + if (nrec < top) { + F_SET(t, R_EOF); + return (RET_SPECIAL); + } + return (RET_SUCCESS); +} + +/* + * __REC_FMAP -- Get fixed length records from a file. + * + * Parameters: + * t: tree + * cnt: records to read + * + * Returns: + * RET_ERROR, RET_SUCCESS + */ +int +__rec_fmap(t, top) + BTREE *t; + recno_t top; +{ + DBT data; + recno_t nrec; + u_char *sp, *ep, *p; + size_t len; + + if (t->bt_rdata.size < t->bt_reclen) { + t->bt_rdata.data = t->bt_rdata.data == NULL ? + malloc(t->bt_reclen) : + realloc(t->bt_rdata.data, t->bt_reclen); + if (t->bt_rdata.data == NULL) + return (RET_ERROR); + t->bt_rdata.size = t->bt_reclen; + } + data.data = t->bt_rdata.data; + data.size = t->bt_reclen; + + sp = (u_char *)t->bt_cmap; + ep = (u_char *)t->bt_emap; + for (nrec = t->bt_nrecs; nrec < top; ++nrec) { + if (sp >= ep) { + F_SET(t, R_EOF); + return (RET_SPECIAL); + } + len = t->bt_reclen; + for (p = t->bt_rdata.data; + sp < ep && len > 0; *p++ = *sp++, --len); + if (len != 0) + memset(p, t->bt_bval, len); + if (__rec_iput(t, nrec, &data, 0) != RET_SUCCESS) + return (RET_ERROR); + } + t->bt_cmap = (caddr_t)sp; + return (RET_SUCCESS); +} + +/* + * __REC_VMAP -- Get variable length records from a file. + * + * Parameters: + * t: tree + * cnt: records to read + * + * Returns: + * RET_ERROR, RET_SUCCESS + */ +int +__rec_vmap(t, top) + BTREE *t; + recno_t top; +{ + DBT data; + u_char *sp, *ep; + recno_t nrec; + int bval; + + sp = (u_char *)t->bt_cmap; + ep = (u_char *)t->bt_emap; + bval = t->bt_bval; + + for (nrec = t->bt_nrecs; nrec < top; ++nrec) { + if (sp >= ep) { + F_SET(t, R_EOF); + return (RET_SPECIAL); + } + for (data.data = sp; sp < ep && *sp != bval; ++sp); + data.size = sp - (u_char *)data.data; + if (__rec_iput(t, nrec, &data, 0) != RET_SUCCESS) + return (RET_ERROR); + ++sp; + } + t->bt_cmap = (caddr_t)sp; + return (RET_SUCCESS); +} diff --git a/main/db1-ast/recno/rec_open.c b/main/db1-ast/recno/rec_open.c new file mode 100644 index 000000000..cf9511e87 --- /dev/null +++ b/main/db1-ast/recno/rec_open.c @@ -0,0 +1,241 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Olson. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)rec_open.c 8.10 (Berkeley) 9/1/94"; +#endif /* LIBC_SCCS and not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include "recno.h" + +DB * +__rec_open(fname, flags, mode, openinfo, dflags) + const char *fname; + int flags, mode, dflags; + const RECNOINFO *openinfo; +{ + BTREE *t; + BTREEINFO btopeninfo; + DB *dbp; + PAGE *h; + struct stat sb; + int rfd = 0, sverrno; + + /* Open the user's file -- if this fails, we're done. */ + if (fname != NULL && (rfd = open(fname, flags, mode)) < 0) + return (NULL); + + /* Create a btree in memory (backed by disk). */ + dbp = NULL; + if (openinfo) { + if (openinfo->flags & ~(R_FIXEDLEN | R_NOKEY | R_SNAPSHOT)) + goto einval; + btopeninfo.flags = 0; + btopeninfo.cachesize = openinfo->cachesize; + btopeninfo.maxkeypage = 0; + btopeninfo.minkeypage = 0; + btopeninfo.psize = openinfo->psize; + btopeninfo.compare = NULL; + btopeninfo.prefix = NULL; + btopeninfo.lorder = openinfo->lorder; + dbp = __bt_open(openinfo->bfname, + O_RDWR, S_IRUSR | S_IWUSR, &btopeninfo, dflags); + } else + dbp = __bt_open(NULL, O_RDWR, S_IRUSR | S_IWUSR, NULL, dflags); + if (dbp == NULL) + goto err; + + /* + * Some fields in the tree structure are recno specific. Fill them + * in and make the btree structure look like a recno structure. We + * don't change the bt_ovflsize value, it's close enough and slightly + * bigger. + */ + t = dbp->internal; + if (openinfo) { + if (openinfo->flags & R_FIXEDLEN) { + F_SET(t, R_FIXLEN); + t->bt_reclen = openinfo->reclen; + if (t->bt_reclen == 0) + goto einval; + } + t->bt_bval = openinfo->bval; + } else + t->bt_bval = '\n'; + + F_SET(t, R_RECNO); + if (fname == NULL) + F_SET(t, R_EOF | R_INMEM); + else + t->bt_rfd = rfd; + + if (fname != NULL) { + /* + * In 4.4BSD, stat(2) returns true for ISSOCK on pipes. + * Unfortunately, that's not portable, so we use lseek + * and check the errno values. + */ + errno = 0; + if (lseek(rfd, (off_t)0, SEEK_CUR) == -1 && errno == ESPIPE) { + switch (flags & O_ACCMODE) { + case O_RDONLY: + F_SET(t, R_RDONLY); + break; + default: + goto einval; + } +slow: if ((t->bt_rfp = fdopen(rfd, "r")) == NULL) + goto err; + F_SET(t, R_CLOSEFP); + t->bt_irec = + F_ISSET(t, R_FIXLEN) ? __rec_fpipe : __rec_vpipe; + } else { + switch (flags & O_ACCMODE) { + case O_RDONLY: + F_SET(t, R_RDONLY); + break; + case O_RDWR: + break; + default: + goto einval; + } + + if (fstat(rfd, &sb)) + goto err; + /* + * Kluge -- we'd like to test to see if the file is too + * big to mmap. Since, we don't know what size or type + * off_t's or size_t's are, what the largest unsigned + * integral type is, or what random insanity the local + * C compiler will perpetrate, doing the comparison in + * a portable way is flatly impossible. Hope that mmap + * fails if the file is too large. + */ + if (sb.st_size == 0) + F_SET(t, R_EOF); + else { +#ifdef MMAP_NOT_AVAILABLE + /* + * XXX + * Mmap doesn't work correctly on many current + * systems. In particular, it can fail subtly, + * with cache coherency problems. Don't use it + * for now. + */ + t->bt_msize = sb.st_size; + if ((t->bt_smap = mmap(NULL, t->bt_msize, + PROT_READ, MAP_PRIVATE, rfd, + (off_t)0)) == (caddr_t)-1) + goto slow; + t->bt_cmap = t->bt_smap; + t->bt_emap = t->bt_smap + sb.st_size; + t->bt_irec = F_ISSET(t, R_FIXLEN) ? + __rec_fmap : __rec_vmap; + F_SET(t, R_MEMMAPPED); +#else + goto slow; +#endif + } + } + } + + /* Use the recno routines. */ + dbp->close = __rec_close; + dbp->del = __rec_delete; + dbp->fd = __rec_fd; + dbp->get = __rec_get; + dbp->put = __rec_put; + dbp->seq = __rec_seq; + dbp->sync = __rec_sync; + + /* If the root page was created, reset the flags. */ + if ((h = mpool_get(t->bt_mp, P_ROOT, 0)) == NULL) + goto err; + if ((h->flags & P_TYPE) == P_BLEAF) { + F_CLR(h, P_TYPE); + F_SET(h, P_RLEAF); + mpool_put(t->bt_mp, h, MPOOL_DIRTY); + } else + mpool_put(t->bt_mp, h, 0); + + if (openinfo && openinfo->flags & R_SNAPSHOT && + !F_ISSET(t, R_EOF | R_INMEM) && + t->bt_irec(t, MAX_REC_NUMBER) == RET_ERROR) + goto err; + return (dbp); + +einval: errno = EINVAL; +err: sverrno = errno; + if (dbp != NULL) + (void)__bt_close(dbp); + if (fname != NULL) + (void)close(rfd); + errno = sverrno; + return (NULL); +} + +int +__rec_fd(dbp) + const DB *dbp; +{ + BTREE *t; + + t = dbp->internal; + + /* Toss any page pinned across calls. */ + if (t->bt_pinned != NULL) { + mpool_put(t->bt_mp, t->bt_pinned, 0); + t->bt_pinned = NULL; + } + + /* In-memory database can't have a file descriptor. */ + if (F_ISSET(t, R_INMEM)) { + errno = ENOENT; + return (-1); + } + return (t->bt_rfd); +} diff --git a/main/db1-ast/recno/rec_put.c b/main/db1-ast/recno/rec_put.c new file mode 100644 index 000000000..5454c40e8 --- /dev/null +++ b/main/db1-ast/recno/rec_put.c @@ -0,0 +1,280 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)rec_put.c 8.7 (Berkeley) 8/18/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include +#include +#include +#include + +#include +#include "recno.h" + +/* + * __REC_PUT -- Add a recno item to the tree. + * + * Parameters: + * dbp: pointer to access method + * key: key + * data: data + * flag: R_CURSOR, R_IAFTER, R_IBEFORE, R_NOOVERWRITE + * + * Returns: + * RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key is + * already in the tree and R_NOOVERWRITE specified. + */ +int +__rec_put(dbp, key, data, flags) + const DB *dbp; + DBT *key; + const DBT *data; + u_int flags; +{ + BTREE *t; + DBT fdata, tdata; + recno_t nrec; + int status; + + t = dbp->internal; + + /* Toss any page pinned across calls. */ + if (t->bt_pinned != NULL) { + mpool_put(t->bt_mp, t->bt_pinned, 0); + t->bt_pinned = NULL; + } + + /* + * If using fixed-length records, and the record is long, return + * EINVAL. If it's short, pad it out. Use the record data return + * memory, it's only short-term. + */ + if (F_ISSET(t, R_FIXLEN) && data->size != t->bt_reclen) { + if (data->size > t->bt_reclen) + goto einval; + + if (t->bt_rdata.size < t->bt_reclen) { + t->bt_rdata.data = t->bt_rdata.data == NULL ? + malloc(t->bt_reclen) : + realloc(t->bt_rdata.data, t->bt_reclen); + if (t->bt_rdata.data == NULL) + return (RET_ERROR); + t->bt_rdata.size = t->bt_reclen; + } + memmove(t->bt_rdata.data, data->data, data->size); + memset((char *)t->bt_rdata.data + data->size, + t->bt_bval, t->bt_reclen - data->size); + fdata.data = t->bt_rdata.data; + fdata.size = t->bt_reclen; + } else { + fdata.data = data->data; + fdata.size = data->size; + } + + switch (flags) { + case R_CURSOR: + if (!F_ISSET(&t->bt_cursor, CURS_INIT)) + goto einval; + nrec = t->bt_cursor.rcursor; + break; + case R_SETCURSOR: + if ((nrec = *(recno_t *)key->data) == 0) + goto einval; + break; + case R_IAFTER: + if ((nrec = *(recno_t *)key->data) == 0) { + nrec = 1; + flags = R_IBEFORE; + } + break; + case 0: + case R_IBEFORE: + if ((nrec = *(recno_t *)key->data) == 0) + goto einval; + break; + case R_NOOVERWRITE: + if ((nrec = *(recno_t *)key->data) == 0) + goto einval; + if (nrec <= t->bt_nrecs) + return (RET_SPECIAL); + break; + default: +einval: errno = EINVAL; + return (RET_ERROR); + } + + /* + * Make sure that records up to and including the put record are + * already in the database. If skipping records, create empty ones. + */ + if (nrec > t->bt_nrecs) { + if (!F_ISSET(t, R_EOF | R_INMEM) && + t->bt_irec(t, nrec) == RET_ERROR) + return (RET_ERROR); + if (nrec > t->bt_nrecs + 1) { + if (F_ISSET(t, R_FIXLEN)) { + if ((tdata.data = + (void *)malloc(t->bt_reclen)) == NULL) + return (RET_ERROR); + tdata.size = t->bt_reclen; + memset(tdata.data, t->bt_bval, tdata.size); + } else { + tdata.data = NULL; + tdata.size = 0; + } + while (nrec > t->bt_nrecs + 1) + if (__rec_iput(t, + t->bt_nrecs, &tdata, 0) != RET_SUCCESS) + return (RET_ERROR); + if (F_ISSET(t, R_FIXLEN)) + free(tdata.data); + } + } + + if ((status = __rec_iput(t, nrec - 1, &fdata, flags)) != RET_SUCCESS) + return (status); + + if (flags == R_SETCURSOR) + t->bt_cursor.rcursor = nrec; + + F_SET(t, R_MODIFIED); + return (__rec_ret(t, NULL, nrec, key, NULL)); +} + +/* + * __REC_IPUT -- Add a recno item to the tree. + * + * Parameters: + * t: tree + * nrec: record number + * data: data + * + * Returns: + * RET_ERROR, RET_SUCCESS + */ +int +__rec_iput(t, nrec, data, flags) + BTREE *t; + recno_t nrec; + const DBT *data; + u_int flags; +{ + DBT tdata; + EPG *e; + PAGE *h; + indx_t index, nxtindex; + pgno_t pg; + u_int32_t nbytes; + int dflags, status; + char *dest, db[NOVFLSIZE]; + + /* + * If the data won't fit on a page, store it on indirect pages. + * + * XXX + * If the insert fails later on, these pages aren't recovered. + */ + if (data->size > t->bt_ovflsize) { + if (__ovfl_put(t, data, &pg) == RET_ERROR) + return (RET_ERROR); + tdata.data = db; + tdata.size = NOVFLSIZE; + *(pgno_t *)db = pg; + *(u_int32_t *)(db + sizeof(pgno_t)) = data->size; + dflags = P_BIGDATA; + data = &tdata; + } else + dflags = 0; + + /* __rec_search pins the returned page. */ + if ((e = __rec_search(t, nrec, + nrec > t->bt_nrecs || flags == R_IAFTER || flags == R_IBEFORE ? + SINSERT : SEARCH)) == NULL) + return (RET_ERROR); + + h = e->page; + index = e->index; + + /* + * Add the specified key/data pair to the tree. The R_IAFTER and + * R_IBEFORE flags insert the key after/before the specified key. + * + * Pages are split as required. + */ + switch (flags) { + case R_IAFTER: + ++index; + break; + case R_IBEFORE: + break; + default: + if (nrec < t->bt_nrecs && + __rec_dleaf(t, h, index) == RET_ERROR) { + mpool_put(t->bt_mp, h, 0); + return (RET_ERROR); + } + break; + } + + /* + * If not enough room, split the page. The split code will insert + * the key and data and unpin the current page. If inserting into + * the offset array, shift the pointers up. + */ + nbytes = NRLEAFDBT(data->size); + if ((u_int32_t) (h->upper - h->lower) < nbytes + sizeof(indx_t)) { + status = __bt_split(t, h, NULL, data, dflags, nbytes, index); + if (status == RET_SUCCESS) + ++t->bt_nrecs; + return (status); + } + + if (index < (nxtindex = NEXTINDEX(h))) + memmove(h->linp + index + 1, h->linp + index, + (nxtindex - index) * sizeof(indx_t)); + h->lower += sizeof(indx_t); + + h->linp[index] = h->upper -= nbytes; + dest = (char *)h + h->upper; + WR_RLEAF(dest, data, dflags); + + ++t->bt_nrecs; + F_SET(t, B_MODIFIED); + mpool_put(t->bt_mp, h, MPOOL_DIRTY); + + return (RET_SUCCESS); +} diff --git a/main/db1-ast/recno/rec_search.c b/main/db1-ast/recno/rec_search.c new file mode 100644 index 000000000..acc109e99 --- /dev/null +++ b/main/db1-ast/recno/rec_search.c @@ -0,0 +1,126 @@ +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)rec_search.c 8.4 (Berkeley) 7/14/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include +#include + +#include +#include "recno.h" + +/* + * __REC_SEARCH -- Search a btree for a key. + * + * Parameters: + * t: tree to search + * recno: key to find + * op: search operation + * + * Returns: + * EPG for matching record, if any, or the EPG for the location of the + * key, if it were inserted into the tree. + * + * Returns: + * The EPG for matching record, if any, or the EPG for the location + * of the key, if it were inserted into the tree, is entered into + * the bt_cur field of the tree. A pointer to the field is returned. + */ +EPG * +__rec_search(t, recno, op) + BTREE *t; + recno_t recno; + enum SRCHOP op; +{ + register indx_t index; + register PAGE *h; + EPGNO *parent; + RINTERNAL *r; + pgno_t pg; + indx_t top; + recno_t total; + int sverrno; + + BT_CLR(t); + for (pg = P_ROOT, total = 0;;) { + if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) + goto err; + if (h->flags & P_RLEAF) { + t->bt_cur.page = h; + t->bt_cur.index = recno - total; + return (&t->bt_cur); + } + for (index = 0, top = NEXTINDEX(h);;) { + r = GETRINTERNAL(h, index); + if (++index == top || total + r->nrecs > recno) + break; + total += r->nrecs; + } + + BT_PUSH(t, pg, index - 1); + + pg = r->pgno; + switch (op) { + case SDELETE: + --GETRINTERNAL(h, (index - 1))->nrecs; + mpool_put(t->bt_mp, h, MPOOL_DIRTY); + break; + case SINSERT: + ++GETRINTERNAL(h, (index - 1))->nrecs; + mpool_put(t->bt_mp, h, MPOOL_DIRTY); + break; + case SEARCH: + mpool_put(t->bt_mp, h, 0); + break; + } + + } + /* Try and recover the tree. */ +err: sverrno = errno; + if (op != SEARCH) + while ((parent = BT_POP(t)) != NULL) { + if ((h = mpool_get(t->bt_mp, parent->pgno, 0)) == NULL) + break; + if (op == SINSERT) + --GETRINTERNAL(h, parent->index)->nrecs; + else + ++GETRINTERNAL(h, parent->index)->nrecs; + mpool_put(t->bt_mp, h, MPOOL_DIRTY); + } + errno = sverrno; + return (NULL); +} diff --git a/main/db1-ast/recno/rec_seq.c b/main/db1-ast/recno/rec_seq.c new file mode 100644 index 000000000..2f8c7695c --- /dev/null +++ b/main/db1-ast/recno/rec_seq.c @@ -0,0 +1,131 @@ +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)rec_seq.c 8.3 (Berkeley) 7/14/94"; +#endif /* not lint */ + +#include + +#include +#include +#include +#include + +#include +#include "recno.h" + +/* + * __REC_SEQ -- Recno sequential scan interface. + * + * Parameters: + * dbp: pointer to access method + * key: key for positioning and return value + * data: data return value + * flags: R_CURSOR, R_FIRST, R_LAST, R_NEXT, R_PREV. + * + * Returns: + * RET_ERROR, RET_SUCCESS or RET_SPECIAL if there's no next key. + */ +int +__rec_seq(dbp, key, data, flags) + const DB *dbp; + DBT *key, *data; + u_int flags; +{ + BTREE *t; + EPG *e; + recno_t nrec; + int status; + + t = dbp->internal; + + /* Toss any page pinned across calls. */ + if (t->bt_pinned != NULL) { + mpool_put(t->bt_mp, t->bt_pinned, 0); + t->bt_pinned = NULL; + } + + switch(flags) { + case R_CURSOR: + if ((nrec = *(recno_t *)key->data) == 0) + goto einval; + break; + case R_NEXT: + if (F_ISSET(&t->bt_cursor, CURS_INIT)) { + nrec = t->bt_cursor.rcursor + 1; + break; + } + /* FALLTHROUGH */ + case R_FIRST: + nrec = 1; + break; + case R_PREV: + if (F_ISSET(&t->bt_cursor, CURS_INIT)) { + if ((nrec = t->bt_cursor.rcursor - 1) == 0) + return (RET_SPECIAL); + break; + } + /* FALLTHROUGH */ + case R_LAST: + if (!F_ISSET(t, R_EOF | R_INMEM) && + t->bt_irec(t, MAX_REC_NUMBER) == RET_ERROR) + return (RET_ERROR); + nrec = t->bt_nrecs; + break; + default: +einval: errno = EINVAL; + return (RET_ERROR); + } + + if (t->bt_nrecs == 0 || nrec > t->bt_nrecs) { + if (!F_ISSET(t, R_EOF | R_INMEM) && + (status = t->bt_irec(t, nrec)) != RET_SUCCESS) + return (status); + if (t->bt_nrecs == 0 || nrec > t->bt_nrecs) + return (RET_SPECIAL); + } + + if ((e = __rec_search(t, nrec - 1, SEARCH)) == NULL) + return (RET_ERROR); + + F_SET(&t->bt_cursor, CURS_INIT); + t->bt_cursor.rcursor = nrec; + + status = __rec_ret(t, e, nrec, key, data); + if (F_ISSET(t, B_DB_LOCK)) + mpool_put(t->bt_mp, e->page, 0); + else + t->bt_pinned = e->page; + return (status); +} diff --git a/main/db1-ast/recno/rec_utils.c b/main/db1-ast/recno/rec_utils.c new file mode 100644 index 000000000..c4c038046 --- /dev/null +++ b/main/db1-ast/recno/rec_utils.c @@ -0,0 +1,122 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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 defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)rec_utils.c 8.6 (Berkeley) 7/16/94"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include +#include +#include + +#include +#include "recno.h" + +/* + * __rec_ret -- + * Build return data. + * + * Parameters: + * t: tree + * e: key/data pair to be returned + * nrec: record number + * key: user's key structure + * data: user's data structure + * + * Returns: + * RET_SUCCESS, RET_ERROR. + */ +int +__rec_ret(t, e, nrec, key, data) + BTREE *t; + EPG *e; + recno_t nrec; + DBT *key, *data; +{ + RLEAF *rl; + void *p; + + if (key == NULL) + goto dataonly; + + /* We have to copy the key, it's not on the page. */ + if (sizeof(recno_t) > t->bt_rkey.size) { + p = (void *)(t->bt_rkey.data == NULL ? + malloc(sizeof(recno_t)) : + realloc(t->bt_rkey.data, sizeof(recno_t))); + if (p == NULL) + return (RET_ERROR); + t->bt_rkey.data = p; + t->bt_rkey.size = sizeof(recno_t); + } + memmove(t->bt_rkey.data, &nrec, sizeof(recno_t)); + key->size = sizeof(recno_t); + key->data = t->bt_rkey.data; + +dataonly: + if (data == NULL) + return (RET_SUCCESS); + + /* + * We must copy big keys/data to make them contiguous. Otherwise, + * leave the page pinned and don't copy unless the user specified + * concurrent access. + */ + rl = GETRLEAF(e->page, e->index); + if (rl->flags & P_BIGDATA) { + if (__ovfl_get(t, rl->bytes, + &data->size, &t->bt_rdata.data, &t->bt_rdata.size)) + return (RET_ERROR); + data->data = t->bt_rdata.data; + } else if (F_ISSET(t, B_DB_LOCK)) { + /* Use +1 in case the first record retrieved is 0 length. */ + if (rl->dsize + 1 > t->bt_rdata.size) { + p = (void *)(t->bt_rdata.data == NULL ? + malloc(rl->dsize + 1) : + realloc(t->bt_rdata.data, rl->dsize + 1)); + if (p == NULL) + return (RET_ERROR); + t->bt_rdata.data = p; + t->bt_rdata.size = rl->dsize + 1; + } + memmove(t->bt_rdata.data, rl->bytes, rl->dsize); + data->size = rl->dsize; + data->data = t->bt_rdata.data; + } else { + data->size = rl->dsize; + data->data = rl->bytes; + } + return (RET_SUCCESS); +} diff --git a/main/db1-ast/recno/recno.h b/main/db1-ast/recno/recno.h new file mode 100644 index 000000000..bec772c2f --- /dev/null +++ b/main/db1-ast/recno/recno.h @@ -0,0 +1,39 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)recno.h 8.1 (Berkeley) 6/4/93 + */ + +enum SRCHOP { SDELETE, SINSERT, SEARCH}; /* Rec_search operation. */ + +#include "../btree/btree.h" +#include "extern.h" diff --git a/main/devicestate.c b/main/devicestate.c new file mode 100644 index 000000000..c434ce9a0 --- /dev/null +++ b/main/devicestate.c @@ -0,0 +1,383 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Device state management + * + * \author Mark Spencer + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include + +#include "asterisk/channel.h" +#include "asterisk/utils.h" +#include "asterisk/lock.h" +#include "asterisk/linkedlists.h" +#include "asterisk/logger.h" +#include "asterisk/devicestate.h" +#include "asterisk/pbx.h" +#include "asterisk/app.h" +#include "asterisk/options.h" + +/*! \brief Device state strings for printing */ +static const char *devstatestring[] = { + /* 0 AST_DEVICE_UNKNOWN */ "Unknown", /*!< Valid, but unknown state */ + /* 1 AST_DEVICE_NOT_INUSE */ "Not in use", /*!< Not used */ + /* 2 AST_DEVICE IN USE */ "In use", /*!< In use */ + /* 3 AST_DEVICE_BUSY */ "Busy", /*!< Busy */ + /* 4 AST_DEVICE_INVALID */ "Invalid", /*!< Invalid - not known to Asterisk */ + /* 5 AST_DEVICE_UNAVAILABLE */ "Unavailable", /*!< Unavailable (not registred) */ + /* 6 AST_DEVICE_RINGING */ "Ringing", /*!< Ring, ring, ring */ + /* 7 AST_DEVICE_RINGINUSE */ "Ring+Inuse", /*!< Ring and in use */ + /* 8 AST_DEVICE_ONHOLD */ "On Hold" /*!< On Hold */ +}; + +/*! \brief A device state provider (not a channel) */ +struct devstate_prov { + char label[40]; + ast_devstate_prov_cb_type callback; + AST_LIST_ENTRY(devstate_prov) list; +}; + +/*! \brief A list of providers */ +static AST_LIST_HEAD_STATIC(devstate_provs, devstate_prov); + +/*! \brief A device state watcher (callback) */ +struct devstate_cb { + void *data; + ast_devstate_cb_type callback; + AST_LIST_ENTRY(devstate_cb) list; +}; + +/*! \brief A device state watcher list */ +static AST_LIST_HEAD_STATIC(devstate_cbs, devstate_cb); + +struct state_change { + AST_LIST_ENTRY(state_change) list; + char device[1]; +}; + +/*! \brief The state change queue. State changes are queued + for processing by a separate thread */ +static AST_LIST_HEAD_STATIC(state_changes, state_change); + +/*! \brief The device state change notification thread */ +static pthread_t change_thread = AST_PTHREADT_NULL; + +/*! \brief Flag for the queue */ +static ast_cond_t change_pending; + +/* Forward declarations */ +static int getproviderstate(const char *provider, const char *address); + +/*! \brief Find devicestate as text message for output */ +const char *devstate2str(int devstate) +{ + return devstatestring[devstate]; +} + +/*! \brief Find out if device is active in a call or not + \note find channels with the device's name in it + This function is only used for channels that does not implement + devicestate natively +*/ +int ast_parse_device_state(const char *device) +{ + struct ast_channel *chan; + char match[AST_CHANNEL_NAME]; + int res; + + ast_copy_string(match, device, sizeof(match)-1); + strcat(match, "-"); + chan = ast_get_channel_by_name_prefix_locked(match, strlen(match)); + + if (!chan) + return AST_DEVICE_UNKNOWN; + + if (chan->_state == AST_STATE_RINGING) + res = AST_DEVICE_RINGING; + else + res = AST_DEVICE_INUSE; + + ast_channel_unlock(chan); + + return res; +} + +/*! \brief Check device state through channel specific function or generic function */ +int ast_device_state(const char *device) +{ + char *buf; + char *number; + const struct ast_channel_tech *chan_tech; + int res = 0; + /*! \brief Channel driver that provides device state */ + char *tech; + /*! \brief Another provider of device state */ + char *provider = NULL; + + buf = ast_strdupa(device); + tech = strsep(&buf, "/"); + number = buf; + if (!number) { + provider = strsep(&tech, ":"); + if (!provider) + return AST_DEVICE_INVALID; + /* We have a provider */ + number = tech; + tech = NULL; + } + + if (provider) { + if(option_debug > 2) + ast_log(LOG_DEBUG, "Checking if I can find provider for \"%s\" - number: %s\n", provider, number); + return getproviderstate(provider, number); + } + if (option_debug > 3) + ast_log(LOG_DEBUG, "No provider found, checking channel drivers for %s - %s\n", tech, number); + + chan_tech = ast_get_channel_tech(tech); + if (!chan_tech) + return AST_DEVICE_INVALID; + + if (!chan_tech->devicestate) /* Does the channel driver support device state notification? */ + return ast_parse_device_state(device); /* No, try the generic function */ + else { + res = chan_tech->devicestate(number); /* Ask the channel driver for device state */ + if (res == AST_DEVICE_UNKNOWN) { + res = ast_parse_device_state(device); + /* at this point we know the device exists, but the channel driver + could not give us a state; if there is no channel state available, + it must be 'not in use' + */ + if (res == AST_DEVICE_UNKNOWN) + res = AST_DEVICE_NOT_INUSE; + return res; + } else + return res; + } +} + +/*! \brief Add device state provider */ +int ast_devstate_prov_add(const char *label, ast_devstate_prov_cb_type callback) +{ + struct devstate_prov *devprov; + + if (!callback || !(devprov = ast_calloc(1, sizeof(*devprov)))) + return -1; + + devprov->callback = callback; + ast_copy_string(devprov->label, label, sizeof(devprov->label)); + + AST_LIST_LOCK(&devstate_provs); + AST_LIST_INSERT_HEAD(&devstate_provs, devprov, list); + AST_LIST_UNLOCK(&devstate_provs); + + return 0; +} + +/*! \brief Remove device state provider */ +void ast_devstate_prov_del(const char *label) +{ + struct devstate_prov *devcb; + + AST_LIST_LOCK(&devstate_provs); + AST_LIST_TRAVERSE_SAFE_BEGIN(&devstate_provs, devcb, list) { + if (!strcasecmp(devcb->label, label)) { + AST_LIST_REMOVE_CURRENT(&devstate_provs, list); + free(devcb); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&devstate_provs); +} + +/*! \brief Get provider device state */ +static int getproviderstate(const char *provider, const char *address) +{ + struct devstate_prov *devprov; + int res = AST_DEVICE_INVALID; + + + AST_LIST_LOCK(&devstate_provs); + AST_LIST_TRAVERSE_SAFE_BEGIN(&devstate_provs, devprov, list) { + if(option_debug > 4) + ast_log(LOG_DEBUG, "Checking provider %s with %s\n", devprov->label, provider); + + if (!strcasecmp(devprov->label, provider)) { + res = devprov->callback(address); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&devstate_provs); + return res; +} + +/*! \brief Add device state watcher */ +int ast_devstate_add(ast_devstate_cb_type callback, void *data) +{ + struct devstate_cb *devcb; + + if (!callback || !(devcb = ast_calloc(1, sizeof(*devcb)))) + return -1; + + devcb->data = data; + devcb->callback = callback; + + AST_LIST_LOCK(&devstate_cbs); + AST_LIST_INSERT_HEAD(&devstate_cbs, devcb, list); + AST_LIST_UNLOCK(&devstate_cbs); + + return 0; +} + +/*! \brief Remove device state watcher */ +void ast_devstate_del(ast_devstate_cb_type callback, void *data) +{ + struct devstate_cb *devcb; + + AST_LIST_LOCK(&devstate_cbs); + AST_LIST_TRAVERSE_SAFE_BEGIN(&devstate_cbs, devcb, list) { + if ((devcb->callback == callback) && (devcb->data == data)) { + AST_LIST_REMOVE_CURRENT(&devstate_cbs, list); + free(devcb); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&devstate_cbs); +} + +/*! \brief Notify callback watchers of change, and notify PBX core for hint updates + Normally executed within a separate thread +*/ +static void do_state_change(const char *device) +{ + int state; + struct devstate_cb *devcb; + + state = ast_device_state(device); + if (option_debug > 2) + ast_log(LOG_DEBUG, "Changing state for %s - state %d (%s)\n", device, state, devstate2str(state)); + + AST_LIST_LOCK(&devstate_cbs); + AST_LIST_TRAVERSE(&devstate_cbs, devcb, list) + devcb->callback(device, state, devcb->data); + AST_LIST_UNLOCK(&devstate_cbs); + + ast_hint_state_changed(device); +} + +static int __ast_device_state_changed_literal(char *buf) +{ + char *device, *tmp; + struct state_change *change; + + if (option_debug > 2) + ast_log(LOG_DEBUG, "Notification of state change to be queued on device/channel %s\n", buf); + + device = buf; + if ((tmp = strrchr(device, '-'))) + *tmp = '\0'; + + + + if (change_thread == AST_PTHREADT_NULL || !(change = ast_calloc(1, sizeof(*change) + strlen(device)))) { + /* we could not allocate a change struct, or */ + /* there is no background thread, so process the change now */ + do_state_change(device); + } else { + /* queue the change */ + strcpy(change->device, device); + AST_LIST_LOCK(&state_changes); + AST_LIST_INSERT_TAIL(&state_changes, change, list); + if (AST_LIST_FIRST(&state_changes) == change) + /* the list was empty, signal the thread */ + ast_cond_signal(&change_pending); + AST_LIST_UNLOCK(&state_changes); + } + + return 1; +} + +int ast_device_state_changed_literal(const char *dev) +{ + char *buf; + buf = ast_strdupa(dev); + return __ast_device_state_changed_literal(buf); +} + +/*! \brief Accept change notification, add it to change queue */ +int ast_device_state_changed(const char *fmt, ...) +{ + char buf[AST_MAX_EXTENSION]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + return __ast_device_state_changed_literal(buf); +} + +/*! \brief Go through the dev state change queue and update changes in the dev state thread */ +static void *do_devstate_changes(void *data) +{ + struct state_change *cur; + + AST_LIST_LOCK(&state_changes); + for(;;) { + /* the list lock will _always_ be held at this point in the loop */ + cur = AST_LIST_REMOVE_HEAD(&state_changes, list); + if (cur) { + /* we got an entry, so unlock the list while we process it */ + AST_LIST_UNLOCK(&state_changes); + do_state_change(cur->device); + free(cur); + AST_LIST_LOCK(&state_changes); + } else { + /* there was no entry, so atomically unlock the list and wait for + the condition to be signalled (returns with the lock held) */ + ast_cond_wait(&change_pending, &state_changes.lock); + } + } + + return NULL; +} + +/*! \brief Initialize the device state engine in separate thread */ +int ast_device_state_engine_init(void) +{ + ast_cond_init(&change_pending, NULL); + if (ast_pthread_create(&change_thread, NULL, do_devstate_changes, NULL) < 0) { + ast_log(LOG_ERROR, "Unable to start device state change thread.\n"); + return -1; + } + + return 0; +} diff --git a/main/dlfcn.c b/main/dlfcn.c new file mode 100644 index 000000000..cc6fe40f9 --- /dev/null +++ b/main/dlfcn.c @@ -0,0 +1,1314 @@ +/* +Copyright (c) 2002 Jorge Acereda & + Peter O'Gorman + +Portions may be copyright others, see the AUTHORS file included with this +distribution. + +Maintained by Peter O'Gorman + +Bug Reports and other queries should go to + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* Just playing to see if it would compile with the freebsd headers, it does, + * but because of the different values for RTLD_LOCAL etc, it would break binary + * compat... oh well + */ +#ifndef __BSD_VISIBLE +#define __BSD_VISIBLE 1 +#endif + +#include "asterisk/dlfcn-compat.h" + +#ifndef dl_restrict +#define dl_restrict __restrict +#endif +/* This is not available on 10.1 */ +#ifndef LC_LOAD_WEAK_DYLIB +#define LC_LOAD_WEAK_DYLIB (0x18 | LC_REQ_DYLD) +#endif + +/* With this stuff here, this thing may actually compile/run on 10.0 systems + * Not that I have a 10.0 system to test it on anylonger + */ +#ifndef LC_REQ_DYLD +#define LC_REQ_DYLD 0x80000000 +#endif +#ifndef NSADDIMAGE_OPTION_RETURN_ONLY_IF_LOADED +#define NSADDIMAGE_OPTION_RETURN_ONLY_IF_LOADED 0x4 +#endif +#ifndef NSADDIMAGE_OPTION_RETURN_ON_ERROR +#define NSADDIMAGE_OPTION_RETURN_ON_ERROR 0x1 +#endif +#ifndef NSLOOKUPSYMBOLINIMAGE_OPTION_BIND +#define NSLOOKUPSYMBOLINIMAGE_OPTION_BIND 0x0 +#endif +#ifndef NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR +#define NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR 0x4 +#endif +/* These symbols will be looked for in dyld */ +static const struct mach_header *(*dyld_NSAddImage) (const char *, unsigned long) = 0; +static int (*dyld_NSIsSymbolNameDefinedInImage) (const struct mach_header *, const char *) = 0; +static NSSymbol(*dyld_NSLookupSymbolInImage) + (const struct mach_header *, const char *, unsigned long) = 0; + +/* Define this to make dlcompat reuse data block. This way in theory we save + * a little bit of overhead. However we then couldn't correctly catch excess + * calls to dlclose(). Hence we don't use this feature + */ +#undef REUSE_STATUS + +/* Size of the internal error message buffer (used by dlerror()) */ +#define ERR_STR_LEN 251 + +/* Maximum number of search paths supported by getSearchPath */ +#define MAX_SEARCH_PATHS 32 + + +#define MAGIC_DYLIB_OFI ((NSObjectFileImage) 'DYOF') +#define MAGIC_DYLIB_MOD ((NSModule) 'DYMO') + +/* internal flags */ +#define DL_IN_LIST 0x01 + +/* our mutex */ +static pthread_mutex_t dlcompat_mutex; +/* Our thread specific storage + */ +static pthread_key_t dlerror_key; + +struct dlthread +{ + int lockcnt; + unsigned char errset; + char errstr[ERR_STR_LEN]; +}; + +/* This is our central data structure. Whenever a module is loaded via + * dlopen(), we create such a struct. + */ +struct dlstatus +{ + struct dlstatus *next; /* pointer to next element in the linked list */ + NSModule module; + const struct mach_header *lib; + int refs; /* reference count */ + int mode; /* mode in which this module was loaded */ + dev_t device; + ino_t inode; + int flags; /* Any internal flags we may need */ +}; + +/* Head node of the dlstatus list */ +static struct dlstatus mainStatus = { 0, MAGIC_DYLIB_MOD, NULL, -1, RTLD_GLOBAL, 0, 0, 0 }; +static struct dlstatus *stqueue = &mainStatus; + + +/* Storage for the last error message (used by dlerror()) */ +/* static char err_str[ERR_STR_LEN]; */ +/* static int err_filled = 0; */ + +/* Prototypes to internal functions */ +static void debug(const char *fmt, ...); +static void error(const char *str, ...); +static const char *safegetenv(const char *s); +static const char *searchList(void); +static const char *getSearchPath(int i); +static const char *getFullPath(int i, const char *file); +static const struct stat *findFile(const char *file, const char **fullPath); +static int isValidStatus(struct dlstatus *status); +static inline int isFlagSet(int mode, int flag); +static struct dlstatus *lookupStatus(const struct stat *sbuf); +static void insertStatus(struct dlstatus *dls, const struct stat *sbuf); +static int promoteLocalToGlobal(struct dlstatus *dls); +static void *reference(struct dlstatus *dls, int mode); +static void *dlsymIntern(struct dlstatus *dls, const char *symbol, int canSetError); +static struct dlstatus *allocStatus(void); +static struct dlstatus *loadModule(const char *path, const struct stat *sbuf, int mode); +static NSSymbol *search_linked_libs(const struct mach_header *mh, const char *symbol); +static const char *get_lib_name(const struct mach_header *mh); +static const struct mach_header *get_mach_header_from_NSModule(NSModule * mod); +static void dlcompat_init_func(void); +static inline void dolock(void); +static inline void dounlock(void); +static void dlerrorfree(void *data); +static void resetdlerror(void); +static const struct mach_header *my_find_image(const char *name); +static const struct mach_header *image_for_address(const void *address); +static void dlcompat_cleanup(void); +static inline const char *dyld_error_str(void); + +#if FINK_BUILD +/* Two Global Functions */ +void *dlsym_prepend_underscore(void *handle, const char *symbol); +void *dlsym_auto_underscore(void *handle, const char *symbol); + +/* And their _intern counterparts */ +static void *dlsym_prepend_underscore_intern(void *handle, const char *symbol); +static void *dlsym_auto_underscore_intern(void *handle, const char *symbol); +#endif + +/* Functions */ + +static void debug(const char *fmt, ...) +{ +#if DEBUG > 1 + va_list arg; + va_start(arg, fmt); + fprintf(stderr, "DLDEBUG: "); + vfprintf(stderr, fmt, arg); + fprintf(stderr, "\n"); + fflush(stderr); + va_end(arg); +#endif +} + +static void error(const char *str, ...) +{ + va_list arg; + struct dlthread *tss; + char * err_str; + va_start(arg, str); + tss = pthread_getspecific(dlerror_key); + err_str = tss->errstr; + strncpy(err_str, "dlcompat: ", ERR_STR_LEN); + vsnprintf(err_str + 10, ERR_STR_LEN - 10, str, arg); + va_end(arg); + debug("ERROR: %s\n", err_str); + tss->errset = 1; +} + +static void warning(const char *str) +{ +#if DEBUG > 0 + fprintf(stderr, "WARNING: dlcompat: %s\n", str); +#endif +} + +static const char *safegetenv(const char *s) +{ + const char *ss = getenv(s); + return ss ? ss : ""; +} + +/* because this is only used for debugging and error reporting functions, we + * don't really care about how elegant it is... it could use the load + * commands to find the install name of the library, but... + */ +static const char *get_lib_name(const struct mach_header *mh) +{ + unsigned long count = _dyld_image_count(); + unsigned long i; + const char *val = NULL; + if (mh) + { + for (i = 0; i < count; i++) + { + if (mh == _dyld_get_image_header(i)) + { + val = _dyld_get_image_name(i); + break; + } + } + } + return val; +} + +/* Returns the mach_header for the module bu going through all the loaded images + * and finding the one with the same name as the module. There really ought to be + * an api for doing this, would be faster, but there isn't one right now + */ +static const struct mach_header *get_mach_header_from_NSModule(NSModule * mod) +{ + const char *mod_name = NSNameOfModule(mod); + struct mach_header *mh = NULL; + unsigned long count = _dyld_image_count(); + unsigned long i; + debug("Module name: %s", mod_name); + for (i = 0; i < count; i++) + { + if (!strcmp(mod_name, _dyld_get_image_name(i))) + { + mh = _dyld_get_image_header(i); + break; + } + } + return mh; +} + + +/* Compute and return a list of all directories that we should search when + * trying to locate a module. We first look at the values of LD_LIBRARY_PATH + * and DYLD_LIBRARY_PATH, and then finally fall back to looking into + * /usr/lib and /lib. Since both of the environments variables can contain a + * list of colon separated paths, we simply concat them and the two other paths + * into one big string, which we then can easily parse. + * Splitting this string into the actual path list is done by getSearchPath() + */ +static const char *searchList() +{ + size_t buf_size; + static char *buf=NULL; + const char *ldlp = safegetenv("LD_LIBRARY_PATH"); + const char *dyldlp = safegetenv("DYLD_LIBRARY_PATH"); + const char *stdpath = getenv("DYLD_FALLBACK_LIBRARY_PATH"); + if (!stdpath) + stdpath = "/usr/local/lib:/lib:/usr/lib"; + if (!buf) + { + buf_size = strlen(ldlp) + strlen(dyldlp) + strlen(stdpath) + 4; + buf = malloc(buf_size); + snprintf(buf, buf_size, "%s%s%s%s%s%c", dyldlp, (dyldlp[0] ? ":" : ""), ldlp, (ldlp[0] ? ":" : ""), + stdpath, '\0'); + } + return buf; +} + +/* Returns the ith search path from the list as computed by searchList() */ +static const char *getSearchPath(int i) +{ + static const char *list = 0; + static char **path = (char **)0; + static int end = 0; + static int numsize = MAX_SEARCH_PATHS; + static char **tmp; + /* So we can call free() in the "destructor" we use i=-1 to return the alloc'd array */ + if (i == -1) + { + return (const char*)path; + } + if (!path) + { + path = (char **)calloc(MAX_SEARCH_PATHS, sizeof(char **)); + } + if (!list && !end) + list = searchList(); + if (i >= (numsize)) + { + debug("Increasing size for long PATH"); + tmp = (char **)calloc((MAX_SEARCH_PATHS + numsize), sizeof(char **)); + if (tmp) + { + memcpy(tmp, path, sizeof(char **) * numsize); + free(path); + path = tmp; + numsize += MAX_SEARCH_PATHS; + } + else + { + return 0; + } + } + + while (!path[i] && !end) + { + path[i] = strsep((char **)&list, ":"); + + if (path[i][0] == 0) + path[i] = 0; + end = (list == 0); + } + return path[i]; +} + +static const char *getFullPath(int i, const char *file) +{ + static char buf[PATH_MAX]; + const char *path = getSearchPath(i); + if (path) + { + snprintf(buf, PATH_MAX, "%s/%s", path, file); + } + return path ? buf : 0; +} + +/* Given a file name, try to determine the full path for that file. Starts + * its search in the current directory, and then tries all paths in the + * search list in the order they are specified there. + */ +static const struct stat *findFile(const char *file, const char **fullPath) +{ + int i = 0; + static struct stat sbuf; + char *fileName; + debug("finding file %s", file); + *fullPath = file; + if (0 == stat(file, &sbuf)) + return &sbuf; + if (strchr(file, '/')) + return 0; /* If the path had a / we don't look in env var places */ + fileName = NULL; + if (!fileName) + fileName = (char *)file; + while ((*fullPath = getFullPath(i++, fileName))) + { + if (0 == stat(*fullPath, &sbuf)) + return &sbuf; + } + ; + return 0; +} + +/* Determine whether a given dlstatus is valid or not */ +static int isValidStatus(struct dlstatus *status) +{ + /* Walk the list to verify status is contained in it */ + struct dlstatus *dls = stqueue; + while (dls && status != dls) + dls = dls->next; + if (dls == 0) + error("invalid handle"); + else if ((dls->module == 0) || (dls->refs == 0)) + error("handle to closed library"); + else + return TRUE; + return FALSE; +} + +static inline int isFlagSet(int mode, int flag) +{ + return (mode & flag) == flag; +} + +static struct dlstatus *lookupStatus(const struct stat *sbuf) +{ + struct dlstatus *dls = stqueue; + debug("looking for status"); + while (dls && ( /* isFlagSet(dls->mode, RTLD_UNSHARED) */ 0 + || sbuf->st_dev != dls->device || sbuf->st_ino != dls->inode)) + dls = dls->next; + return dls; +} + +static void insertStatus(struct dlstatus *dls, const struct stat *sbuf) +{ + debug("inserting status"); + dls->inode = sbuf->st_ino; + dls->device = sbuf->st_dev; + dls->refs = 0; + dls->mode = 0; + if ((dls->flags & DL_IN_LIST) == 0) + { + dls->next = stqueue; + stqueue = dls; + dls->flags |= DL_IN_LIST; + } +} + +static struct dlstatus *allocStatus() +{ + struct dlstatus *dls; +#ifdef REUSE_STATUS + dls = stqueue; + while (dls && dls->module) + dls = dls->next; + if (!dls) +#endif + dls = malloc(sizeof(*dls)); + dls->flags = 0; + return dls; +} + +static int promoteLocalToGlobal(struct dlstatus *dls) +{ + static int (*p) (NSModule module) = 0; + debug("promoting"); + if (!p) + _dyld_func_lookup("__dyld_NSMakePrivateModulePublic", (unsigned long *)&p); + return (dls->module == MAGIC_DYLIB_MOD) || (p && p(dls->module)); +} + +static void *reference(struct dlstatus *dls, int mode) +{ + if (dls) + { + if (dls->module == MAGIC_DYLIB_MOD && !isFlagSet(mode, RTLD_GLOBAL)) + { + warning("trying to open a .dylib with RTLD_LOCAL"); + error("unable to open a .dylib with RTLD_LOCAL"); + return NULL; + } + if (isFlagSet(mode, RTLD_GLOBAL) && + !isFlagSet(dls->mode, RTLD_GLOBAL) && !promoteLocalToGlobal(dls)) + { + error("unable to promote local module to global"); + return NULL; + } + dls->mode |= mode; + dls->refs++; + } + else + debug("reference called with NULL argument"); + + return dls; +} + +static const struct mach_header *my_find_image(const char *name) +{ + const struct mach_header *mh = 0; + const char *id = NULL; + int i = _dyld_image_count(); + int j; + mh = (struct mach_header *) + dyld_NSAddImage(name, NSADDIMAGE_OPTION_RETURN_ONLY_IF_LOADED | + NSADDIMAGE_OPTION_RETURN_ON_ERROR); + if (!mh) + { + for (j = 0; j < i; j++) + { + id = _dyld_get_image_name(j); + if (!strcmp(id, name)) + { + mh = _dyld_get_image_header(j); + break; + } + } + } + return mh; +} + +/* + * dyld adds libraries by first adding the directly dependant libraries in link order, and + * then adding the dependencies for those libraries, so we should do the same... but we don't + * bother adding the extra dependencies, if the symbols are neither in the loaded image nor + * any of it's direct dependencies, then it probably isn't there. + */ +NSSymbol *search_linked_libs(const struct mach_header * mh, const char *symbol) +{ + int n; + struct load_command *lc = 0; + struct mach_header *wh; + NSSymbol *nssym = 0; + if (dyld_NSAddImage && dyld_NSIsSymbolNameDefinedInImage && dyld_NSLookupSymbolInImage) + { + lc = (struct load_command *)((char *)mh + sizeof(struct mach_header)); + for (n = 0; n < mh->ncmds; n++, lc = (struct load_command *)((char *)lc + lc->cmdsize)) + { + if ((LC_LOAD_DYLIB == lc->cmd) || (LC_LOAD_WEAK_DYLIB == lc->cmd)) + { + if ((wh = (struct mach_header *) + my_find_image((char *)(((struct dylib_command *)lc)->dylib.name.offset + + (char *)lc)))) + { + if (dyld_NSIsSymbolNameDefinedInImage(wh, symbol)) + { + nssym = dyld_NSLookupSymbolInImage(wh, + symbol, + NSLOOKUPSYMBOLINIMAGE_OPTION_BIND | + NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR); + break; + } + } + } + } + if ((!nssym) && NSIsSymbolNameDefined(symbol)) + { + /* I've never seen this debug message...*/ + debug("Symbol \"%s\" is defined but was not found", symbol); + } + } + return nssym; +} + +/* Up to the caller to free() returned string */ +static inline const char *dyld_error_str() +{ + NSLinkEditErrors dylder; + int dylderno; + const char *dylderrstr; + const char *dyldfile; + const char* retStr = NULL; + NSLinkEditError(&dylder, &dylderno, &dyldfile, &dylderrstr); + if (dylderrstr && strlen(dylderrstr)) + { + retStr = malloc(strlen(dylderrstr) +1); + strcpy((char*)retStr,dylderrstr); + } + return retStr; +} + +static void *dlsymIntern(struct dlstatus *dls, const char *symbol, int canSetError) +{ + NSSymbol *nssym = 0; + void *caller = __builtin_return_address(1); /* Be *very* careful about inlining */ + const struct mach_header *caller_mh = 0; + const char* savedErrorStr = NULL; + resetdlerror(); +#ifndef RTLD_SELF +#define RTLD_SELF ((void *) -3) +#endif + if (NULL == dls) + dls = RTLD_SELF; + if ((RTLD_NEXT == dls) || (RTLD_SELF == dls)) + { + if (dyld_NSIsSymbolNameDefinedInImage && dyld_NSLookupSymbolInImage) + { + caller_mh = image_for_address(caller); + if (RTLD_SELF == dls) + { + /* FIXME: We should be using the NSModule api, if SELF is an MH_BUNDLE + * But it appears to work anyway, and looking at the code in dyld_libfuncs.c + * this is acceptable. + */ + if (dyld_NSIsSymbolNameDefinedInImage(caller_mh, symbol)) + { + nssym = dyld_NSLookupSymbolInImage(caller_mh, + symbol, + NSLOOKUPSYMBOLINIMAGE_OPTION_BIND | + NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR); + } + } + if (!nssym) + { + if (RTLD_SELF == dls) + savedErrorStr = dyld_error_str(); + nssym = search_linked_libs(caller_mh, symbol); + } + } + else + { + if (canSetError) + error("RTLD_SELF and RTLD_NEXT are not supported"); + return NULL; + } + } + if (!nssym) + { + + if (RTLD_DEFAULT == dls) + { + dls = &mainStatus; + } + if (!isValidStatus(dls)) + return NULL; + + if (dls->module != MAGIC_DYLIB_MOD) + { + nssym = NSLookupSymbolInModule(dls->module, symbol); + if (!nssym && NSIsSymbolNameDefined(symbol)) + { + debug("Searching dependencies"); + savedErrorStr = dyld_error_str(); + nssym = search_linked_libs(get_mach_header_from_NSModule(dls->module), symbol); + } + } + else if (dls->lib && dyld_NSIsSymbolNameDefinedInImage && dyld_NSLookupSymbolInImage) + { + if (dyld_NSIsSymbolNameDefinedInImage(dls->lib, symbol)) + { + nssym = dyld_NSLookupSymbolInImage(dls->lib, + symbol, + NSLOOKUPSYMBOLINIMAGE_OPTION_BIND | + NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR); + } + else if (NSIsSymbolNameDefined(symbol)) + { + debug("Searching dependencies"); + savedErrorStr = dyld_error_str(); + nssym = search_linked_libs(dls->lib, symbol); + } + } + else if (dls->module == MAGIC_DYLIB_MOD) + { + /* Global context, use NSLookupAndBindSymbol */ + if (NSIsSymbolNameDefined(symbol)) + { + /* There doesn't seem to be a return on error option for this call??? + this is potentially broken, if binding fails, it will improperly + exit the application. */ + nssym = NSLookupAndBindSymbol(symbol); + } + else + { + if (savedErrorStr) + free((char*)savedErrorStr); + savedErrorStr = malloc(256); + snprintf((char*)savedErrorStr, 256, "Symbol \"%s\" not in global context",symbol); + } + } + } + /* Error reporting */ + if (!nssym) + { + if (!savedErrorStr || !strlen(savedErrorStr)) + { + if (savedErrorStr) + free((char*)savedErrorStr); + savedErrorStr = malloc(256); + snprintf((char*)savedErrorStr, 256,"Symbol \"%s\" not found",symbol); + } + if (canSetError) + { + error(savedErrorStr); + } + else + { + debug(savedErrorStr); + } + if (savedErrorStr) + free((char*)savedErrorStr); + return NULL; + } + return NSAddressOfSymbol(nssym); +} + +static struct dlstatus *loadModule(const char *path, const struct stat *sbuf, int mode) +{ + NSObjectFileImage ofi = 0; + NSObjectFileImageReturnCode ofirc; + struct dlstatus *dls; + NSLinkEditErrors ler; + int lerno; + const char *errstr; + const char *file; + void (*init) (void); + ofirc = NSCreateObjectFileImageFromFile(path, &ofi); + switch (ofirc) + { + case NSObjectFileImageSuccess: + break; + case NSObjectFileImageInappropriateFile: + if (dyld_NSAddImage && dyld_NSIsSymbolNameDefinedInImage && dyld_NSLookupSymbolInImage) + { + if (!isFlagSet(mode, RTLD_GLOBAL)) + { + warning("trying to open a .dylib with RTLD_LOCAL"); + error("unable to open this file with RTLD_LOCAL"); + return NULL; + } + } + else + { + error("opening this file is unsupported on this system"); + return NULL; + } + break; + case NSObjectFileImageFailure: + error("object file setup failure"); + return NULL; + case NSObjectFileImageArch: + error("no object for this architecture"); + return NULL; + case NSObjectFileImageFormat: + error("bad object file format"); + return NULL; + case NSObjectFileImageAccess: + error("can't read object file"); + return NULL; + default: + error("unknown error from NSCreateObjectFileImageFromFile()"); + return NULL; + } + dls = lookupStatus(sbuf); + if (!dls) + { + dls = allocStatus(); + } + if (!dls) + { + error("unable to allocate memory"); + return NULL; + } + dls->lib = 0; + if (ofirc == NSObjectFileImageInappropriateFile) + { + if ((dls->lib = dyld_NSAddImage(path, NSADDIMAGE_OPTION_RETURN_ON_ERROR))) + { + debug("Dynamic lib loaded at %ld", dls->lib); + ofi = MAGIC_DYLIB_OFI; + dls->module = MAGIC_DYLIB_MOD; + ofirc = NSObjectFileImageSuccess; + /* Although it is possible with a bit of work to modify this so it works and + functions with RTLD_NOW, I don't deem it necessary at the moment */ + } + if (!(dls->module)) + { + NSLinkEditError(&ler, &lerno, &file, &errstr); + if (!errstr || (!strlen(errstr))) + error("Can't open this file type"); + else + error(errstr); + if ((dls->flags & DL_IN_LIST) == 0) + { + free(dls); + } + return NULL; + } + } + else + { + dls->module = NSLinkModule(ofi, path, + NSLINKMODULE_OPTION_RETURN_ON_ERROR | + NSLINKMODULE_OPTION_PRIVATE | + (isFlagSet(mode, RTLD_NOW) ? NSLINKMODULE_OPTION_BINDNOW : 0)); + NSDestroyObjectFileImage(ofi); + if (dls->module) + { + dls->lib = get_mach_header_from_NSModule(dls->module); + } + } + if (!dls->module) + { + NSLinkEditError(&ler, &lerno, &file, &errstr); + if ((dls->flags & DL_IN_LIST) == 0) + { + free(dls); + } + error(errstr); + return NULL; + } + + insertStatus(dls, sbuf); + dls = reference(dls, mode); + if ((init = dlsymIntern(dls, "__init", 0))) + { + debug("calling _init()"); + init(); + } + return dls; +} + +static void dlcompat_init_func(void) +{ + static int inited = 0; + if (!inited) + { + inited = 1; + _dyld_func_lookup("__dyld_NSAddImage", (unsigned long *)&dyld_NSAddImage); + _dyld_func_lookup("__dyld_NSIsSymbolNameDefinedInImage", + (unsigned long *)&dyld_NSIsSymbolNameDefinedInImage); + _dyld_func_lookup("__dyld_NSLookupSymbolInImage", (unsigned long *)&dyld_NSLookupSymbolInImage); + if (pthread_mutex_init(&dlcompat_mutex, NULL)) + exit(1); + if (pthread_key_create(&dlerror_key, &dlerrorfree)) + exit(1); + /* And be neat and tidy and clean up after ourselves */ + atexit(dlcompat_cleanup); + } +} + +#if 0 +#pragma CALL_ON_LOAD dlcompat_init_func +#endif + +static void dlcompat_cleanup(void) +{ + struct dlstatus *dls; + struct dlstatus *next; + char *data; + data = (char *)searchList(); + if ( data ) + free( data ); + data = (char *)getSearchPath(-1); + if ( data ) + free( data ); + pthread_mutex_destroy(&dlcompat_mutex); + pthread_key_delete(dlerror_key); + next = stqueue; + while (next && (next != &mainStatus)) + { + dls = next; + next = dls->next; + free(dls); + } +} + +static void resetdlerror() +{ + struct dlthread *tss; + tss = pthread_getspecific(dlerror_key); + tss->errset = 0; +} + +static void dlerrorfree(void *data) +{ + free(data); +} + +/* We kind of want a recursive lock here, but meet a little trouble + * because they are not available pre OS X 10.2, so we fake it + * using thread specific storage to keep a lock count + */ +static inline void dolock(void) +{ + int err = 0; + struct dlthread *tss; + tss = pthread_getspecific(dlerror_key); + if (!tss) + { + tss = malloc(sizeof(struct dlthread)); + tss->lockcnt = 0; + tss->errset = 0; + if (pthread_setspecific(dlerror_key, tss)) + { + fprintf(stderr,"dlcompat: pthread_setspecific failed\n"); + exit(1); + } + } + if (!tss->lockcnt) + err = pthread_mutex_lock(&dlcompat_mutex); + tss->lockcnt = tss->lockcnt +1; + if (err) + exit(err); +} + +static inline void dounlock(void) +{ + int err = 0; + struct dlthread *tss; + tss = pthread_getspecific(dlerror_key); + tss->lockcnt = tss->lockcnt -1; + if (!tss->lockcnt) + err = pthread_mutex_unlock(&dlcompat_mutex); + if (err) + exit(err); +} + +void *dlopen(const char *path, int mode) +{ + const struct stat *sbuf; + struct dlstatus *dls; + const char *fullPath; + dlcompat_init_func(); /* Just in case */ + dolock(); + resetdlerror(); + if (!path) + { + dls = &mainStatus; + goto dlopenok; + } + if (!(sbuf = findFile(path, &fullPath))) + { + error("file \"%s\" not found", path); + goto dlopenerror; + } + /* Now checks that it hasn't been closed already */ + if ((dls = lookupStatus(sbuf)) && (dls->refs > 0)) + { + /* debug("status found"); */ + dls = reference(dls, mode); + goto dlopenok; + } +#ifdef RTLD_NOLOAD + if (isFlagSet(mode, RTLD_NOLOAD)) + { + error("no existing handle and RTLD_NOLOAD specified"); + goto dlopenerror; + } +#endif + if (isFlagSet(mode, RTLD_LAZY) && isFlagSet(mode, RTLD_NOW)) + { + error("how can I load something both RTLD_LAZY and RTLD_NOW?"); + goto dlopenerror; + } + dls = loadModule(fullPath, sbuf, mode); + + dlopenok: + dounlock(); + return (void *)dls; + dlopenerror: + dounlock(); + return NULL; +} + +#if !FINK_BUILD +void *dlsym(void * dl_restrict handle, const char * dl_restrict symbol) +{ + int sym_len = strlen(symbol); + void *value = NULL; + char *malloc_sym = NULL; + dolock(); + malloc_sym = malloc(sym_len + 2); + if (malloc_sym) + { + sprintf(malloc_sym, "_%s", symbol); + value = dlsymIntern(handle, malloc_sym, 1); + free(malloc_sym); + } + else + { + error("Unable to allocate memory"); + goto dlsymerror; + } + dounlock(); + return value; + dlsymerror: + dounlock(); + return NULL; +} +#endif + +#if FINK_BUILD + +void *dlsym_prepend_underscore(void *handle, const char *symbol) +{ + void *answer; + dolock(); + answer = dlsym_prepend_underscore_intern(handle, symbol); + dounlock(); + return answer; +} + +static void *dlsym_prepend_underscore_intern(void *handle, const char *symbol) +{ +/* + * A quick and easy way for porting packages which call dlsym(handle,"sym") + * If the porter adds -Ddlsym=dlsym_prepend_underscore to the CFLAGS then + * this function will be called, and will add the required underscore. + * + * Note that I haven't figured out yet which should be "standard", prepend + * the underscore always, or not at all. These global functions need to go away + * for opendarwin. + */ + int sym_len = strlen(symbol); + void *value = NULL; + char *malloc_sym = NULL; + malloc_sym = malloc(sym_len + 2); + if (malloc_sym) + { + sprintf(malloc_sym, "_%s", symbol); + value = dlsymIntern(handle, malloc_sym, 1); + free(malloc_sym); + } + else + { + error("Unable to allocate memory"); + } + return value; +} + +void *dlsym_auto_underscore(void *handle, const char *symbol) +{ + void *answer; + dolock(); + answer = dlsym_auto_underscore_intern(handle, symbol); + dounlock(); + return answer; + +} +static void *dlsym_auto_underscore_intern(void *handle, const char *symbol) +{ + struct dlstatus *dls = handle; + void *addr = 0; + addr = dlsymIntern(dls, symbol, 0); + if (!addr) + addr = dlsym_prepend_underscore_intern(handle, symbol); + return addr; +} + + +void *dlsym(void * dl_restrict handle, const char * dl_restrict symbol) +{ + struct dlstatus *dls = handle; + void *addr = 0; + dolock(); + addr = dlsymIntern(dls, symbol, 1); + dounlock(); + return addr; +} +#endif + +int dlclose(void *handle) +{ + struct dlstatus *dls = handle; + dolock(); + resetdlerror(); + if (!isValidStatus(dls)) + { + goto dlcloseerror; + } + if (dls->module == MAGIC_DYLIB_MOD) + { + const char *name; + if (!dls->lib) + { + name = "global context"; + } + else + { + name = get_lib_name(dls->lib); + } + warning("trying to close a .dylib!"); + error("Not closing \"%s\" - dynamic libraries cannot be closed", name); + goto dlcloseerror; + } + if (!dls->module) + { + error("module already closed"); + goto dlcloseerror; + } + + if (dls->refs == 1) + { + unsigned long options = 0; + void (*fini) (void); + if ((fini = dlsymIntern(dls, "__fini", 0))) + { + debug("calling _fini()"); + fini(); + } +#ifdef __ppc__ + options |= NSUNLINKMODULE_OPTION_RESET_LAZY_REFERENCES; +#endif +#if 1 +/* Currently, if a module contains c++ static destructors and it is unloaded, we + * get a segfault in atexit(), due to compiler and dynamic loader differences of + * opinion, this works around that. + * I really need a way to figure out from code if this is still necessary. + */ + if ((const struct section *)NULL != + getsectbynamefromheader(get_mach_header_from_NSModule(dls->module), + "__DATA", "__mod_term_func")) + { + options |= NSUNLINKMODULE_OPTION_KEEP_MEMORY_MAPPED; + } +#endif +#ifdef RTLD_NODELETE + if (isFlagSet(dls->mode, RTLD_NODELETE)) + options |= NSUNLINKMODULE_OPTION_KEEP_MEMORY_MAPPED; +#endif + if (!NSUnLinkModule(dls->module, options)) + { + error("unable to unlink module"); + goto dlcloseerror; + } + dls->refs--; + dls->module = 0; + /* Note: the dlstatus struct dls is neither removed from the list + * nor is the memory it occupies freed. This shouldn't pose a + * problem in mostly all cases, though. + */ + } + dounlock(); + return 0; + dlcloseerror: + dounlock(); + return 1; +} + +const char *dlerror(void) +{ + struct dlthread *tss; + char * err_str; + tss = pthread_getspecific(dlerror_key); + err_str = tss->errstr; + tss = pthread_getspecific(dlerror_key); + if (tss->errset == 0) + return 0; + tss->errset = 0; + return (err_str ); +} + +/* Given an address, return the mach_header for the image containing it + * or zero if the given address is not contained in any loaded images. + */ +const struct mach_header *image_for_address(const void *address) +{ + unsigned long i; + unsigned long j; + unsigned long count = _dyld_image_count(); + struct mach_header *mh = 0; + struct load_command *lc = 0; + unsigned long addr = NULL; + for (i = 0; i < count; i++) + { + addr = (unsigned long)address - _dyld_get_image_vmaddr_slide(i); + mh = _dyld_get_image_header(i); + if (mh) + { + lc = (struct load_command *)((char *)mh + sizeof(struct mach_header)); + for (j = 0; j < mh->ncmds; j++, lc = (struct load_command *)((char *)lc + lc->cmdsize)) + { + if (LC_SEGMENT == lc->cmd && + addr >= ((struct segment_command *)lc)->vmaddr && + addr < + ((struct segment_command *)lc)->vmaddr + ((struct segment_command *)lc)->vmsize) + { + goto image_found; + } + } + } + mh = 0; + } + image_found: + return mh; +} + +int dladdr(const void * dl_restrict p, Dl_info * dl_restrict info) +{ +/* + FIXME: USe the routine image_for_address. +*/ + unsigned long i; + unsigned long j; + unsigned long count = _dyld_image_count(); + struct mach_header *mh = 0; + struct load_command *lc = 0; + unsigned long addr = NULL; + unsigned long table_off = (unsigned long)0; + int found = 0; + if (!info) + return 0; + dolock(); + resetdlerror(); + info->dli_fname = 0; + info->dli_fbase = 0; + info->dli_sname = 0; + info->dli_saddr = 0; +/* Some of this was swiped from code posted by Douglas Davidson + * to darwin-development AT lists DOT apple DOT com and slightly modified + */ + for (i = 0; i < count; i++) + { + addr = (unsigned long)p - _dyld_get_image_vmaddr_slide(i); + mh = _dyld_get_image_header(i); + if (mh) + { + lc = (struct load_command *)((char *)mh + sizeof(struct mach_header)); + for (j = 0; j < mh->ncmds; j++, lc = (struct load_command *)((char *)lc + lc->cmdsize)) + { + if (LC_SEGMENT == lc->cmd && + addr >= ((struct segment_command *)lc)->vmaddr && + addr < + ((struct segment_command *)lc)->vmaddr + ((struct segment_command *)lc)->vmsize) + { + info->dli_fname = _dyld_get_image_name(i); + info->dli_fbase = (void *)mh; + found = 1; + break; + } + } + if (found) + break; + } + } + if (!found) + { + dounlock(); + return 0; + } + lc = (struct load_command *)((char *)mh + sizeof(struct mach_header)); + for (j = 0; j < mh->ncmds; j++, lc = (struct load_command *)((char *)lc + lc->cmdsize)) + { + if (LC_SEGMENT == lc->cmd) + { + if (!strcmp(((struct segment_command *)lc)->segname, "__LINKEDIT")) + break; + } + } + table_off = + ((unsigned long)((struct segment_command *)lc)->vmaddr) - + ((unsigned long)((struct segment_command *)lc)->fileoff) + _dyld_get_image_vmaddr_slide(i); + debug("table off %x", table_off); + + lc = (struct load_command *)((char *)mh + sizeof(struct mach_header)); + for (j = 0; j < mh->ncmds; j++, lc = (struct load_command *)((char *)lc + lc->cmdsize)) + { + if (LC_SYMTAB == lc->cmd) + { + + struct nlist *symtable = (struct nlist *)(((struct symtab_command *)lc)->symoff + table_off); + unsigned long numsyms = ((struct symtab_command *)lc)->nsyms; + struct nlist *nearest = NULL; + unsigned long diff = 0xffffffff; + unsigned long strtable = (unsigned long)(((struct symtab_command *)lc)->stroff + table_off); + debug("symtable %x", symtable); + for (i = 0; i < numsyms; i++) + { + /* Ignore the following kinds of Symbols */ + if ((!symtable->n_value) /* Undefined */ + || (symtable->n_type >= N_PEXT) /* Debug symbol */ + || (!(symtable->n_type & N_EXT)) /* Local Symbol */ + ) + { + symtable++; + continue; + } + if ((addr >= symtable->n_value) && (diff >= (symtable->n_value - addr))) + { + diff = (unsigned long)symtable->n_value - addr; + nearest = symtable; + } + symtable++; + } + if (nearest) + { + info->dli_saddr = nearest->n_value + ((void *)p - addr); + info->dli_sname = (char *)(strtable + nearest->n_un.n_strx); + } + } + } + dounlock(); + return 1; +} + + +/* + * Implement the dlfunc() interface, which behaves exactly the same as + * dlsym() except that it returns a function pointer instead of a data + * pointer. This can be used by applications to avoid compiler warnings + * about undefined behavior, and is intended as prior art for future + * POSIX standardization. This function requires that all pointer types + * have the same representation, which is true on all platforms FreeBSD + * runs on, but is not guaranteed by the C standard. + */ +#if 0 +dlfunc_t dlfunc(void * dl_restrict handle, const char * dl_restrict symbol) +{ + union + { + void *d; + dlfunc_t f; + } rv; + int sym_len = strlen(symbol); + char *malloc_sym = NULL; + dolock(); + malloc_sym = malloc(sym_len + 2); + if (malloc_sym) + { + sprintf(malloc_sym, "_%s", symbol); + rv.d = dlsymIntern(handle, malloc_sym, 1); + free(malloc_sym); + } + else + { + error("Unable to allocate memory"); + goto dlfuncerror; + } + dounlock(); + return rv.f; + dlfuncerror: + dounlock(); + return NULL; +} +#endif diff --git a/main/dns.c b/main/dns.c new file mode 100644 index 000000000..3599de2c5 --- /dev/null +++ b/main/dns.c @@ -0,0 +1,228 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006 Thorsten Lockert + * + * Written by Thorsten Lockert + * + * Funding provided by Troll Phone Networks AS + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief DNS Support for Asterisk + * + * \author Thorsten Lockert + * + * \par Reference + * - DNR SRV records http://www.ietf.org/rfc/rfc2782.txt + * + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include + +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/dns.h" +#include "asterisk/endian.h" + +#define MAX_SIZE 4096 + +typedef struct { + unsigned id:16; /*!< query identification number */ +#if __BYTE_ORDER == __BIG_ENDIAN + /* fields in third byte */ + unsigned qr:1; /*!< response flag */ + unsigned opcode:4; /*!< purpose of message */ + unsigned aa:1; /*!< authoritive answer */ + unsigned tc:1; /*!< truncated message */ + unsigned rd:1; /*!< recursion desired */ + /* fields in fourth byte */ + unsigned ra:1; /*!< recursion available */ + unsigned unused:1; /*!< unused bits (MBZ as of 4.9.3a3) */ + unsigned ad:1; /*!< authentic data from named */ + unsigned cd:1; /*!< checking disabled by resolver */ + unsigned rcode:4; /*!< response code */ +#endif +#if __BYTE_ORDER == __LITTLE_ENDIAN || __BYTE_ORDER == __PDP_ENDIAN + /* fields in third byte */ + unsigned rd:1; /*!< recursion desired */ + unsigned tc:1; /*!< truncated message */ + unsigned aa:1; /*!< authoritive answer */ + unsigned opcode:4; /*!< purpose of message */ + unsigned qr:1; /*!< response flag */ + /* fields in fourth byte */ + unsigned rcode:4; /*!< response code */ + unsigned cd:1; /*!< checking disabled by resolver */ + unsigned ad:1; /*!< authentic data from named */ + unsigned unused:1; /*!< unused bits (MBZ as of 4.9.3a3) */ + unsigned ra:1; /*!< recursion available */ +#endif + /* remaining bytes */ + unsigned qdcount:16; /*!< number of question entries */ + unsigned ancount:16; /*!< number of answer entries */ + unsigned nscount:16; /*!< number of authority entries */ + unsigned arcount:16; /*!< number of resource entries */ +} dns_HEADER; + +struct dn_answer { + unsigned short rtype; + unsigned short class; + unsigned int ttl; + unsigned short size; +} __attribute__ ((__packed__)); + +static int skip_name(unsigned char *s, int len) +{ + int x = 0; + + while (x < len) { + if (*s == '\0') { + s++; + x++; + break; + } + if ((*s & 0xc0) == 0xc0) { + s += 2; + x += 2; + break; + } + x += *s + 1; + s += *s + 1; + } + if (x >= len) + return -1; + return x; +} + +/*! \brief Parse DNS lookup result, call callback */ +static int dns_parse_answer(void *context, + int class, int type, unsigned char *answer, int len, + int (*callback)(void *context, unsigned char *answer, int len, unsigned char *fullanswer)) +{ + unsigned char *fullanswer = answer; + struct dn_answer *ans; + dns_HEADER *h; + int res; + int x; + + h = (dns_HEADER *)answer; + answer += sizeof(dns_HEADER); + len -= sizeof(dns_HEADER); + + for (x = 0; x < ntohs(h->qdcount); x++) { + if ((res = skip_name(answer, len)) < 0) { + ast_log(LOG_WARNING, "Couldn't skip over name\n"); + return -1; + } + answer += res + 4; /* Skip name and QCODE / QCLASS */ + len -= res + 4; + if (len < 0) { + ast_log(LOG_WARNING, "Strange query size\n"); + return -1; + } + } + + for (x = 0; x < ntohs(h->ancount); x++) { + if ((res = skip_name(answer, len)) < 0) { + ast_log(LOG_WARNING, "Failed skipping name\n"); + return -1; + } + answer += res; + len -= res; + ans = (struct dn_answer *)answer; + answer += sizeof(struct dn_answer); + len -= sizeof(struct dn_answer); + if (len < 0) { + ast_log(LOG_WARNING, "Strange result size\n"); + return -1; + } + if (len < 0) { + ast_log(LOG_WARNING, "Length exceeds frame\n"); + return -1; + } + + if (ntohs(ans->class) == class && ntohs(ans->rtype) == type) { + if (callback) { + if ((res = callback(context, answer, ntohs(ans->size), fullanswer)) < 0) { + ast_log(LOG_WARNING, "Failed to parse result\n"); + return -1; + } + if (res > 0) + return 1; + } + } + answer += ntohs(ans->size); + len -= ntohs(ans->size); + } + return 0; +} + +#if !HAVE_RES_NINIT +AST_MUTEX_DEFINE_STATIC(res_lock); +#endif + +/*! \brief Lookup record in DNS +\note Asterisk DNS is synchronus at this time. This means that if your DNS does +not work properly, Asterisk might not start properly or a channel may lock. +*/ +int ast_search_dns(void *context, + const char *dname, int class, int type, + int (*callback)(void *context, unsigned char *answer, int len, unsigned char *fullanswer)) +{ +#if HAVE_RES_NINIT + struct __res_state dnsstate; +#endif + unsigned char answer[MAX_SIZE]; + int res, ret = -1; + +#if HAVE_RES_NINIT + res_ninit(&dnsstate); + res = res_nsearch(&dnsstate, dname, class, type, answer, sizeof(answer)); +#else + ast_mutex_lock(&res_lock); + res_init(); + res = res_search(dname, class, type, answer, sizeof(answer)); +#endif + if (res > 0) { + if ((res = dns_parse_answer(context, class, type, answer, res, callback)) < 0) { + ast_log(LOG_WARNING, "DNS Parse error for %s\n", dname); + ret = -1; + } + else if (ret == 0) { + ast_log(LOG_DEBUG, "No matches found in DNS for %s\n", dname); + ret = 0; + } + else + ret = 1; + } +#if HAVE_RES_NINIT + res_nclose(&dnsstate); +#else +#ifndef __APPLE__ + res_close(); +#endif + ast_mutex_unlock(&res_lock); +#endif + + return ret; +} diff --git a/main/dnsmgr.c b/main/dnsmgr.c new file mode 100644 index 000000000..98b7f0905 --- /dev/null +++ b/main/dnsmgr.c @@ -0,0 +1,426 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2005-2006, Kevin P. Fleming + * + * Kevin P. Fleming + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Background DNS update manager + * + * \author Kevin P. Fleming + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/dnsmgr.h" +#include "asterisk/linkedlists.h" +#include "asterisk/utils.h" +#include "asterisk/config.h" +#include "asterisk/logger.h" +#include "asterisk/sched.h" +#include "asterisk/options.h" +#include "asterisk/cli.h" + +static struct sched_context *sched; +static int refresh_sched = -1; +static pthread_t refresh_thread = AST_PTHREADT_NULL; + +struct ast_dnsmgr_entry { + /*! where we will store the resulting address */ + struct in_addr *result; + /*! the last result, used to check if address has changed */ + struct in_addr last; + /*! Set to 1 if the entry changes */ + int changed:1; + ast_mutex_t lock; + AST_LIST_ENTRY(ast_dnsmgr_entry) list; + /*! just 1 here, but we use calloc to allocate the correct size */ + char name[1]; +}; + +static AST_LIST_HEAD_STATIC(entry_list, ast_dnsmgr_entry); + +AST_MUTEX_DEFINE_STATIC(refresh_lock); + +#define REFRESH_DEFAULT 300 + +static int enabled = 0; +static int refresh_interval; + +struct refresh_info { + struct entry_list *entries; + int verbose; + unsigned int regex_present:1; + regex_t filter; +}; + +static struct refresh_info master_refresh_info = { + .entries = &entry_list, + .verbose = 0, +}; + +struct ast_dnsmgr_entry *ast_dnsmgr_get(const char *name, struct in_addr *result) +{ + struct ast_dnsmgr_entry *entry; + + if (!result || ast_strlen_zero(name) || !(entry = ast_calloc(1, sizeof(*entry) + strlen(name)))) + return NULL; + + entry->result = result; + ast_mutex_init(&entry->lock); + strcpy(entry->name, name); + + AST_LIST_LOCK(&entry_list); + AST_LIST_INSERT_HEAD(&entry_list, entry, list); + AST_LIST_UNLOCK(&entry_list); + + return entry; +} + +void ast_dnsmgr_release(struct ast_dnsmgr_entry *entry) +{ + if (!entry) + return; + + AST_LIST_LOCK(&entry_list); + AST_LIST_REMOVE(&entry_list, entry, list); + AST_LIST_UNLOCK(&entry_list); + if (option_verbose > 3) + ast_verbose(VERBOSE_PREFIX_4 "removing dns manager for '%s'\n", entry->name); + + ast_mutex_destroy(&entry->lock); + free(entry); +} + +int ast_dnsmgr_lookup(const char *name, struct in_addr *result, struct ast_dnsmgr_entry **dnsmgr) +{ + if (ast_strlen_zero(name) || !result || !dnsmgr) + return -1; + + if (*dnsmgr && !strcasecmp((*dnsmgr)->name, name)) + return 0; + + if (option_verbose > 3) + ast_verbose(VERBOSE_PREFIX_4 "doing dnsmgr_lookup for '%s'\n", name); + + /* if it's actually an IP address and not a name, + there's no need for a managed lookup */ + if (inet_aton(name, result)) + return 0; + + /* if the manager is disabled, do a direct lookup and return the result, + otherwise register a managed lookup for the name */ + if (!enabled) { + struct ast_hostent ahp; + struct hostent *hp; + + if ((hp = ast_gethostbyname(name, &ahp))) + memcpy(result, hp->h_addr, sizeof(result)); + return 0; + } else { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_2 "adding dns manager for '%s'\n", name); + *dnsmgr = ast_dnsmgr_get(name, result); + return !*dnsmgr; + } +} + +/* + * Refresh a dnsmgr entry + */ +static int dnsmgr_refresh(struct ast_dnsmgr_entry *entry, int verbose) +{ + struct ast_hostent ahp; + struct hostent *hp; + char iabuf[INET_ADDRSTRLEN]; + char iabuf2[INET_ADDRSTRLEN]; + struct in_addr tmp; + int changed = 0; + + ast_mutex_lock(&entry->lock); + if (verbose && (option_verbose > 2)) + ast_verbose(VERBOSE_PREFIX_2 "refreshing '%s'\n", entry->name); + + if ((hp = ast_gethostbyname(entry->name, &ahp))) { + /* check to see if it has changed, do callback if requested (where de callback is defined ????) */ + memcpy(&tmp, hp->h_addr, sizeof(tmp)); + if (tmp.s_addr != entry->last.s_addr) { + ast_copy_string(iabuf, ast_inet_ntoa(entry->last), sizeof(iabuf)); + ast_copy_string(iabuf2, ast_inet_ntoa(tmp), sizeof(iabuf2)); + ast_log(LOG_NOTICE, "host '%s' changed from %s to %s\n", + entry->name, iabuf, iabuf2); + memcpy(entry->result, hp->h_addr, sizeof(entry->result)); + memcpy(&entry->last, hp->h_addr, sizeof(entry->last)); + changed = entry->changed = 1; + } + + } + ast_mutex_unlock(&entry->lock); + return changed; +} + +int ast_dnsmgr_refresh(struct ast_dnsmgr_entry *entry) +{ + return dnsmgr_refresh(entry, 0); +} + +/* + * Check if dnsmgr entry has changed from since last call to this function + */ +int ast_dnsmgr_changed(struct ast_dnsmgr_entry *entry) +{ + int changed; + + ast_mutex_lock(&entry->lock); + + changed = entry->changed; + entry->changed = 0; + + ast_mutex_unlock(&entry->lock); + + return changed; +} + +static void *do_refresh(void *data) +{ + for (;;) { + pthread_testcancel(); + usleep(ast_sched_wait(sched)); + pthread_testcancel(); + ast_sched_runq(sched); + } + return NULL; +} + +static int refresh_list(void *data) +{ + struct refresh_info *info = data; + struct ast_dnsmgr_entry *entry; + + /* if a refresh or reload is already in progress, exit now */ + if (ast_mutex_trylock(&refresh_lock)) { + if (info->verbose) + ast_log(LOG_WARNING, "DNS Manager refresh already in progress.\n"); + return -1; + } + + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_2 "Refreshing DNS lookups.\n"); + AST_LIST_LOCK(info->entries); + AST_LIST_TRAVERSE(info->entries, entry, list) { + if (info->regex_present && regexec(&info->filter, entry->name, 0, NULL, 0)) + continue; + + dnsmgr_refresh(entry, info->verbose); + } + AST_LIST_UNLOCK(info->entries); + + ast_mutex_unlock(&refresh_lock); + + /* automatically reschedule based on the interval */ + return refresh_interval * 1000; +} + +void dnsmgr_start_refresh(void) +{ + if (refresh_sched > -1) { + ast_sched_del(sched, refresh_sched); + refresh_sched = ast_sched_add_variable(sched, 100, refresh_list, &master_refresh_info, 1); + } +} + +static int do_reload(int loading); + +static int handle_cli_reload(int fd, int argc, char *argv[]) +{ + if (argc > 2) + return RESULT_SHOWUSAGE; + + do_reload(0); + return 0; +} + +static int handle_cli_refresh(int fd, int argc, char *argv[]) +{ + struct refresh_info info = { + .entries = &entry_list, + .verbose = 1, + }; + + if (argc > 3) + return RESULT_SHOWUSAGE; + + if (argc == 3) { + if (regcomp(&info.filter, argv[2], REG_EXTENDED | REG_NOSUB)) + return RESULT_SHOWUSAGE; + else + info.regex_present = 1; + } + + refresh_list(&info); + + if (info.regex_present) + regfree(&info.filter); + + return 0; +} + +static int handle_cli_status(int fd, int argc, char *argv[]) +{ + int count = 0; + struct ast_dnsmgr_entry *entry; + + if (argc > 2) + return RESULT_SHOWUSAGE; + + ast_cli(fd, "DNS Manager: %s\n", enabled ? "enabled" : "disabled"); + ast_cli(fd, "Refresh Interval: %d seconds\n", refresh_interval); + AST_LIST_LOCK(&entry_list); + AST_LIST_TRAVERSE(&entry_list, entry, list) + count++; + AST_LIST_UNLOCK(&entry_list); + ast_cli(fd, "Number of entries: %d\n", count); + + return 0; +} + +static struct ast_cli_entry cli_reload = { + .cmda = { "dnsmgr", "reload", NULL }, + .handler = handle_cli_reload, + .summary = "Reloads the DNS manager configuration", + .usage = + "Usage: dnsmgr reload\n" + " Reloads the DNS manager configuration.\n" +}; + +static struct ast_cli_entry cli_refresh = { + .cmda = { "dnsmgr", "refresh", NULL }, + .handler = handle_cli_refresh, + .summary = "Performs an immediate refresh", + .usage = + "Usage: dnsmgr refresh [pattern]\n" + " Peforms an immediate refresh of the managed DNS entries.\n" + " Optional regular expression pattern is used to filter the entries to refresh.\n", +}; + +static struct ast_cli_entry cli_status = { + .cmda = { "dnsmgr", "status", NULL }, + .handler = handle_cli_status, + .summary = "Display the DNS manager status", + .usage = + "Usage: dnsmgr status\n" + " Displays the DNS manager status.\n" +}; + +int dnsmgr_init(void) +{ + if (!(sched = sched_context_create())) { + ast_log(LOG_ERROR, "Unable to create schedule context.\n"); + return -1; + } + ast_cli_register(&cli_reload); + ast_cli_register(&cli_status); + return do_reload(1); +} + +int dnsmgr_reload(void) +{ + return do_reload(0); +} + +static int do_reload(int loading) +{ + struct ast_config *config; + const char *interval_value; + const char *enabled_value; + int interval; + int was_enabled; + int res = -1; + + /* ensure that no refresh cycles run while the reload is in progress */ + ast_mutex_lock(&refresh_lock); + + /* reset defaults in preparation for reading config file */ + refresh_interval = REFRESH_DEFAULT; + was_enabled = enabled; + enabled = 0; + + if (refresh_sched > -1) + ast_sched_del(sched, refresh_sched); + + if ((config = ast_config_load("dnsmgr.conf"))) { + if ((enabled_value = ast_variable_retrieve(config, "general", "enable"))) { + enabled = ast_true(enabled_value); + } + if ((interval_value = ast_variable_retrieve(config, "general", "refreshinterval"))) { + if (sscanf(interval_value, "%d", &interval) < 1) + ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", interval_value); + else if (interval < 0) + ast_log(LOG_WARNING, "Invalid refresh interval '%d' specified, using default\n", interval); + else + refresh_interval = interval; + } + ast_config_destroy(config); + } + + if (enabled && refresh_interval) + ast_log(LOG_NOTICE, "Managed DNS entries will be refreshed every %d seconds.\n", refresh_interval); + + /* if this reload enabled the manager, create the background thread + if it does not exist */ + if (enabled && !was_enabled && (refresh_thread == AST_PTHREADT_NULL)) { + if (ast_pthread_create(&refresh_thread, NULL, do_refresh, NULL) < 0) { + ast_log(LOG_ERROR, "Unable to start refresh thread.\n"); + } + else { + ast_cli_register(&cli_refresh); + /* make a background refresh happen right away */ + refresh_sched = ast_sched_add_variable(sched, 100, refresh_list, &master_refresh_info, 1); + res = 0; + } + } + /* if this reload disabled the manager and there is a background thread, + kill it */ + else if (!enabled && was_enabled && (refresh_thread != AST_PTHREADT_NULL)) { + /* wake up the thread so it will exit */ + pthread_cancel(refresh_thread); + pthread_kill(refresh_thread, SIGURG); + pthread_join(refresh_thread, NULL); + refresh_thread = AST_PTHREADT_NULL; + ast_cli_unregister(&cli_refresh); + res = 0; + } + else + res = 0; + + ast_mutex_unlock(&refresh_lock); + + return res; +} diff --git a/main/dsp.c b/main/dsp.c new file mode 100644 index 000000000..7e62c8bf7 --- /dev/null +++ b/main/dsp.c @@ -0,0 +1,1761 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer + * + * Goertzel routines are borrowed from Steve Underwood's tremendous work on the + * DTMF detector. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Convenience Signal Processing routines + * + * \author Mark Spencer + * \author Steve Underwood + */ + +/* Some routines from tone_detect.c by Steven Underwood as published under the zapata library */ +/* + tone_detect.c - General telephony tone detection, and specific + detection of DTMF. + + Copyright (C) 2001 Steve Underwood + + Despite my general liking of the GPL, I place this code in the + public domain for the benefit of all mankind - even the slimy + ones who might try to proprietize my work and use it to my + detriment. +*/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/frame.h" +#include "asterisk/channel.h" +#include "asterisk/logger.h" +#include "asterisk/dsp.h" +#include "asterisk/ulaw.h" +#include "asterisk/alaw.h" +#include "asterisk/utils.h" + +/*! Number of goertzels for progress detect */ +enum gsamp_size { + GSAMP_SIZE_NA = 183, /*!< North America - 350, 440, 480, 620, 950, 1400, 1800 Hz */ + GSAMP_SIZE_CR = 188, /*!< Costa Rica, Brazil - Only care about 425 Hz */ + GSAMP_SIZE_UK = 160 /*!< UK disconnect goertzel feed - should trigger 400hz */ +}; + +enum prog_mode { + PROG_MODE_NA = 0, + PROG_MODE_CR, + PROG_MODE_UK +}; + +enum freq_index { + /*! For US modes { */ + HZ_350 = 0, + HZ_440, + HZ_480, + HZ_620, + HZ_950, + HZ_1400, + HZ_1800, /*!< } */ + + /*! For CR/BR modes */ + HZ_425 = 0, + + /*! For UK mode */ + HZ_400 = 0 +}; + +static struct progalias { + char *name; + enum prog_mode mode; +} aliases[] = { + { "us", PROG_MODE_NA }, + { "ca", PROG_MODE_NA }, + { "cr", PROG_MODE_CR }, + { "br", PROG_MODE_CR }, + { "uk", PROG_MODE_UK }, +}; + +static struct progress { + enum gsamp_size size; + int freqs[7]; +} modes[] = { + { GSAMP_SIZE_NA, { 350, 440, 480, 620, 950, 1400, 1800 } }, /*!< North America */ + { GSAMP_SIZE_CR, { 425 } }, /*!< Costa Rica, Brazil */ + { GSAMP_SIZE_UK, { 400 } }, /*!< UK */ +}; + +#define DEFAULT_THRESHOLD 512 + +enum busy_detect { + BUSY_PERCENT = 10, /*!< The percentage difference between the two last silence periods */ + BUSY_PAT_PERCENT = 7, /*!< The percentage difference between measured and actual pattern */ + BUSY_THRESHOLD = 100, /*!< Max number of ms difference between max and min times in busy */ + BUSY_MIN = 75, /*!< Busy must be at least 80 ms in half-cadence */ + BUSY_MAX =3100 /*!< Busy can't be longer than 3100 ms in half-cadence */ +}; + +/*! Remember last 15 units */ +#define DSP_HISTORY 15 + +/*! Define if you want the fax detector -- NOT RECOMMENDED IN -STABLE */ +#define FAX_DETECT + +#define TONE_THRESH 10.0 /*!< How much louder the tone should be than channel energy */ +#define TONE_MIN_THRESH 1e8 /*!< How much tone there should be at least to attempt */ + +/*! All THRESH_XXX values are in GSAMP_SIZE chunks (us = 22ms) */ +enum gsamp_thresh { + THRESH_RING = 8, /*!< Need at least 150ms ring to accept */ + THRESH_TALK = 2, /*!< Talk detection does not work continuously */ + THRESH_BUSY = 4, /*!< Need at least 80ms to accept */ + THRESH_CONGESTION = 4, /*!< Need at least 80ms to accept */ + THRESH_HANGUP = 60, /*!< Need at least 1300ms to accept hangup */ + THRESH_RING2ANSWER = 300 /*!< Timeout from start of ring to answer (about 6600 ms) */ +}; + +#define MAX_DTMF_DIGITS 128 + +/* Basic DTMF specs: + * + * Minimum tone on = 40ms + * Minimum tone off = 50ms + * Maximum digit rate = 10 per second + * Normal twist <= 8dB accepted + * Reverse twist <= 4dB accepted + * S/N >= 15dB will detect OK + * Attenuation <= 26dB will detect OK + * Frequency tolerance +- 1.5% will detect, +-3.5% will reject + */ + +#define DTMF_THRESHOLD 8.0e7 +#define FAX_THRESHOLD 8.0e7 +#define FAX_2ND_HARMONIC 2.0 /* 4dB */ +#define DTMF_NORMAL_TWIST 6.3 /* 8dB */ +#ifdef RADIO_RELAX +#define DTMF_REVERSE_TWIST ((digitmode & DSP_DIGITMODE_RELAXDTMF) ? 6.5 : 2.5) /* 4dB normal */ +#else +#define DTMF_REVERSE_TWIST ((digitmode & DSP_DIGITMODE_RELAXDTMF) ? 4.0 : 2.5) /* 4dB normal */ +#endif +#define DTMF_RELATIVE_PEAK_ROW 6.3 /* 8dB */ +#define DTMF_RELATIVE_PEAK_COL 6.3 /* 8dB */ +#define DTMF_2ND_HARMONIC_ROW ((digitmode & DSP_DIGITMODE_RELAXDTMF) ? 1.7 : 2.5) /* 4dB normal */ +#define DTMF_2ND_HARMONIC_COL 63.1 /* 18dB */ +#define DTMF_TO_TOTAL_ENERGY 42.0 + +#ifdef OLD_DSP_ROUTINES +#define MF_THRESHOLD 8.0e7 +#define MF_NORMAL_TWIST 5.3 /* 8dB */ +#define MF_REVERSE_TWIST 4.0 /* was 2.5 */ +#define MF_RELATIVE_PEAK 5.3 /* 8dB */ +#define MF_2ND_HARMONIC 1.7 /* was 2.5 */ +#else +#define BELL_MF_THRESHOLD 1.6e9 +#define BELL_MF_TWIST 4.0 /* 6dB */ +#define BELL_MF_RELATIVE_PEAK 12.6 /* 11dB */ +#endif + +#if !defined(BUSYDETECT_MARTIN) && !defined(BUSYDETECT) && !defined(BUSYDETECT_TONEONLY) && !defined(BUSYDETECT_COMPARE_TONE_AND_SILENCE) +#define BUSYDETECT_MARTIN +#endif + +typedef struct { + float v2; + float v3; + float fac; +#ifndef OLD_DSP_ROUTINES + int samples; +#endif +} goertzel_state_t; + +typedef struct +{ + goertzel_state_t row_out[4]; + goertzel_state_t col_out[4]; +#ifdef FAX_DETECT + goertzel_state_t fax_tone; +#endif +#ifdef OLD_DSP_ROUTINES + goertzel_state_t row_out2nd[4]; + goertzel_state_t col_out2nd[4]; +#ifdef FAX_DETECT + goertzel_state_t fax_tone2nd; +#endif + int hit1; + int hit2; + int hit3; + int hit4; +#else + int hits[3]; +#endif + int mhit; + float energy; + int current_sample; + + char digits[MAX_DTMF_DIGITS + 1]; + + int current_digits; + int detected_digits; + int lost_digits; + int digit_hits[16]; +#ifdef FAX_DETECT + int fax_hits; +#endif +} dtmf_detect_state_t; + +typedef struct +{ + goertzel_state_t tone_out[6]; + int mhit; +#ifdef OLD_DSP_ROUTINES + int hit1; + int hit2; + int hit3; + int hit4; + goertzel_state_t tone_out2nd[6]; + float energy; +#else + int hits[5]; +#endif + int current_sample; + + char digits[MAX_DTMF_DIGITS + 1]; + + int current_digits; + int detected_digits; + int lost_digits; +#ifdef FAX_DETECT + int fax_hits; +#endif +} mf_detect_state_t; + +static float dtmf_row[] = +{ + 697.0, 770.0, 852.0, 941.0 +}; +static float dtmf_col[] = +{ + 1209.0, 1336.0, 1477.0, 1633.0 +}; + +static float mf_tones[] = +{ + 700.0, 900.0, 1100.0, 1300.0, 1500.0, 1700.0 +}; + +#ifdef FAX_DETECT +static float fax_freq = 1100.0; +#endif + +static char dtmf_positions[] = "123A" "456B" "789C" "*0#D"; + +#ifdef OLD_DSP_ROUTINES +static char mf_hit[6][6] = { + /* 700 + */ { 0, '1', '2', '4', '7', 'C' }, + /* 900 + */ { '1', 0, '3', '5', '8', 'A' }, + /* 1100 + */ { '2', '3', 0, '6', '9', '*' }, + /* 1300 + */ { '4', '5', '6', 0, '0', 'B' }, + /* 1500 + */ { '7', '8', '9', '0', 0, '#' }, + /* 1700 + */ { 'C', 'A', '*', 'B', '#', 0 }, +}; +#else +static char bell_mf_positions[] = "1247C-358A--69*---0B----#"; +#endif + +static inline void goertzel_sample(goertzel_state_t *s, short sample) +{ + float v1; + float fsamp = sample; + + v1 = s->v2; + s->v2 = s->v3; + s->v3 = s->fac * s->v2 - v1 + fsamp; +} + +static inline void goertzel_update(goertzel_state_t *s, short *samps, int count) +{ + int i; + + for (i=0;iv3 * s->v3 + s->v2 * s->v2 - s->v2 * s->v3 * s->fac; +} + +static inline void goertzel_init(goertzel_state_t *s, float freq, int samples) +{ + s->v2 = s->v3 = 0.0; + s->fac = 2.0 * cos(2.0 * M_PI * (freq / 8000.0)); +#ifndef OLD_DSP_ROUTINES + s->samples = samples; +#endif +} + +static inline void goertzel_reset(goertzel_state_t *s) +{ + s->v2 = s->v3 = 0.0; +} + +struct ast_dsp { + struct ast_frame f; + int threshold; + int totalsilence; + int totalnoise; + int features; + int ringtimeout; + int busymaybe; + int busycount; + int busy_tonelength; + int busy_quietlength; + int historicnoise[DSP_HISTORY]; + int historicsilence[DSP_HISTORY]; + goertzel_state_t freqs[7]; + int freqcount; + int gsamps; + enum gsamp_size gsamp_size; + enum prog_mode progmode; + int tstate; + int tcount; + int digitmode; + int thinkdigit; + float genergy; + union { + dtmf_detect_state_t dtmf; + mf_detect_state_t mf; + } td; +}; + +static void ast_dtmf_detect_init (dtmf_detect_state_t *s) +{ + int i; + +#ifdef OLD_DSP_ROUTINES + s->hit1 = + s->mhit = + s->hit3 = + s->hit4 = + s->hit2 = 0; +#else + s->hits[0] = s->hits[1] = s->hits[2] = 0; +#endif + for (i = 0; i < 4; i++) { + goertzel_init (&s->row_out[i], dtmf_row[i], 102); + goertzel_init (&s->col_out[i], dtmf_col[i], 102); +#ifdef OLD_DSP_ROUTINES + goertzel_init (&s->row_out2nd[i], dtmf_row[i] * 2.0, 102); + goertzel_init (&s->col_out2nd[i], dtmf_col[i] * 2.0, 102); +#endif + s->energy = 0.0; + } +#ifdef FAX_DETECT + /* Same for the fax dector */ + goertzel_init (&s->fax_tone, fax_freq, 102); + +#ifdef OLD_DSP_ROUTINES + /* Same for the fax dector 2nd harmonic */ + goertzel_init (&s->fax_tone2nd, fax_freq * 2.0, 102); +#endif +#endif /* FAX_DETECT */ + s->current_sample = 0; + s->detected_digits = 0; + s->current_digits = 0; + memset(&s->digits, 0, sizeof(s->digits)); + s->lost_digits = 0; + s->digits[0] = '\0'; +} + +static void ast_mf_detect_init (mf_detect_state_t *s) +{ + int i; +#ifdef OLD_DSP_ROUTINES + s->hit1 = + s->hit2 = 0; +#else + s->hits[0] = s->hits[1] = s->hits[2] = s->hits[3] = s->hits[4] = 0; +#endif + for (i = 0; i < 6; i++) { + goertzel_init (&s->tone_out[i], mf_tones[i], 160); +#ifdef OLD_DSP_ROUTINES + goertzel_init (&s->tone_out2nd[i], mf_tones[i] * 2.0, 160); + s->energy = 0.0; +#endif + } + s->current_digits = 0; + memset(&s->digits, 0, sizeof(s->digits)); + s->current_sample = 0; + s->detected_digits = 0; + s->lost_digits = 0; + s->digits[0] = '\0'; + s->mhit = 0; +} + +static int dtmf_detect (dtmf_detect_state_t *s, int16_t amp[], int samples, + int digitmode, int *writeback, int faxdetect) +{ + float row_energy[4]; + float col_energy[4]; +#ifdef FAX_DETECT + float fax_energy; +#ifdef OLD_DSP_ROUTINES + float fax_energy_2nd; +#endif +#endif /* FAX_DETECT */ + float famp; + float v1; + int i; + int j; + int sample; + int best_row; + int best_col; + int hit; + int limit; + + hit = 0; + for (sample = 0; sample < samples; sample = limit) { + /* 102 is optimised to meet the DTMF specs. */ + if ((samples - sample) >= (102 - s->current_sample)) + limit = sample + (102 - s->current_sample); + else + limit = samples; +#if defined(USE_3DNOW) + _dtmf_goertzel_update (s->row_out, amp + sample, limit - sample); + _dtmf_goertzel_update (s->col_out, amp + sample, limit - sample); +#ifdef OLD_DSP_ROUTINES + _dtmf_goertzel_update (s->row_out2nd, amp + sample, limit2 - sample); + _dtmf_goertzel_update (s->col_out2nd, amp + sample, limit2 - sample); +#endif + /* XXX Need to fax detect for 3dnow too XXX */ + #warning "Fax Support Broken" +#else + /* The following unrolled loop takes only 35% (rough estimate) of the + time of a rolled loop on the machine on which it was developed */ + for (j=sample;jenergy += famp*famp; + /* With GCC 2.95, the following unrolled code seems to take about 35% + (rough estimate) as long as a neat little 0-3 loop */ + v1 = s->row_out[0].v2; + s->row_out[0].v2 = s->row_out[0].v3; + s->row_out[0].v3 = s->row_out[0].fac*s->row_out[0].v2 - v1 + famp; + v1 = s->col_out[0].v2; + s->col_out[0].v2 = s->col_out[0].v3; + s->col_out[0].v3 = s->col_out[0].fac*s->col_out[0].v2 - v1 + famp; + v1 = s->row_out[1].v2; + s->row_out[1].v2 = s->row_out[1].v3; + s->row_out[1].v3 = s->row_out[1].fac*s->row_out[1].v2 - v1 + famp; + v1 = s->col_out[1].v2; + s->col_out[1].v2 = s->col_out[1].v3; + s->col_out[1].v3 = s->col_out[1].fac*s->col_out[1].v2 - v1 + famp; + v1 = s->row_out[2].v2; + s->row_out[2].v2 = s->row_out[2].v3; + s->row_out[2].v3 = s->row_out[2].fac*s->row_out[2].v2 - v1 + famp; + v1 = s->col_out[2].v2; + s->col_out[2].v2 = s->col_out[2].v3; + s->col_out[2].v3 = s->col_out[2].fac*s->col_out[2].v2 - v1 + famp; + v1 = s->row_out[3].v2; + s->row_out[3].v2 = s->row_out[3].v3; + s->row_out[3].v3 = s->row_out[3].fac*s->row_out[3].v2 - v1 + famp; + v1 = s->col_out[3].v2; + s->col_out[3].v2 = s->col_out[3].v3; + s->col_out[3].v3 = s->col_out[3].fac*s->col_out[3].v2 - v1 + famp; +#ifdef FAX_DETECT + /* Update fax tone */ + v1 = s->fax_tone.v2; + s->fax_tone.v2 = s->fax_tone.v3; + s->fax_tone.v3 = s->fax_tone.fac*s->fax_tone.v2 - v1 + famp; +#endif /* FAX_DETECT */ +#ifdef OLD_DSP_ROUTINES + v1 = s->col_out2nd[0].v2; + s->col_out2nd[0].v2 = s->col_out2nd[0].v3; + s->col_out2nd[0].v3 = s->col_out2nd[0].fac*s->col_out2nd[0].v2 - v1 + famp; + v1 = s->row_out2nd[0].v2; + s->row_out2nd[0].v2 = s->row_out2nd[0].v3; + s->row_out2nd[0].v3 = s->row_out2nd[0].fac*s->row_out2nd[0].v2 - v1 + famp; + v1 = s->col_out2nd[1].v2; + s->col_out2nd[1].v2 = s->col_out2nd[1].v3; + s->col_out2nd[1].v3 = s->col_out2nd[1].fac*s->col_out2nd[1].v2 - v1 + famp; + v1 = s->row_out2nd[1].v2; + s->row_out2nd[1].v2 = s->row_out2nd[1].v3; + s->row_out2nd[1].v3 = s->row_out2nd[1].fac*s->row_out2nd[1].v2 - v1 + famp; + v1 = s->col_out2nd[2].v2; + s->col_out2nd[2].v2 = s->col_out2nd[2].v3; + s->col_out2nd[2].v3 = s->col_out2nd[2].fac*s->col_out2nd[2].v2 - v1 + famp; + v1 = s->row_out2nd[2].v2; + s->row_out2nd[2].v2 = s->row_out2nd[2].v3; + s->row_out2nd[2].v3 = s->row_out2nd[2].fac*s->row_out2nd[2].v2 - v1 + famp; + v1 = s->col_out2nd[3].v2; + s->col_out2nd[3].v2 = s->col_out2nd[3].v3; + s->col_out2nd[3].v3 = s->col_out2nd[3].fac*s->col_out2nd[3].v2 - v1 + famp; + v1 = s->row_out2nd[3].v2; + s->row_out2nd[3].v2 = s->row_out2nd[3].v3; + s->row_out2nd[3].v3 = s->row_out2nd[3].fac*s->row_out2nd[3].v2 - v1 + famp; +#ifdef FAX_DETECT + /* Update fax tone */ + v1 = s->fax_tone.v2; + s->fax_tone2nd.v2 = s->fax_tone2nd.v3; + s->fax_tone2nd.v3 = s->fax_tone2nd.fac*s->fax_tone2nd.v2 - v1 + famp; +#endif /* FAX_DETECT */ +#endif + } +#endif + s->current_sample += (limit - sample); + if (s->current_sample < 102) { + if (hit && !((digitmode & DSP_DIGITMODE_NOQUELCH))) { + /* If we had a hit last time, go ahead and clear this out since likely it + will be another hit */ + for (i=sample;ifax_tone); +#endif + /* We are at the end of a DTMF detection block */ + /* Find the peak row and the peak column */ + row_energy[0] = goertzel_result (&s->row_out[0]); + col_energy[0] = goertzel_result (&s->col_out[0]); + + for (best_row = best_col = 0, i = 1; i < 4; i++) { + row_energy[i] = goertzel_result (&s->row_out[i]); + if (row_energy[i] > row_energy[best_row]) + best_row = i; + col_energy[i] = goertzel_result (&s->col_out[i]); + if (col_energy[i] > col_energy[best_col]) + best_col = i; + } + hit = 0; + /* Basic signal level test and the twist test */ + if (row_energy[best_row] >= DTMF_THRESHOLD && + col_energy[best_col] >= DTMF_THRESHOLD && + col_energy[best_col] < row_energy[best_row]*DTMF_REVERSE_TWIST && + col_energy[best_col]*DTMF_NORMAL_TWIST > row_energy[best_row]) { + /* Relative peak test */ + for (i = 0; i < 4; i++) { + if ((i != best_col && + col_energy[i]*DTMF_RELATIVE_PEAK_COL > col_energy[best_col]) || + (i != best_row + && row_energy[i]*DTMF_RELATIVE_PEAK_ROW > row_energy[best_row])) { + break; + } + } +#ifdef OLD_DSP_ROUTINES + /* ... and second harmonic test */ + if (i >= 4 && + (row_energy[best_row] + col_energy[best_col]) > 42.0*s->energy && + goertzel_result(&s->col_out2nd[best_col])*DTMF_2ND_HARMONIC_COL < col_energy[best_col] + && goertzel_result(&s->row_out2nd[best_row])*DTMF_2ND_HARMONIC_ROW < row_energy[best_row]) { +#else + /* ... and fraction of total energy test */ + if (i >= 4 && + (row_energy[best_row] + col_energy[best_col]) > DTMF_TO_TOTAL_ENERGY*s->energy) { +#endif + /* Got a hit */ + hit = dtmf_positions[(best_row << 2) + best_col]; + if (!(digitmode & DSP_DIGITMODE_NOQUELCH)) { + /* Zero out frame data if this is part DTMF */ + for (i=sample;ihit3 && s->hit3 != s->hit2) { + s->mhit = hit; + s->digit_hits[(best_row << 2) + best_col]++; + s->detected_digits++; + if (s->current_digits < MAX_DTMF_DIGITS) { + s->digits[s->current_digits++] = hit; + s->digits[s->current_digits] = '\0'; + } else { + s->lost_digits++; + } + } +#else + if (hit == s->hits[2] && hit != s->hits[1] && hit != s->hits[0]) { + s->mhit = hit; + s->digit_hits[(best_row << 2) + best_col]++; + s->detected_digits++; + if (s->current_digits < MAX_DTMF_DIGITS) { + s->digits[s->current_digits++] = hit; + s->digits[s->current_digits] = '\0'; + } else { + s->lost_digits++; + } + } +#endif + } + } +#ifdef FAX_DETECT + if (!hit && (fax_energy >= FAX_THRESHOLD) && + (fax_energy >= DTMF_TO_TOTAL_ENERGY*s->energy) && + (faxdetect)) { +#if 0 + printf("Fax energy/Second Harmonic: %f\n", fax_energy); +#endif + /* XXX Probably need better checking than just this the energy XXX */ + hit = 'f'; + s->fax_hits++; + } else { + if (s->fax_hits > 5) { + hit = 'f'; + s->mhit = 'f'; + s->detected_digits++; + if (s->current_digits < MAX_DTMF_DIGITS) { + s->digits[s->current_digits++] = hit; + s->digits[s->current_digits] = '\0'; + } else { + s->lost_digits++; + } + } + s->fax_hits = 0; + } +#endif /* FAX_DETECT */ +#ifdef OLD_DSP_ROUTINES + s->hit1 = s->hit2; + s->hit2 = s->hit3; + s->hit3 = hit; +#else + s->hits[0] = s->hits[1]; + s->hits[1] = s->hits[2]; + s->hits[2] = hit; +#endif + /* Reinitialise the detector for the next block */ + for (i = 0; i < 4; i++) { + goertzel_reset(&s->row_out[i]); + goertzel_reset(&s->col_out[i]); +#ifdef OLD_DSP_ROUTINES + goertzel_reset(&s->row_out2nd[i]); + goertzel_reset(&s->col_out2nd[i]); +#endif + } +#ifdef FAX_DETECT + goertzel_reset (&s->fax_tone); +#ifdef OLD_DSP_ROUTINES + goertzel_reset (&s->fax_tone2nd); +#endif +#endif + s->energy = 0.0; + s->current_sample = 0; + } + if ((!s->mhit) || (s->mhit != hit)) { + s->mhit = 0; + return(0); + } + return (hit); +} + +/* MF goertzel size */ +#ifdef OLD_DSP_ROUTINES +#define MF_GSIZE 160 +#else +#define MF_GSIZE 120 +#endif + +static int mf_detect (mf_detect_state_t *s, int16_t amp[], + int samples, int digitmode, int *writeback) +{ +#ifdef OLD_DSP_ROUTINES + float tone_energy[6]; + int best1; + int best2; + float max; + int sofarsogood; +#else + float energy[6]; + int best; + int second_best; +#endif + float famp; + float v1; + int i; + int j; + int sample; + int hit; + int limit; + + hit = 0; + for (sample = 0; sample < samples; sample = limit) { + /* 80 is optimised to meet the MF specs. */ + if ((samples - sample) >= (MF_GSIZE - s->current_sample)) + limit = sample + (MF_GSIZE - s->current_sample); + else + limit = samples; +#if defined(USE_3DNOW) + _dtmf_goertzel_update (s->row_out, amp + sample, limit - sample); + _dtmf_goertzel_update (s->col_out, amp + sample, limit - sample); +#ifdef OLD_DSP_ROUTINES + _dtmf_goertzel_update (s->row_out2nd, amp + sample, limit2 - sample); + _dtmf_goertzel_update (s->col_out2nd, amp + sample, limit2 - sample); +#endif + /* XXX Need to fax detect for 3dnow too XXX */ + #warning "Fax Support Broken" +#else + /* The following unrolled loop takes only 35% (rough estimate) of the + time of a rolled loop on the machine on which it was developed */ + for (j = sample; j < limit; j++) { + famp = amp[j]; +#ifdef OLD_DSP_ROUTINES + s->energy += famp*famp; +#endif + /* With GCC 2.95, the following unrolled code seems to take about 35% + (rough estimate) as long as a neat little 0-3 loop */ + v1 = s->tone_out[0].v2; + s->tone_out[0].v2 = s->tone_out[0].v3; + s->tone_out[0].v3 = s->tone_out[0].fac*s->tone_out[0].v2 - v1 + famp; + v1 = s->tone_out[1].v2; + s->tone_out[1].v2 = s->tone_out[1].v3; + s->tone_out[1].v3 = s->tone_out[1].fac*s->tone_out[1].v2 - v1 + famp; + v1 = s->tone_out[2].v2; + s->tone_out[2].v2 = s->tone_out[2].v3; + s->tone_out[2].v3 = s->tone_out[2].fac*s->tone_out[2].v2 - v1 + famp; + v1 = s->tone_out[3].v2; + s->tone_out[3].v2 = s->tone_out[3].v3; + s->tone_out[3].v3 = s->tone_out[3].fac*s->tone_out[3].v2 - v1 + famp; + v1 = s->tone_out[4].v2; + s->tone_out[4].v2 = s->tone_out[4].v3; + s->tone_out[4].v3 = s->tone_out[4].fac*s->tone_out[4].v2 - v1 + famp; + v1 = s->tone_out[5].v2; + s->tone_out[5].v2 = s->tone_out[5].v3; + s->tone_out[5].v3 = s->tone_out[5].fac*s->tone_out[5].v2 - v1 + famp; +#ifdef OLD_DSP_ROUTINES + v1 = s->tone_out2nd[0].v2; + s->tone_out2nd[0].v2 = s->tone_out2nd[0].v3; + s->tone_out2nd[0].v3 = s->tone_out2nd[0].fac*s->tone_out2nd[0].v2 - v1 + famp; + v1 = s->tone_out2nd[1].v2; + s->tone_out2nd[1].v2 = s->tone_out2nd[1].v3; + s->tone_out2nd[1].v3 = s->tone_out2nd[1].fac*s->tone_out2nd[1].v2 - v1 + famp; + v1 = s->tone_out2nd[2].v2; + s->tone_out2nd[2].v2 = s->tone_out2nd[2].v3; + s->tone_out2nd[2].v3 = s->tone_out2nd[2].fac*s->tone_out2nd[2].v2 - v1 + famp; + v1 = s->tone_out2nd[3].v2; + s->tone_out2nd[3].v2 = s->tone_out2nd[3].v3; + s->tone_out2nd[3].v3 = s->tone_out2nd[3].fac*s->tone_out2nd[3].v2 - v1 + famp; + v1 = s->tone_out2nd[4].v2; + s->tone_out2nd[4].v2 = s->tone_out2nd[4].v3; + s->tone_out2nd[4].v3 = s->tone_out2nd[4].fac*s->tone_out2nd[2].v2 - v1 + famp; + v1 = s->tone_out2nd[3].v2; + s->tone_out2nd[5].v2 = s->tone_out2nd[6].v3; + s->tone_out2nd[5].v3 = s->tone_out2nd[6].fac*s->tone_out2nd[3].v2 - v1 + famp; +#endif + } +#endif + s->current_sample += (limit - sample); + if (s->current_sample < MF_GSIZE) { + if (hit && !((digitmode & DSP_DIGITMODE_NOQUELCH))) { + /* If we had a hit last time, go ahead and clear this out since likely it + will be another hit */ + for (i=sample;itone_out[i]); + } + /* Find highest */ + best1 = 0; + max = tone_energy[0]; + for (i=1;i<6;i++) { + if (tone_energy[i] > max) { + max = tone_energy[i]; + best1 = i; + } + } + + /* Find 2nd highest */ + if (best1) { + max = tone_energy[0]; + best2 = 0; + } else { + max = tone_energy[1]; + best2 = 1; + } + + for (i=0;i<6;i++) { + if (i == best1) continue; + if (tone_energy[i] > max) { + max = tone_energy[i]; + best2 = i; + } + } + hit = 0; + if (best1 != best2) + sofarsogood=1; + else + sofarsogood=0; + /* Check for relative energies */ + for (i=0;i<6;i++) { + if (i == best1) + continue; + if (i == best2) + continue; + if (tone_energy[best1] < tone_energy[i] * MF_RELATIVE_PEAK) { + sofarsogood = 0; + break; + } + if (tone_energy[best2] < tone_energy[i] * MF_RELATIVE_PEAK) { + sofarsogood = 0; + break; + } + } + + if (sofarsogood) { + /* Check for 2nd harmonic */ + if (goertzel_result(&s->tone_out2nd[best1]) * MF_2ND_HARMONIC > tone_energy[best1]) + sofarsogood = 0; + else if (goertzel_result(&s->tone_out2nd[best2]) * MF_2ND_HARMONIC > tone_energy[best2]) + sofarsogood = 0; + } + if (sofarsogood) { + hit = mf_hit[best1][best2]; + if (!(digitmode & DSP_DIGITMODE_NOQUELCH)) { + /* Zero out frame data if this is part DTMF */ + for (i=sample;ihit3) && (s->hit3 != s->hit2)) { + s->mhit = hit; + s->detected_digits++; + if (s->current_digits < MAX_DTMF_DIGITS - 2) { + s->digits[s->current_digits++] = hit; + s->digits[s->current_digits] = '\0'; + } else { + s->lost_digits++; + } + } + } + + s->hit1 = s->hit2; + s->hit2 = s->hit3; + s->hit3 = hit; + /* Reinitialise the detector for the next block */ + for (i = 0; i < 6; i++) { + goertzel_reset(&s->tone_out[i]); + goertzel_reset(&s->tone_out2nd[i]); + } + s->energy = 0.0; + s->current_sample = 0; + } +#else + /* We're at the end of an MF detection block. */ + /* Find the two highest energies. The spec says to look for + two tones and two tones only. Taking this literally -ie + only two tones pass the minimum threshold - doesn't work + well. The sinc function mess, due to rectangular windowing + ensure that! Find the two highest energies and ensure they + are considerably stronger than any of the others. */ + energy[0] = goertzel_result(&s->tone_out[0]); + energy[1] = goertzel_result(&s->tone_out[1]); + if (energy[0] > energy[1]) { + best = 0; + second_best = 1; + } else { + best = 1; + second_best = 0; + } + /*endif*/ + for (i=2;i<6;i++) { + energy[i] = goertzel_result(&s->tone_out[i]); + if (energy[i] >= energy[best]) { + second_best = best; + best = i; + } else if (energy[i] >= energy[second_best]) { + second_best = i; + } + } + /* Basic signal level and twist tests */ + hit = 0; + if (energy[best] >= BELL_MF_THRESHOLD && energy[second_best] >= BELL_MF_THRESHOLD + && energy[best] < energy[second_best]*BELL_MF_TWIST + && energy[best]*BELL_MF_TWIST > energy[second_best]) { + /* Relative peak test */ + hit = -1; + for (i=0;i<6;i++) { + if (i != best && i != second_best) { + if (energy[i]*BELL_MF_RELATIVE_PEAK >= energy[second_best]) { + /* The best two are not clearly the best */ + hit = 0; + break; + } + } + } + } + if (hit) { + /* Get the values into ascending order */ + if (second_best < best) { + i = best; + best = second_best; + second_best = i; + } + best = best*5 + second_best - 1; + hit = bell_mf_positions[best]; + /* Look for two successive similar results */ + /* The logic in the next test is: + For KP we need 4 successive identical clean detects, with + two blocks of something different preceeding it. For anything + else we need two successive identical clean detects, with + two blocks of something different preceeding it. */ + if (hit == s->hits[4] && hit == s->hits[3] && + ((hit != '*' && hit != s->hits[2] && hit != s->hits[1])|| + (hit == '*' && hit == s->hits[2] && hit != s->hits[1] && + hit != s->hits[0]))) { + s->detected_digits++; + if (s->current_digits < MAX_DTMF_DIGITS) { + s->digits[s->current_digits++] = hit; + s->digits[s->current_digits] = '\0'; + } else { + s->lost_digits++; + } + } + } else { + hit = 0; + } + s->hits[0] = s->hits[1]; + s->hits[1] = s->hits[2]; + s->hits[2] = s->hits[3]; + s->hits[3] = s->hits[4]; + s->hits[4] = hit; + /* Reinitialise the detector for the next block */ + for (i = 0; i < 6; i++) + goertzel_reset(&s->tone_out[i]); + s->current_sample = 0; + } +#endif + if ((!s->mhit) || (s->mhit != hit)) { + s->mhit = 0; + return(0); + } + return (hit); +} + +static int __ast_dsp_digitdetect(struct ast_dsp *dsp, short *s, int len, int *writeback) +{ + int res; + + if (dsp->digitmode & DSP_DIGITMODE_MF) + res = mf_detect(&dsp->td.mf, s, len, dsp->digitmode & DSP_DIGITMODE_RELAXDTMF, writeback); + else + res = dtmf_detect(&dsp->td.dtmf, s, len, dsp->digitmode & DSP_DIGITMODE_RELAXDTMF, writeback, dsp->features & DSP_FEATURE_FAX_DETECT); + return res; +} + +int ast_dsp_digitdetect(struct ast_dsp *dsp, struct ast_frame *inf) +{ + short *s; + int len; + int ign=0; + + if (inf->frametype != AST_FRAME_VOICE) { + ast_log(LOG_WARNING, "Can't check call progress of non-voice frames\n"); + return 0; + } + if (inf->subclass != AST_FORMAT_SLINEAR) { + ast_log(LOG_WARNING, "Can only check call progress in signed-linear frames\n"); + return 0; + } + s = inf->data; + len = inf->datalen / 2; + return __ast_dsp_digitdetect(dsp, s, len, &ign); +} + +static inline int pair_there(float p1, float p2, float i1, float i2, float e) +{ + /* See if p1 and p2 are there, relative to i1 and i2 and total energy */ + /* Make sure absolute levels are high enough */ + if ((p1 < TONE_MIN_THRESH) || (p2 < TONE_MIN_THRESH)) + return 0; + /* Amplify ignored stuff */ + i2 *= TONE_THRESH; + i1 *= TONE_THRESH; + e *= TONE_THRESH; + /* Check first tone */ + if ((p1 < i1) || (p1 < i2) || (p1 < e)) + return 0; + /* And second */ + if ((p2 < i1) || (p2 < i2) || (p2 < e)) + return 0; + /* Guess it's there... */ + return 1; +} + +int ast_dsp_getdigits (struct ast_dsp *dsp, char *buf, int max) +{ + if (dsp->digitmode & DSP_DIGITMODE_MF) { + if (max > dsp->td.mf.current_digits) + max = dsp->td.mf.current_digits; + if (max > 0) { + memcpy(buf, dsp->td.mf.digits, max); + memmove(dsp->td.mf.digits, dsp->td.mf.digits + max, dsp->td.mf.current_digits - max); + dsp->td.mf.current_digits -= max; + } + buf[max] = '\0'; + return max; + } else { + if (max > dsp->td.dtmf.current_digits) + max = dsp->td.dtmf.current_digits; + if (max > 0) { + memcpy (buf, dsp->td.dtmf.digits, max); + memmove (dsp->td.dtmf.digits, dsp->td.dtmf.digits + max, dsp->td.dtmf.current_digits - max); + dsp->td.dtmf.current_digits -= max; + } + buf[max] = '\0'; + return max; + } +} + +static int __ast_dsp_call_progress(struct ast_dsp *dsp, short *s, int len) +{ + int x; + int y; + int pass; + int newstate = DSP_TONE_STATE_SILENCE; + int res = 0; + while(len) { + /* Take the lesser of the number of samples we need and what we have */ + pass = len; + if (pass > dsp->gsamp_size - dsp->gsamps) + pass = dsp->gsamp_size - dsp->gsamps; + for (x=0;xfreqcount;y++) + goertzel_sample(&dsp->freqs[y], s[x]); + dsp->genergy += s[x] * s[x]; + } + s += pass; + dsp->gsamps += pass; + len -= pass; + if (dsp->gsamps == dsp->gsamp_size) { + float hz[7]; + for (y=0;y<7;y++) + hz[y] = goertzel_result(&dsp->freqs[y]); +#if 0 + printf("\n350: 425: 440: 480: 620: 950: 1400: 1800: Energy: \n"); + printf("%.2e %.2e %.2e %.2e %.2e %.2e %.2e %.2e %.2e\n", + hz[HZ_350], hz[HZ_425], hz[HZ_440], hz[HZ_480], hz[HZ_620], hz[HZ_950], hz[HZ_1400], hz[HZ_1800], dsp->genergy); +#endif + switch(dsp->progmode) { + case PROG_MODE_NA: + if (pair_there(hz[HZ_480], hz[HZ_620], hz[HZ_350], hz[HZ_440], dsp->genergy)) { + newstate = DSP_TONE_STATE_BUSY; + } else if (pair_there(hz[HZ_440], hz[HZ_480], hz[HZ_350], hz[HZ_620], dsp->genergy)) { + newstate = DSP_TONE_STATE_RINGING; + } else if (pair_there(hz[HZ_350], hz[HZ_440], hz[HZ_480], hz[HZ_620], dsp->genergy)) { + newstate = DSP_TONE_STATE_DIALTONE; + } else if (hz[HZ_950] > TONE_MIN_THRESH * TONE_THRESH) { + newstate = DSP_TONE_STATE_SPECIAL1; + } else if (hz[HZ_1400] > TONE_MIN_THRESH * TONE_THRESH) { + if (dsp->tstate == DSP_TONE_STATE_SPECIAL1) + newstate = DSP_TONE_STATE_SPECIAL2; + } else if (hz[HZ_1800] > TONE_MIN_THRESH * TONE_THRESH) { + if (dsp->tstate == DSP_TONE_STATE_SPECIAL2) + newstate = DSP_TONE_STATE_SPECIAL3; + } else if (dsp->genergy > TONE_MIN_THRESH * TONE_THRESH) { + newstate = DSP_TONE_STATE_TALKING; + } else + newstate = DSP_TONE_STATE_SILENCE; + break; + case PROG_MODE_CR: + if (hz[HZ_425] > TONE_MIN_THRESH * TONE_THRESH) { + newstate = DSP_TONE_STATE_RINGING; + } else if (dsp->genergy > TONE_MIN_THRESH * TONE_THRESH) { + newstate = DSP_TONE_STATE_TALKING; + } else + newstate = DSP_TONE_STATE_SILENCE; + break; + case PROG_MODE_UK: + if (hz[HZ_400] > TONE_MIN_THRESH * TONE_THRESH) { + newstate = DSP_TONE_STATE_HUNGUP; + } + break; + default: + ast_log(LOG_WARNING, "Can't process in unknown prog mode '%d'\n", dsp->progmode); + } + if (newstate == dsp->tstate) { + dsp->tcount++; + if (dsp->ringtimeout) + dsp->ringtimeout++; + switch (dsp->tstate) { + case DSP_TONE_STATE_RINGING: + if ((dsp->features & DSP_PROGRESS_RINGING) && + (dsp->tcount==THRESH_RING)) { + res = AST_CONTROL_RINGING; + dsp->ringtimeout= 1; + } + break; + case DSP_TONE_STATE_BUSY: + if ((dsp->features & DSP_PROGRESS_BUSY) && + (dsp->tcount==THRESH_BUSY)) { + res = AST_CONTROL_BUSY; + dsp->features &= ~DSP_FEATURE_CALL_PROGRESS; + } + break; + case DSP_TONE_STATE_TALKING: + if ((dsp->features & DSP_PROGRESS_TALK) && + (dsp->tcount==THRESH_TALK)) { + res = AST_CONTROL_ANSWER; + dsp->features &= ~DSP_FEATURE_CALL_PROGRESS; + } + break; + case DSP_TONE_STATE_SPECIAL3: + if ((dsp->features & DSP_PROGRESS_CONGESTION) && + (dsp->tcount==THRESH_CONGESTION)) { + res = AST_CONTROL_CONGESTION; + dsp->features &= ~DSP_FEATURE_CALL_PROGRESS; + } + break; + case DSP_TONE_STATE_HUNGUP: + if ((dsp->features & DSP_FEATURE_CALL_PROGRESS) && + (dsp->tcount==THRESH_HANGUP)) { + res = AST_CONTROL_HANGUP; + dsp->features &= ~DSP_FEATURE_CALL_PROGRESS; + } + break; + } + if (dsp->ringtimeout==THRESH_RING2ANSWER) { +#if 0 + ast_log(LOG_NOTICE, "Consider call as answered because of timeout after last ring\n"); +#endif + res = AST_CONTROL_ANSWER; + dsp->features &= ~DSP_FEATURE_CALL_PROGRESS; + } + } else { +#if 0 + ast_log(LOG_NOTICE, "Stop state %d with duration %d\n", dsp->tstate, dsp->tcount); + ast_log(LOG_NOTICE, "Start state %d\n", newstate); +#endif + dsp->tstate = newstate; + dsp->tcount = 1; + } + + /* Reset goertzel */ + for (x=0;x<7;x++) + dsp->freqs[x].v2 = dsp->freqs[x].v3 = 0.0; + dsp->gsamps = 0; + dsp->genergy = 0.0; + } + } +#if 0 + if (res) + printf("Returning %d\n", res); +#endif + return res; +} + +int ast_dsp_call_progress(struct ast_dsp *dsp, struct ast_frame *inf) +{ + if (inf->frametype != AST_FRAME_VOICE) { + ast_log(LOG_WARNING, "Can't check call progress of non-voice frames\n"); + return 0; + } + if (inf->subclass != AST_FORMAT_SLINEAR) { + ast_log(LOG_WARNING, "Can only check call progress in signed-linear frames\n"); + return 0; + } + return __ast_dsp_call_progress(dsp, inf->data, inf->datalen / 2); +} + +static int __ast_dsp_silence(struct ast_dsp *dsp, short *s, int len, int *totalsilence) +{ + int accum; + int x; + int res = 0; + + if (!len) + return 0; + accum = 0; + for (x=0;xthreshold) { + /* Silent */ + dsp->totalsilence += len/8; + if (dsp->totalnoise) { + /* Move and save history */ + memmove(dsp->historicnoise + DSP_HISTORY - dsp->busycount, dsp->historicnoise + DSP_HISTORY - dsp->busycount +1, dsp->busycount*sizeof(dsp->historicnoise[0])); + dsp->historicnoise[DSP_HISTORY - 1] = dsp->totalnoise; +/* we don't want to check for busydetect that frequently */ +#if 0 + dsp->busymaybe = 1; +#endif + } + dsp->totalnoise = 0; + res = 1; + } else { + /* Not silent */ + dsp->totalnoise += len/8; + if (dsp->totalsilence) { + int silence1 = dsp->historicsilence[DSP_HISTORY - 1]; + int silence2 = dsp->historicsilence[DSP_HISTORY - 2]; + /* Move and save history */ + memmove(dsp->historicsilence + DSP_HISTORY - dsp->busycount, dsp->historicsilence + DSP_HISTORY - dsp->busycount + 1, dsp->busycount*sizeof(dsp->historicsilence[0])); + dsp->historicsilence[DSP_HISTORY - 1] = dsp->totalsilence; + /* check if the previous sample differs only by BUSY_PERCENT from the one before it */ + if (silence1 < silence2) { + if (silence1 + silence1*BUSY_PERCENT/100 >= silence2) + dsp->busymaybe = 1; + else + dsp->busymaybe = 0; + } else { + if (silence1 - silence1*BUSY_PERCENT/100 <= silence2) + dsp->busymaybe = 1; + else + dsp->busymaybe = 0; + } + } + dsp->totalsilence = 0; + } + if (totalsilence) + *totalsilence = dsp->totalsilence; + return res; +} + +#ifdef BUSYDETECT_MARTIN +int ast_dsp_busydetect(struct ast_dsp *dsp) +{ + int res = 0, x; +#ifndef BUSYDETECT_TONEONLY + int avgsilence = 0, hitsilence = 0; +#endif + int avgtone = 0, hittone = 0; + if (!dsp->busymaybe) + return res; + for (x=DSP_HISTORY - dsp->busycount;xhistoricsilence[x]; +#endif + avgtone += dsp->historicnoise[x]; + } +#ifndef BUSYDETECT_TONEONLY + avgsilence /= dsp->busycount; +#endif + avgtone /= dsp->busycount; + for (x=DSP_HISTORY - dsp->busycount;x dsp->historicsilence[x]) { + if (avgsilence - (avgsilence*BUSY_PERCENT/100) <= dsp->historicsilence[x]) + hitsilence++; + } else { + if (avgsilence + (avgsilence*BUSY_PERCENT/100) >= dsp->historicsilence[x]) + hitsilence++; + } +#endif + if (avgtone > dsp->historicnoise[x]) { + if (avgtone - (avgtone*BUSY_PERCENT/100) <= dsp->historicnoise[x]) + hittone++; + } else { + if (avgtone + (avgtone*BUSY_PERCENT/100) >= dsp->historicnoise[x]) + hittone++; + } + } +#ifndef BUSYDETECT_TONEONLY + if ((hittone >= dsp->busycount - 1) && (hitsilence >= dsp->busycount - 1) && + (avgtone >= BUSY_MIN && avgtone <= BUSY_MAX) && + (avgsilence >= BUSY_MIN && avgsilence <= BUSY_MAX)) { +#else + if ((hittone >= dsp->busycount - 1) && (avgtone >= BUSY_MIN && avgtone <= BUSY_MAX)) { +#endif +#ifdef BUSYDETECT_COMPARE_TONE_AND_SILENCE +#ifdef BUSYDETECT_TONEONLY +#error You cant use BUSYDETECT_TONEONLY together with BUSYDETECT_COMPARE_TONE_AND_SILENCE +#endif + if (avgtone > avgsilence) { + if (avgtone - avgtone*BUSY_PERCENT/100 <= avgsilence) + res = 1; + } else { + if (avgtone + avgtone*BUSY_PERCENT/100 >= avgsilence) + res = 1; + } +#else + res = 1; +#endif + } + /* If we know the expected busy tone length, check we are in the range */ + if (res && (dsp->busy_tonelength > 0)) { + if (abs(avgtone - dsp->busy_tonelength) > (dsp->busy_tonelength*BUSY_PAT_PERCENT/100)) { +#if 0 + ast_log(LOG_NOTICE, "busy detector: avgtone of %d not close enough to desired %d\n", + avgtone, dsp->busy_tonelength); +#endif + res = 0; + } + } +#ifndef BUSYDETECT_TONEONLY + /* If we know the expected busy tone silent-period length, check we are in the range */ + if (res && (dsp->busy_quietlength > 0)) { + if (abs(avgsilence - dsp->busy_quietlength) > (dsp->busy_quietlength*BUSY_PAT_PERCENT/100)) { +#if 0 + ast_log(LOG_NOTICE, "busy detector: avgsilence of %d not close enough to desired %d\n", + avgsilence, dsp->busy_quietlength); +#endif + res = 0; + } + } +#endif +#if 1 + if (res) + ast_log(LOG_DEBUG, "ast_dsp_busydetect detected busy, avgtone: %d, avgsilence %d\n", avgtone, avgsilence); +#endif + return res; +} +#endif + +#ifdef BUSYDETECT +int ast_dsp_busydetect(struct ast_dsp *dsp) +{ + int x; + int res = 0; + int max, min; + +#if 0 + if (dsp->busy_hits > 5); + return 0; +#endif + if (dsp->busymaybe) { +#if 0 + printf("Maybe busy!\n"); +#endif + dsp->busymaybe = 0; + min = 9999; + max = 0; + for (x=DSP_HISTORY - dsp->busycount;xhistoricsilence[x], dsp->historicnoise[x]); +#endif + if (dsp->historicsilence[x] < min) + min = dsp->historicsilence[x]; + if (dsp->historicnoise[x] < min) + min = dsp->historicnoise[x]; + if (dsp->historicsilence[x] > max) + max = dsp->historicsilence[x]; + if (dsp->historicnoise[x] > max) + max = dsp->historicnoise[x]; + } + if ((max - min < BUSY_THRESHOLD) && (max < BUSY_MAX) && (min > BUSY_MIN)) { +#if 0 + printf("Busy!\n"); +#endif + res = 1; + } +#if 0 + printf("Min: %d, max: %d\n", min, max); +#endif + } + return res; +} +#endif + +int ast_dsp_silence(struct ast_dsp *dsp, struct ast_frame *f, int *totalsilence) +{ + short *s; + int len; + + if (f->frametype != AST_FRAME_VOICE) { + ast_log(LOG_WARNING, "Can't calculate silence on a non-voice frame\n"); + return 0; + } + if (f->subclass != AST_FORMAT_SLINEAR) { + ast_log(LOG_WARNING, "Can only calculate silence on signed-linear frames :(\n"); + return 0; + } + s = f->data; + len = f->datalen/2; + return __ast_dsp_silence(dsp, s, len, totalsilence); +} + +struct ast_frame *ast_dsp_process(struct ast_channel *chan, struct ast_dsp *dsp, struct ast_frame *af) +{ + int silence; + int res; + int digit; + int x; + short *shortdata; + unsigned char *odata; + int len; + int writeback = 0; + +#define FIX_INF(inf) do { \ + if (writeback) { \ + switch(inf->subclass) { \ + case AST_FORMAT_SLINEAR: \ + break; \ + case AST_FORMAT_ULAW: \ + for (x=0;xframetype != AST_FRAME_VOICE) + return af; + odata = af->data; + len = af->datalen; + /* Make sure we have short data */ + switch(af->subclass) { + case AST_FORMAT_SLINEAR: + shortdata = af->data; + len = af->datalen / 2; + break; + case AST_FORMAT_ULAW: + shortdata = alloca(af->datalen * 2); + for (x = 0;x < len; x++) + shortdata[x] = AST_MULAW(odata[x]); + break; + case AST_FORMAT_ALAW: + shortdata = alloca(af->datalen * 2); + for (x = 0; x < len; x++) + shortdata[x] = AST_ALAW(odata[x]); + break; + default: + ast_log(LOG_WARNING, "Inband DTMF is not supported on codec %s. Use RFC2833\n", ast_getformatname(af->subclass)); + return af; + } + silence = __ast_dsp_silence(dsp, shortdata, len, NULL); + if ((dsp->features & DSP_FEATURE_SILENCE_SUPPRESS) && silence) { + memset(&dsp->f, 0, sizeof(dsp->f)); + dsp->f.frametype = AST_FRAME_NULL; + return &dsp->f; + } + if ((dsp->features & DSP_FEATURE_BUSY_DETECT) && ast_dsp_busydetect(dsp)) { + chan->_softhangup |= AST_SOFTHANGUP_DEV; + memset(&dsp->f, 0, sizeof(dsp->f)); + dsp->f.frametype = AST_FRAME_CONTROL; + dsp->f.subclass = AST_CONTROL_BUSY; + ast_log(LOG_DEBUG, "Requesting Hangup because the busy tone was detected on channel %s\n", chan->name); + return &dsp->f; + } + if ((dsp->features & DSP_FEATURE_DTMF_DETECT)) { + digit = __ast_dsp_digitdetect(dsp, shortdata, len, &writeback); +#if 0 + if (digit) + printf("Performing digit detection returned %d, digitmode is %d\n", digit, dsp->digitmode); +#endif + if (dsp->digitmode & (DSP_DIGITMODE_MUTECONF | DSP_DIGITMODE_MUTEMAX)) { + if (!dsp->thinkdigit) { + if (digit) { + /* Looks like we might have something. + * Request a conference mute for the moment */ + memset(&dsp->f, 0, sizeof(dsp->f)); + dsp->f.frametype = AST_FRAME_DTMF; + dsp->f.subclass = 'm'; + dsp->thinkdigit = 'x'; + FIX_INF(af); + if (chan) + ast_queue_frame(chan, af); + ast_frfree(af); + return &dsp->f; + } + } else { + if (digit) { + /* Thought we saw one last time. Pretty sure we really have now */ + if (dsp->thinkdigit) { + if ((dsp->thinkdigit != 'x') && (dsp->thinkdigit != digit)) { + /* If we found a digit, and we're changing digits, go + ahead and send this one, but DON'T stop confmute because + we're detecting something else, too... */ + memset(&dsp->f, 0, sizeof(dsp->f)); + dsp->f.frametype = AST_FRAME_DTMF; + dsp->f.subclass = dsp->thinkdigit; + FIX_INF(af); + if (chan) + ast_queue_frame(chan, af); + ast_frfree(af); + } + dsp->thinkdigit = digit; + return &dsp->f; + } + dsp->thinkdigit = digit; + } else { + if (dsp->thinkdigit) { + memset(&dsp->f, 0, sizeof(dsp->f)); + if (dsp->thinkdigit != 'x') { + /* If we found a digit, send it now */ + dsp->f.frametype = AST_FRAME_DTMF; + dsp->f.subclass = dsp->thinkdigit; + dsp->thinkdigit = 0; + } else { + dsp->f.frametype = AST_FRAME_DTMF; + dsp->f.subclass = 'u'; + dsp->thinkdigit = 0; + } + FIX_INF(af); + if (chan) + ast_queue_frame(chan, af); + ast_frfree(af); + return &dsp->f; + } + } + } + } else if (!digit) { + /* Only check when there is *not* a hit... */ + if (dsp->digitmode & DSP_DIGITMODE_MF) { + if (dsp->td.mf.current_digits) { + memset(&dsp->f, 0, sizeof(dsp->f)); + dsp->f.frametype = AST_FRAME_DTMF; + dsp->f.subclass = dsp->td.mf.digits[0]; + memmove(dsp->td.mf.digits, dsp->td.mf.digits + 1, dsp->td.mf.current_digits); + dsp->td.mf.current_digits--; + FIX_INF(af); + if (chan) + ast_queue_frame(chan, af); + ast_frfree(af); + return &dsp->f; + } + } else { + if (dsp->td.dtmf.current_digits) { + memset(&dsp->f, 0, sizeof(dsp->f)); + dsp->f.frametype = AST_FRAME_DTMF; + dsp->f.subclass = dsp->td.dtmf.digits[0]; + memmove(dsp->td.dtmf.digits, dsp->td.dtmf.digits + 1, dsp->td.dtmf.current_digits); + dsp->td.dtmf.current_digits--; + FIX_INF(af); + if (chan) + ast_queue_frame(chan, af); + ast_frfree(af); + return &dsp->f; + } + } + } + } + if ((dsp->features & DSP_FEATURE_CALL_PROGRESS)) { + res = __ast_dsp_call_progress(dsp, shortdata, len); + if (res) { + switch(res) { + case AST_CONTROL_ANSWER: + case AST_CONTROL_BUSY: + case AST_CONTROL_RINGING: + case AST_CONTROL_CONGESTION: + case AST_CONTROL_HANGUP: + memset(&dsp->f, 0, sizeof(dsp->f)); + dsp->f.frametype = AST_FRAME_CONTROL; + dsp->f.subclass = res; + dsp->f.src = "dsp_progress"; + if (chan) + ast_queue_frame(chan, &dsp->f); + break; + default: + ast_log(LOG_WARNING, "Don't know how to represent call progress message %d\n", res); + } + } + } + FIX_INF(af); + return af; +} + +static void ast_dsp_prog_reset(struct ast_dsp *dsp) +{ + int max = 0; + int x; + + dsp->gsamp_size = modes[dsp->progmode].size; + dsp->gsamps = 0; + for (x=0;xprogmode].freqs) / sizeof(modes[dsp->progmode].freqs[0]);x++) { + if (modes[dsp->progmode].freqs[x]) { + goertzel_init(&dsp->freqs[x], (float)modes[dsp->progmode].freqs[x], dsp->gsamp_size); + max = x + 1; + } + } + dsp->freqcount = max; + dsp->ringtimeout= 0; +} + +struct ast_dsp *ast_dsp_new(void) +{ + struct ast_dsp *dsp; + + if ((dsp = ast_calloc(1, sizeof(*dsp)))) { + dsp->threshold = DEFAULT_THRESHOLD; + dsp->features = DSP_FEATURE_SILENCE_SUPPRESS; + dsp->busycount = DSP_HISTORY; + /* Initialize DTMF detector */ + ast_dtmf_detect_init(&dsp->td.dtmf); + /* Initialize initial DSP progress detect parameters */ + ast_dsp_prog_reset(dsp); + } + return dsp; +} + +void ast_dsp_set_features(struct ast_dsp *dsp, int features) +{ + dsp->features = features; +} + +void ast_dsp_free(struct ast_dsp *dsp) +{ + free(dsp); +} + +void ast_dsp_set_threshold(struct ast_dsp *dsp, int threshold) +{ + dsp->threshold = threshold; +} + +void ast_dsp_set_busy_count(struct ast_dsp *dsp, int cadences) +{ + if (cadences < 4) + cadences = 4; + if (cadences > DSP_HISTORY) + cadences = DSP_HISTORY; + dsp->busycount = cadences; +} + +void ast_dsp_set_busy_pattern(struct ast_dsp *dsp, int tonelength, int quietlength) +{ + dsp->busy_tonelength = tonelength; + dsp->busy_quietlength = quietlength; + ast_log(LOG_DEBUG, "dsp busy pattern set to %d,%d\n", tonelength, quietlength); +} + +void ast_dsp_digitreset(struct ast_dsp *dsp) +{ + int i; + + dsp->thinkdigit = 0; + if (dsp->digitmode & DSP_DIGITMODE_MF) { + memset(dsp->td.mf.digits, 0, sizeof(dsp->td.mf.digits)); + dsp->td.mf.current_digits = 0; + /* Reinitialise the detector for the next block */ + for (i = 0; i < 6; i++) { + goertzel_reset(&dsp->td.mf.tone_out[i]); +#ifdef OLD_DSP_ROUTINES + goertzel_reset(&dsp->td.mf.tone_out2nd[i]); +#endif + } +#ifdef OLD_DSP_ROUTINES + dsp->td.mf.energy = 0.0; + dsp->td.mf.hit1 = dsp->td.mf.hit2 = dsp->td.mf.hit3 = dsp->td.mf.hit4 = dsp->td.mf.mhit = 0; +#else + dsp->td.mf.hits[4] = dsp->td.mf.hits[3] = dsp->td.mf.hits[2] = dsp->td.mf.hits[1] = dsp->td.mf.hits[0] = dsp->td.mf.mhit = 0; +#endif + dsp->td.mf.current_sample = 0; + } else { + memset(dsp->td.dtmf.digits, 0, sizeof(dsp->td.dtmf.digits)); + dsp->td.dtmf.current_digits = 0; + /* Reinitialise the detector for the next block */ + for (i = 0; i < 4; i++) { + goertzel_reset(&dsp->td.dtmf.row_out[i]); + goertzel_reset(&dsp->td.dtmf.col_out[i]); +#ifdef OLD_DSP_ROUTINES + goertzel_reset(&dsp->td.dtmf.row_out2nd[i]); + goertzel_reset(&dsp->td.dtmf.col_out2nd[i]); +#endif + } +#ifdef FAX_DETECT + goertzel_reset (&dsp->td.dtmf.fax_tone); +#endif +#ifdef OLD_DSP_ROUTINES +#ifdef FAX_DETECT + goertzel_reset (&dsp->td.dtmf.fax_tone2nd); +#endif + dsp->td.dtmf.hit1 = dsp->td.dtmf.hit2 = dsp->td.dtmf.hit3 = dsp->td.dtmf.hit4 = dsp->td.dtmf.mhit = 0; +#else + dsp->td.dtmf.hits[2] = dsp->td.dtmf.hits[1] = dsp->td.dtmf.hits[0] = dsp->td.dtmf.mhit = 0; +#endif + dsp->td.dtmf.energy = 0.0; + dsp->td.dtmf.current_sample = 0; + } +} + +void ast_dsp_reset(struct ast_dsp *dsp) +{ + int x; + + dsp->totalsilence = 0; + dsp->gsamps = 0; + for (x=0;x<4;x++) + dsp->freqs[x].v2 = dsp->freqs[x].v3 = 0.0; + memset(dsp->historicsilence, 0, sizeof(dsp->historicsilence)); + memset(dsp->historicnoise, 0, sizeof(dsp->historicnoise)); + dsp->ringtimeout= 0; +} + +int ast_dsp_digitmode(struct ast_dsp *dsp, int digitmode) +{ + int new; + int old; + + old = dsp->digitmode & (DSP_DIGITMODE_DTMF | DSP_DIGITMODE_MF | DSP_DIGITMODE_MUTECONF | DSP_DIGITMODE_MUTEMAX); + new = digitmode & (DSP_DIGITMODE_DTMF | DSP_DIGITMODE_MF | DSP_DIGITMODE_MUTECONF | DSP_DIGITMODE_MUTEMAX); + if (old != new) { + /* Must initialize structures if switching from MF to DTMF or vice-versa */ + if (new & DSP_DIGITMODE_MF) + ast_mf_detect_init(&dsp->td.mf); + else + ast_dtmf_detect_init(&dsp->td.dtmf); + } + dsp->digitmode = digitmode; + return 0; +} + +int ast_dsp_set_call_progress_zone(struct ast_dsp *dsp, char *zone) +{ + int x; + + for (x=0;xprogmode = aliases[x].mode; + ast_dsp_prog_reset(dsp); + return 0; + } + } + return -1; +} + +int ast_dsp_get_tstate(struct ast_dsp *dsp) +{ + return dsp->tstate; +} + +int ast_dsp_get_tcount(struct ast_dsp *dsp) +{ + return dsp->tcount; +} diff --git a/main/ecdisa.h b/main/ecdisa.h new file mode 100644 index 000000000..df6f773a0 --- /dev/null +++ b/main/ecdisa.h @@ -0,0 +1,15 @@ +/* ecdisa.h: Generated from frequency 2100 + by gentone. 80 samples */ +static unsigned char ecdisa[80] = { + 255, 143, 58, 16, 171, 146, 34, 20, + 156, 151, 25, 26, 149, 159, 19, 38, + 145, 177, 16, 73, 143, 73, 16, 177, + 145, 38, 19, 159, 149, 26, 25, 151, + 156, 20, 34, 146, 171, 16, 58, 143, + 255, 15, 186, 144, 43, 18, 162, 148, + 28, 23, 153, 154, 21, 31, 147, 166, + 17, 49, 144, 201, 15, 201, 144, 49, + 17, 166, 147, 31, 21, 154, 153, 23, + 28, 148, 162, 18, 43, 144, 186, 15, + +}; diff --git a/main/editline/CHANGES b/main/editline/CHANGES new file mode 100644 index 000000000..c18b56cdf --- /dev/null +++ b/main/editline/CHANGES @@ -0,0 +1,42 @@ +2002-02-25 : Christos Zoulas + * Bring in constification fixes from NetBSD tree. + * Use NetBSD's vis, fgetln + +2002-02-10 : Jason Evans + + * Makefile.in : Append "" arguments to for loops, to avoid syntax errors + with some shells that occur if there are no arguments to the for + loops. + +2002-02-09 : Jason Evans + + * Install man pages in @prefix@/man/, rather than @prefix@/share/man. + + * Fix the Darwin -install_name S_LDFLAGS argument to default to a prefix + of /usr/local if --prefix is not specified. + +2002-02-05 : Jason Evans + + * Convert to using an autoconf-generated config.h, rather than passing + -D_HAVE_=1 definitions on the command line. Include sys.h in + config.h, and include config.h in .c files rather than sys.h. + + * Mangle function names for implementations in the np directory in order + to avoid namespace collisions with other code that may provide copies + of the same unimplemented functions. For example: + + #define fgetln libedit_fgetln + +2002-02-03 : Jason Evans + + * Add autoconf infrastructure, plus a generic Makefile that works with + at least BSD make, GNU make and Sun make. + + * Port and/or test on FreeBSD 4.5, FreeBSD-current, NetBSD 1.5 (sparc64 + and arm32), Apple OS X 10.1.2, Solaris 2.6, and Red Hat Linux 2.6. + Add the np directory, which contains implementations of non-portable + functions. + + * Add the LIBEDIT_MAJOR and LIBEDIT_MINOR macros to histedit.h, since + there is otherwise no straightforward method of programmatically + detecting the library version. diff --git a/main/editline/INSTALL b/main/editline/INSTALL new file mode 100644 index 000000000..16fb6ffd1 --- /dev/null +++ b/main/editline/INSTALL @@ -0,0 +1,64 @@ +Building this distribution in many cases is as simple as typing the following +while in the root directory of the source tree: + + ./configure + make + +To install, do the above, then type: + + make install + +Additional build targets of finer granularity include: + + lib_a + lib_s + install_hdr + install_lib + install_lib_a + install_lib_s + install_man + test + +Cleanup targets include: + + clean + distclean + +Optionally, pass any of the following (not a definitive list) arguments to +'configure': + +--prefix= + Set the base directory in which to install. For example: + + ./configure --prefix=/usr/local + + will cause files to be installed into /usr/local/bin, /usr/local/man, + /usr/local/include, /usr/local/lib, and /usr/local/share. + +--disable-readline + By default, libedit is built and installed such that it works as a + drop-in replacement for the readline library. This option turns that + behavior off. + +--enable-debug + Build debugging code (for libedit development). + +Optionally, define environment variables, including (not exclusively): + +CFLAGS="?" + Pass these flags to the compiler. You probably shouldn't define this + unless you know what you are doing. + +CPPFLAGS="?" + Pass these flags to the C preprocessor. Note that CFLAGS is not passed + to 'cpp' when 'configure' is looking for include files, so you must use + CPPFLAGS instead if you need to help 'configure' find header files. + +LD_LIBRARY_PATH="?" + 'ld' uses this colon-separated list to find libraries. + +LDFLAGS="?" + Flags passed to 'gcc', which should normally be passed on to 'ld'. + +PATH="?" + 'configure' uses this to find programs. diff --git a/main/editline/Makefile.in b/main/editline/Makefile.in new file mode 100644 index 000000000..805642281 --- /dev/null +++ b/main/editline/Makefile.in @@ -0,0 +1,233 @@ +# +# Generic Makefile for libedit. +# + +OSTYPE=$(shell uname -s) +define cyg_subst_sys + if uname -s | grep -qi cygwin; then \ + cat $@ | sed -e s/"sys\.h"/"config.h"/g > $@.copy; \ + mv --force $@.copy $@; \ + fi +endef + +SHELL = /bin/sh + +CC = @CC@ +AR = @AR@ +RANLIB = @RANLIB@ +CPPFLAGS = @CPPFLAGS@ -I. +CFLAGS = @CFLAGS@ +A_CFLAGS = @A_CFLAGS@ +S_CFLAGS = @S_CFLAGS@ +LDFLAGS = @LDFLAGS@ +S_LDFLAGS = @S_LDFLAGS@ +LIBS = @LIBS@ + +INSTALL = @INSTALL@ +PREFIX = @prefix@ + +ifeq ($(OSTYPE),SunOS) +CFLAGS+=-DSOLARIS -I../include/solaris-compat +endif + +# .c files. +ACSRCS = @ACSRCS@ +BCSRCS = @BCSRCS@ +CCSRCS = @CCSRCS@ + +# Generated .c files. +AGCSRCS = @AGCSRCS@ +BGCSRCS = @BGCSRCS@ + +# .h files. +HDRS = @HDRS@ + +# Generated .h files. +AGHDRS = @AGHDRS@ +BGHDRS = @BGHDRS@ + +# Installed .h files. +IHDRS = @IHDRS@ +IHDR_LINKS = @IHDR_LINKS@ +HDR_DIRS = @HDR_DIRS@ + +# Man pages. +MAN3 = @MAN3@ +MAN5 = @MAN5@ +MAN3_LINKS = @MAN3_LINKS@ +MAN_DIRS = @MAN_DIRS@ + +# Library. +LIB_DIRS = @LIB_DIRS@ +LIB_VER = @LIB_VER@ +LIB_A = @LIB_A@ +LIB_A_LINKS = @LIB_A_LINKS@ +LIB_S = @LIB_S@ +LIB_S_LINKS = @LIB_S_LINKS@ + +# Test program. +TEST = @TEST@ +TCSRCS = @TCSRCS@ + +# Clear out all paths, then set just one (default path) for the main build +# directory. +.PATH : +.PATH : . + +.SUFFIXES : +.SUFFIXES : .c .o .o_a .o_s + +all : lib_a lib_s + +lib_a : $(LIB_A) +lib_s : $(LIB_S) + +test : $(TEST) + +install : install_hdr install_lib install_man + +install_hdr : + @for i in $(HDR_DIRS) ; do \ + echo "$(INSTALL) -d $(PREFIX)/$$i/"; \ + $(INSTALL) -d $(PREFIX)/$$i/; \ + done + @for i in $(IHDRS); do \ + echo "$(INSTALL) -m 0444 $$i $(PREFIX)/include/`dirname $$i`/"; \ + $(INSTALL) -m 0444 $$i $(PREFIX)/include/`dirname $$i`/; \ + done + @f=; \ + for i in $(IHDR_LINKS) ""; do \ + if test -z "$$f" ; then \ + f=$$i; \ + else \ + echo "rm -f $(PREFIX)/include/$$i"; \ + rm -f $(PREFIX)/include/$$i; \ + echo "ln -s $$f $(PREFIX)/include/$$i"; \ + ln -s $$f $(PREFIX)/include/$$i; \ + f=; \ + fi; \ + done + +install_lib : install_lib_a install_lib_s + +install_lib_common : + @for i in $(LIB_DIRS) ; do \ + echo "$(INSTALL) -d $(PREFIX)/$$i/"; \ + $(INSTALL) -d $(PREFIX)/$$i/; \ + done + +install_lib_a : $(LIB_A) install_lib_common + $(INSTALL) -m 0644 $(LIB_A) $(PREFIX)/lib/ + @f=; \ + for i in $(LIB_A_LINKS) ""; do \ + if test -z "$$f" ; then \ + f=$$i; \ + else \ + echo "rm -f $(PREFIX)/lib/$$i"; \ + rm -f $(PREFIX)/lib/$$i; \ + echo "ln -s $$f $(PREFIX)/lib/$$i"; \ + ln -s $$f $(PREFIX)/lib/$$i; \ + f=; \ + fi; \ + done + +install_lib_s : $(LIB_S) install_lib_common + $(INSTALL) -m 0755 $(LIB_S) $(PREFIX)/lib/ + @f=; \ + for i in $(LIB_S_LINKS) ""; do \ + if test -z "$$f" ; then \ + f=$$i; \ + else \ + echo "rm -f $(PREFIX)/lib/$$i"; \ + rm -f $(PREFIX)/lib/$$i; \ + echo "ln -s $$f $(PREFIX)/lib/$$i"; \ + ln -s $$f $(PREFIX)/lib/$$i; \ + f=; \ + fi; \ + done + +install_man : + @for i in $(MAN_DIRS) ; do \ + echo "$(INSTALL) -d $(PREFIX)/$$i/"; \ + $(INSTALL) -d $(PREFIX)/$$i/; \ + done + @for i in $(MAN3); do \ + echo $(INSTALL) -m 0444 $$i $(PREFIX)/man/man3/; \ + $(INSTALL) -m 0444 $$i $(PREFIX)/man/man3/; \ + done + @f=; \ + for i in $(MAN3_LINKS) ""; do \ + if test -z "$$f" ; then \ + f=$$i; \ + else \ + echo "rm -f $(PREFIX)/man/man3/$$i"; \ + rm -f $(PREFIX)/man/man3/$$i; \ + echo "ln -s $$f $(PREFIX)/man/man3/$$i"; \ + ln -s $$f $(PREFIX)/man/man3/$$i; \ + f=; \ + fi; \ + done + @for i in $(MAN5); do\ + echo $(INSTALL) -m 0444 $$i $(PREFIX)/man/man5/; \ + $(INSTALL) -m 0444 $$i $(PREFIX)/man/man5/; \ + done + +clean : + rm -f $(AGCSRCS) $(BGCSRCS) $(AGHDRS) $(BGHDRS) $(LIB_A) $(LIB_S) + rm -f $(BGCSRCS:.c=.o_a) $(CCSRCS:.c=.o_a) + rm -f $(BGCSRCS:.c=.o_s) $(CCSRCS:.c=.o_s) + rm -f $(TCSRCS:.c=.o) $(TEST) + +distclean : clean + rm -f config.cache config.log config.status config.h Makefile + +# +# Internal targets and rules. +# + +$(LIB_A) : $(BGCSRCS:.c=.o_a) $(CCSRCS:.c=.o_a) + $(AR) cru $@ $? + $(RANLIB) $@ + +$(LIB_S) : $(BGCSRCS:.c=.o_s) $(CCSRCS:.c=.o_s) + $(CC) $(S_LDFLAGS) -o $@ $(BGCSRCS:.c=.o_s) $(CCSRCS:.c=.o_s) $(LIBS) + +$(TEST) : $(TCSRCS:.c=.o) $(LIB_A) + $(CC) -o $@ $(TCSRCS:.c=.o) $(LIB_A) $(LIBS) + +common.h : common.c + $(SHELL) makelist -h common.c > $@ + +emacs.h : emacs.c + $(SHELL) makelist -h emacs.c> $@ + +vi.h : vi.c + $(SHELL) makelist -h vi.c > $@ + +fcns.h : $(AGHDRS) + $(SHELL) makelist -fh $(AGHDRS) > $@ + +fcns.c : $(AGHDRS) fcns.h + $(SHELL) makelist -fc $(AGHDRS) > $@ + $(cyg_subst_sys) + +help.h : $(ACSRCS) + $(SHELL) makelist -bh $(ACSRCS) > $@ + +help.c : $(ACSRCS) help.h + $(SHELL) makelist -bc $(ACSRCS) > $@ + $(cyg_subst_sys) + +editline.c : $(ACSRCS) $(BCSRCS) $(AGCSRCS) + $(SHELL) makelist -e $(ACSRCS) $(BCSRCS) $(AGCSRCS) > $@ + +.c.o : + $(CC) -c $(A_CFLAGS) $(CFLAGS) $(CPPFLAGS) $< -o $@ + +.c.o_a : $(AGHDRS) $(BGHDRS) + $(CC) -c $(A_CFLAGS) $(CFLAGS) $(CPPFLAGS) $< -o $@ + +.c.o_s : $(AGHDRS) $(BGHDRS) + $(CC) -c $(S_CFLAGS) $(CFLAGS) $(CPPFLAGS) $< -o $@ + +$(CCSRCS) : $(BGHDRS) diff --git a/main/editline/PLATFORMS b/main/editline/PLATFORMS new file mode 100644 index 000000000..ea7c5bb68 --- /dev/null +++ b/main/editline/PLATFORMS @@ -0,0 +1,13 @@ +This distribution of libedit is expected to work on at least the following +platforms. It may also work on additional platforms, but no explicit support +for them is built into the configuration system. + +* Apple OS X 10.1. + +* FreeBSD 4.x. + +* NetBSD 1.5. + +* Red Hat Linux 7.2. + +* Sun Solaris 2.6. diff --git a/main/editline/README b/main/editline/README new file mode 100644 index 000000000..49a2a6947 --- /dev/null +++ b/main/editline/README @@ -0,0 +1,11 @@ +libedit is a command line editing and history library. It is designed to be +used by interactive programs that allow the user to type commands at a terminal +prompt. + +The following files may be of direct interest to the user: + +* CHANGES - Software change log. + +* INSTALL - Installation information. + +* PLATFORMS - Supported platforms and platform-specific information. diff --git a/main/editline/TEST/test.c b/main/editline/TEST/test.c new file mode 100644 index 000000000..3169a2071 --- /dev/null +++ b/main/editline/TEST/test.c @@ -0,0 +1,268 @@ +/* $NetBSD: test.c,v 1.9 2000/09/04 23:36:41 lukem Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1992, 1993\n\ + The Regents of the University of California. All rights reserved.\n"); +#endif /* not lint */ + +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)test.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: test.c,v 1.9 2000/09/04 23:36:41 lukem Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * test.c: A little test program + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "histedit.h" +#include "tokenizer.h" + +static int continuation = 0; +static EditLine *el = NULL; + +static u_char complete(EditLine *, int); + int main(int, char **); +static char *prompt(EditLine *); +static void sig(int); + +static char * +prompt(EditLine *el) +{ + static char a[] = "Edit$"; + static char b[] = "Edit>"; + + return (continuation ? b : a); +} + +static void +sig(int i) +{ + + (void) fprintf(stderr, "Got signal %d.\n", i); + el_reset(el); +} + +static unsigned char +complete(EditLine *el, int ch) +{ + DIR *dd = opendir("."); + struct dirent *dp; + const char* ptr; + const LineInfo *lf = el_line(el); + int len; + + /* + * Find the last word + */ + for (ptr = lf->cursor - 1; !isspace(*ptr) && ptr > lf->buffer; ptr--) + continue; + len = lf->cursor - ++ptr; + + for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) { + if (len > strlen(dp->d_name)) + continue; + if (strncmp(dp->d_name, ptr, len) == 0) { + closedir(dd); + if (el_insertstr(el, &dp->d_name[len]) == -1) + return (CC_ERROR); + else + return (CC_REFRESH); + } + } + + closedir(dd); + return (CC_ERROR); +} + +int +main(int argc, char *argv[]) +{ + int num; + const char *buf; + Tokenizer *tok; +#if 0 + int lastevent = 0; +#endif + int ncontinuation; + History *hist; + HistEvent ev; + + (void) signal(SIGINT, sig); + (void) signal(SIGQUIT, sig); + (void) signal(SIGHUP, sig); + (void) signal(SIGTERM, sig); + + hist = history_init(); /* Init the builtin history */ + /* Remember 100 events */ + history(hist, &ev, H_SETSIZE, 100); + + tok = tok_init(NULL); /* Initialize the tokenizer */ + + /* Initialize editline */ + el = el_init(*argv, stdin, stdout, stderr); + + el_set(el, EL_EDITOR, "vi"); /* Default editor is vi */ + el_set(el, EL_SIGNAL, 1); /* Handle signals gracefully */ + el_set(el, EL_PROMPT, prompt); /* Set the prompt function */ + + /* Tell editline to use this history interface */ + el_set(el, EL_HIST, history, hist); + + /* Add a user-defined function */ + el_set(el, EL_ADDFN, "ed-complete", "Complete argument", complete); + + /* Bind tab to it */ + el_set(el, EL_BIND, "^I", "ed-complete", NULL); + + /* + * Bind j, k in vi command mode to previous and next line, instead + * of previous and next history. + */ + el_set(el, EL_BIND, "-a", "k", "ed-prev-line", NULL); + el_set(el, EL_BIND, "-a", "j", "ed-next-line", NULL); + + /* + * Source the user's defaults file. + */ + el_source(el, NULL); + + while ((buf = el_gets(el, &num)) != NULL && num != 0) { + int ac; + const char **av; +#ifdef DEBUG + (void) fprintf(stderr, "got %d %s", num, buf); +#endif + if (!continuation && num == 1) + continue; + + ncontinuation = tok_line(tok, buf, &ac, &av) > 0; +#if 0 + if (continuation) { + /* + * Append to the right event in case the user + * moved around in history. + */ + if (history(hist, &ev, H_SET, lastevent) == -1) + err(1, "%d: %s\n", lastevent, ev.str); + history(hist, &ev, H_ADD , buf); + } else { + history(hist, &ev, H_ENTER, buf); + lastevent = ev.num; + } +#else + /* Simpler */ + history(hist, &ev, continuation ? H_APPEND : H_ENTER, buf); +#endif + + continuation = ncontinuation; + ncontinuation = 0; + + if (strcmp(av[0], "history") == 0) { + int rv; + + switch (ac) { + case 1: + for (rv = history(hist, &ev, H_LAST); rv != -1; + rv = history(hist, &ev, H_PREV)) + (void) fprintf(stdout, "%4d %s", + ev.num, ev.str); + break; + + case 2: + if (strcmp(av[1], "clear") == 0) + history(hist, &ev, H_CLEAR); + else + goto badhist; + break; + + case 3: + if (strcmp(av[1], "load") == 0) + history(hist, &ev, H_LOAD, av[2]); + else if (strcmp(av[1], "save") == 0) + history(hist, &ev, H_SAVE, av[2]); + break; + + badhist: + default: + (void) fprintf(stderr, + "Bad history arguments\n"); + break; + } + } else if (el_parse(el, ac, av) == -1) { + switch (fork()) { + case 0: + execvp(av[0], (char *const *)av); + perror(av[0]); + _exit(1); + /*NOTREACHED*/ + break; + + case -1: + perror("fork"); + break; + + default: + if (wait(&num) == -1) + perror("wait"); + (void) fprintf(stderr, "Exit %x\n", num); + break; + } + } + + tok_reset(tok); + } + + el_end(el); + tok_end(tok); + history_end(hist); + + return (0); +} diff --git a/main/editline/chared.c b/main/editline/chared.c new file mode 100644 index 000000000..8eaeb3b54 --- /dev/null +++ b/main/editline/chared.c @@ -0,0 +1,695 @@ +/* $NetBSD: chared.c,v 1.15 2002/03/18 16:00:50 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)chared.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: chared.c,v 1.15 2002/03/18 16:00:50 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * chared.c: Character editor utilities + */ +#include +#include "el.h" + +/* value to leave unused in line buffer */ +#define EL_LEAVE 2 + +/* cv_undo(): + * Handle state for the vi undo command + */ +protected void +cv_undo(EditLine *el,int action, size_t size, char *ptr) +{ + c_undo_t *vu = &el->el_chared.c_undo; + vu->action = action; + vu->ptr = ptr; + vu->isize = size; + (void) memcpy(vu->buf, vu->ptr, size); +#ifdef DEBUG_UNDO + (void) fprintf(el->el_errfile, "Undo buffer \"%s\" size = +%d -%d\n", + vu->ptr, vu->isize, vu->dsize); +#endif +} + + +/* c_insert(): + * Insert num characters + */ +protected void +c_insert(EditLine *el, int num) +{ + char *cp; + + if (el->el_line.lastchar + num >= el->el_line.limit) + return; /* can't go past end of buffer */ + + if (el->el_line.cursor < el->el_line.lastchar) { + /* if I must move chars */ + for (cp = el->el_line.lastchar; cp >= el->el_line.cursor; cp--) + cp[num] = *cp; + } + el->el_line.lastchar += num; +} + + +/* c_delafter(): + * Delete num characters after the cursor + */ +protected void +c_delafter(EditLine *el, int num) +{ + + if (el->el_line.cursor + num > el->el_line.lastchar) + num = el->el_line.lastchar - el->el_line.cursor; + + if (num > 0) { + char *cp; + + if (el->el_map.current != el->el_map.emacs) + cv_undo(el, INSERT, (size_t)num, el->el_line.cursor); + + for (cp = el->el_line.cursor; cp <= el->el_line.lastchar; cp++) + *cp = cp[num]; + + el->el_line.lastchar -= num; + } +} + + +/* c_delbefore(): + * Delete num characters before the cursor + */ +protected void +c_delbefore(EditLine *el, int num) +{ + + if (el->el_line.cursor - num < el->el_line.buffer) + num = el->el_line.cursor - el->el_line.buffer; + + if (num > 0) { + char *cp; + + if (el->el_map.current != el->el_map.emacs) + cv_undo(el, INSERT, (size_t)num, + el->el_line.cursor - num); + + for (cp = el->el_line.cursor - num; + cp <= el->el_line.lastchar; + cp++) + *cp = cp[num]; + + el->el_line.lastchar -= num; + } +} + + +/* ce__isword(): + * Return if p is part of a word according to emacs + */ +protected int +ce__isword(int p) +{ + return (isalpha(p) || isdigit(p) || strchr("*?_-.[]~=", p) != NULL); +} + + +/* cv__isword(): + * Return if p is part of a word according to vi + */ +protected int +cv__isword(int p) +{ + return (!isspace(p)); +} + + +/* c__prev_word(): + * Find the previous word + */ +protected char * +c__prev_word(char *p, char *low, int n, int (*wtest)(int)) +{ + p--; + + while (n--) { + while ((p >= low) && !(*wtest)((unsigned char) *p)) + p--; + while ((p >= low) && (*wtest)((unsigned char) *p)) + p--; + } + + /* cp now points to one character before the word */ + p++; + if (p < low) + p = low; + /* cp now points where we want it */ + return (p); +} + + +/* c__next_word(): + * Find the next word + */ +protected char * +c__next_word(char *p, char *high, int n, int (*wtest)(int)) +{ + while (n--) { + while ((p < high) && !(*wtest)((unsigned char) *p)) + p++; + while ((p < high) && (*wtest)((unsigned char) *p)) + p++; + } + if (p > high) + p = high; + /* p now points where we want it */ + return (p); +} + +/* cv_next_word(): + * Find the next word vi style + */ +protected char * +cv_next_word(EditLine *el, char *p, char *high, int n, int (*wtest)(int)) +{ + int test; + + while (n--) { + test = (*wtest)((unsigned char) *p); + while ((p < high) && (*wtest)((unsigned char) *p) == test) + p++; + /* + * vi historically deletes with cw only the word preserving the + * trailing whitespace! This is not what 'w' does.. + */ + if (el->el_chared.c_vcmd.action != (DELETE|INSERT)) + while ((p < high) && isspace((unsigned char) *p)) + p++; + } + + /* p now points where we want it */ + if (p > high) + return (high); + else + return (p); +} + + +/* cv_prev_word(): + * Find the previous word vi style + */ +protected char * +cv_prev_word(EditLine *el, char *p, char *low, int n, int (*wtest)(int)) +{ + int test; + + while (n--) { + p--; + /* + * vi historically deletes with cb only the word preserving the + * leading whitespace! This is not what 'b' does.. + */ + if (el->el_chared.c_vcmd.action != (DELETE|INSERT)) + while ((p > low) && isspace((unsigned char) *p)) + p--; + test = (*wtest)((unsigned char) *p); + while ((p >= low) && (*wtest)((unsigned char) *p) == test) + p--; + p++; + while (isspace((unsigned char) *p)) + p++; + } + + /* p now points where we want it */ + if (p < low) + return (low); + else + return (p); +} + + +#ifdef notdef +/* c__number(): + * Ignore character p points to, return number appearing after that. + * A '$' by itself means a big number; "$-" is for negative; '^' means 1. + * Return p pointing to last char used. + */ +protected char * +c__number( + char *p, /* character position */ + int *num, /* Return value */ + int dval) /* dval is the number to subtract from like $-3 */ +{ + int i; + int sign = 1; + + if (*++p == '^') { + *num = 1; + return (p); + } + if (*p == '$') { + if (*++p != '-') { + *num = 0x7fffffff; /* Handle $ */ + return (--p); + } + sign = -1; /* Handle $- */ + ++p; + } + for (i = 0; isdigit((unsigned char) *p); i = 10 * i + *p++ - '0') + continue; + *num = (sign < 0 ? dval - i : i); + return (--p); +} +#endif + +/* cv_delfini(): + * Finish vi delete action + */ +protected void +cv_delfini(EditLine *el) +{ + int size; + int oaction; + + if (el->el_chared.c_vcmd.action & INSERT) + el->el_map.current = el->el_map.key; + + oaction = el->el_chared.c_vcmd.action; + el->el_chared.c_vcmd.action = NOP; + + if (el->el_chared.c_vcmd.pos == 0) + return; + + + if (el->el_line.cursor > el->el_chared.c_vcmd.pos) { + size = (int) (el->el_line.cursor - el->el_chared.c_vcmd.pos); + c_delbefore(el, size); + el->el_line.cursor = el->el_chared.c_vcmd.pos; + re_refresh_cursor(el); + } else if (el->el_line.cursor < el->el_chared.c_vcmd.pos) { + size = (int)(el->el_chared.c_vcmd.pos - el->el_line.cursor); + c_delafter(el, size); + } else { + size = 1; + c_delafter(el, size); + } + switch (oaction) { + case DELETE|INSERT: + el->el_chared.c_undo.action = DELETE|INSERT; + break; + case DELETE: + el->el_chared.c_undo.action = INSERT; + break; + case NOP: + case INSERT: + default: + EL_ABORT((el->el_errfile, "Bad oaction %d\n", oaction)); + break; + } + + + el->el_chared.c_undo.ptr = el->el_line.cursor; + el->el_chared.c_undo.dsize = size; +} + + +#ifdef notdef +/* ce__endword(): + * Go to the end of this word according to emacs + */ +protected char * +ce__endword(char *p, char *high, int n) +{ + p++; + + while (n--) { + while ((p < high) && isspace((unsigned char) *p)) + p++; + while ((p < high) && !isspace((unsigned char) *p)) + p++; + } + + p--; + return (p); +} +#endif + + +/* cv__endword(): + * Go to the end of this word according to vi + */ +protected char * +cv__endword(char *p, char *high, int n) +{ + p++; + + while (n--) { + while ((p < high) && isspace((unsigned char) *p)) + p++; + + if (isalnum((unsigned char) *p)) + while ((p < high) && isalnum((unsigned char) *p)) + p++; + else + while ((p < high) && !(isspace((unsigned char) *p) || + isalnum((unsigned char) *p))) + p++; + } + p--; + return (p); +} + +/* ch_init(): + * Initialize the character editor + */ +protected int +ch_init(EditLine *el) +{ + el->el_line.buffer = (char *) el_malloc(EL_BUFSIZ); + if (el->el_line.buffer == NULL) + return (-1); + + (void) memset(el->el_line.buffer, 0, EL_BUFSIZ); + el->el_line.cursor = el->el_line.buffer; + el->el_line.lastchar = el->el_line.buffer; + el->el_line.limit = &el->el_line.buffer[EL_BUFSIZ - 2]; + + el->el_chared.c_undo.buf = (char *) el_malloc(EL_BUFSIZ); + if (el->el_chared.c_undo.buf == NULL) + return (-1); + (void) memset(el->el_chared.c_undo.buf, 0, EL_BUFSIZ); + el->el_chared.c_undo.action = NOP; + el->el_chared.c_undo.isize = 0; + el->el_chared.c_undo.dsize = 0; + el->el_chared.c_undo.ptr = el->el_line.buffer; + + el->el_chared.c_vcmd.action = NOP; + el->el_chared.c_vcmd.pos = el->el_line.buffer; + el->el_chared.c_vcmd.ins = el->el_line.buffer; + + el->el_chared.c_kill.buf = (char *) el_malloc(EL_BUFSIZ); + if (el->el_chared.c_kill.buf == NULL) + return (-1); + (void) memset(el->el_chared.c_kill.buf, 0, EL_BUFSIZ); + el->el_chared.c_kill.mark = el->el_line.buffer; + el->el_chared.c_kill.last = el->el_chared.c_kill.buf; + + el->el_map.current = el->el_map.key; + + el->el_state.inputmode = MODE_INSERT; /* XXX: save a default */ + el->el_state.doingarg = 0; + el->el_state.metanext = 0; + el->el_state.argument = 1; + el->el_state.lastcmd = ED_UNASSIGNED; + + el->el_chared.c_macro.nline = NULL; + el->el_chared.c_macro.level = -1; + el->el_chared.c_macro.macro = (char **) el_malloc(EL_MAXMACRO * + sizeof(char *)); + if (el->el_chared.c_macro.macro == NULL) + return (-1); + return (0); +} + +/* ch_reset(): + * Reset the character editor + */ +protected void +ch_reset(EditLine *el) +{ + el->el_line.cursor = el->el_line.buffer; + el->el_line.lastchar = el->el_line.buffer; + + el->el_chared.c_undo.action = NOP; + el->el_chared.c_undo.isize = 0; + el->el_chared.c_undo.dsize = 0; + el->el_chared.c_undo.ptr = el->el_line.buffer; + + el->el_chared.c_vcmd.action = NOP; + el->el_chared.c_vcmd.pos = el->el_line.buffer; + el->el_chared.c_vcmd.ins = el->el_line.buffer; + + el->el_chared.c_kill.mark = el->el_line.buffer; + + el->el_map.current = el->el_map.key; + + el->el_state.inputmode = MODE_INSERT; /* XXX: save a default */ + el->el_state.doingarg = 0; + el->el_state.metanext = 0; + el->el_state.argument = 1; + el->el_state.lastcmd = ED_UNASSIGNED; + + el->el_chared.c_macro.level = -1; + + el->el_history.eventno = 0; +} + +/* ch_enlargebufs(): + * Enlarge line buffer to be able to hold twice as much characters. + * Returns 1 if successful, 0 if not. + */ +protected int +ch_enlargebufs(el, addlen) + EditLine *el; + size_t addlen; +{ + size_t sz, newsz; + char *newbuffer, *oldbuf, *oldkbuf; + + sz = el->el_line.limit - el->el_line.buffer + EL_LEAVE; + newsz = sz * 2; + /* + * If newly required length is longer than current buffer, we need + * to make the buffer big enough to hold both old and new stuff. + */ + if (addlen > sz) { + while(newsz - sz < addlen) + newsz *= 2; + } + + /* + * Reallocate line buffer. + */ + newbuffer = el_realloc(el->el_line.buffer, newsz); + if (!newbuffer) + return 0; + + /* zero the newly added memory, leave old data in */ + (void) memset(&newbuffer[sz], 0, newsz - sz); + + oldbuf = el->el_line.buffer; + + el->el_line.buffer = newbuffer; + el->el_line.cursor = newbuffer + (el->el_line.cursor - oldbuf); + el->el_line.lastchar = newbuffer + (el->el_line.lastchar - oldbuf); + el->el_line.limit = &newbuffer[newsz - EL_LEAVE]; + + /* + * Reallocate kill buffer. + */ + newbuffer = el_realloc(el->el_chared.c_kill.buf, newsz); + if (!newbuffer) + return 0; + + /* zero the newly added memory, leave old data in */ + (void) memset(&newbuffer[sz], 0, newsz - sz); + + oldkbuf = el->el_chared.c_kill.buf; + + el->el_chared.c_kill.buf = newbuffer; + el->el_chared.c_kill.last = newbuffer + + (el->el_chared.c_kill.last - oldkbuf); + el->el_chared.c_kill.mark = el->el_line.buffer + + (el->el_chared.c_kill.mark - oldbuf); + + /* + * Reallocate undo buffer. + */ + newbuffer = el_realloc(el->el_chared.c_undo.buf, newsz); + if (!newbuffer) + return 0; + + /* zero the newly added memory, leave old data in */ + (void) memset(&newbuffer[sz], 0, newsz - sz); + + el->el_chared.c_undo.ptr = el->el_line.buffer + + (el->el_chared.c_undo.ptr - oldbuf); + el->el_chared.c_undo.buf = newbuffer; + + if (!hist_enlargebuf(el, sz, newsz)) + return 0; + + return 1; +} + +/* ch_end(): + * Free the data structures used by the editor + */ +protected void +ch_end(EditLine *el) +{ + el_free((ptr_t) el->el_line.buffer); + el->el_line.buffer = NULL; + el->el_line.limit = NULL; + el_free((ptr_t) el->el_chared.c_undo.buf); + el->el_chared.c_undo.buf = NULL; + el_free((ptr_t) el->el_chared.c_kill.buf); + el->el_chared.c_kill.buf = NULL; + el_free((ptr_t) el->el_chared.c_macro.macro); + el->el_chared.c_macro.macro = NULL; + ch_reset(el); +} + + +/* el_insertstr(): + * Insert string at cursorI + */ +public int +el_insertstr(EditLine *el, const char *s) +{ + size_t len; + + if ((len = strlen(s)) == 0) + return (-1); + if (el->el_line.lastchar + len >= el->el_line.limit) { + if (!ch_enlargebufs(el, len)) + return (-1); + } + + c_insert(el, (int)len); + while (*s) + *el->el_line.cursor++ = *s++; + return (0); +} + + +/* el_deletestr(): + * Delete num characters before the cursor + */ +public void +el_deletestr(EditLine *el, int n) +{ + if (n <= 0) + return; + + if (el->el_line.cursor < &el->el_line.buffer[n]) + return; + + c_delbefore(el, n); /* delete before dot */ + el->el_line.cursor -= n; + if (el->el_line.cursor < el->el_line.buffer) + el->el_line.cursor = el->el_line.buffer; +} + +/* c_gets(): + * Get a string + */ +protected int +c_gets(EditLine *el, char *buf) +{ + char ch; + int len = 0; + + for (ch = 0; ch == 0;) { + if (el_getc(el, &ch) != 1) + return (ed_end_of_file(el, 0)); + switch (ch) { + case 0010: /* Delete and backspace */ + case 0177: + if (len > 1) { + *el->el_line.cursor-- = '\0'; + el->el_line.lastchar = el->el_line.cursor; + buf[len--] = '\0'; + } else { + el->el_line.buffer[0] = '\0'; + el->el_line.lastchar = el->el_line.buffer; + el->el_line.cursor = el->el_line.buffer; + return (CC_REFRESH); + } + re_refresh(el); + ch = 0; + break; + + case 0033: /* ESC */ + case '\r': /* Newline */ + case '\n': + break; + + default: + if (len >= EL_BUFSIZ) + term_beep(el); + else { + buf[len++] = ch; + *el->el_line.cursor++ = ch; + el->el_line.lastchar = el->el_line.cursor; + } + re_refresh(el); + ch = 0; + break; + } + } + buf[len] = ch; + return (len); +} + + +/* c_hpos(): + * Return the current horizontal position of the cursor + */ +protected int +c_hpos(EditLine *el) +{ + char *ptr; + + /* + * Find how many characters till the beginning of this line. + */ + if (el->el_line.cursor == el->el_line.buffer) + return (0); + else { + for (ptr = el->el_line.cursor - 1; + ptr >= el->el_line.buffer && *ptr != '\n'; + ptr--) + continue; + return (el->el_line.cursor - ptr - 1); + } +} diff --git a/main/editline/chared.h b/main/editline/chared.h new file mode 100644 index 000000000..403eca011 --- /dev/null +++ b/main/editline/chared.h @@ -0,0 +1,159 @@ +/* $NetBSD: chared.h,v 1.8 2002/03/18 16:00:51 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)chared.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.chared.h: Character editor interface + */ +#ifndef _h_el_chared +#define _h_el_chared + +#include +#include + +#include "histedit.h" + +#define EL_MAXMACRO 10 + +/* + * This is a issue of basic "vi" look-and-feel. Defining VI_MOVE works + * like real vi: i.e. the transition from command<->insert modes moves + * the cursor. + * + * On the other hand we really don't want to move the cursor, because + * all the editing commands don't include the character under the cursor. + * Probably the best fix is to make all the editing commands aware of + * this fact. + */ +#define VI_MOVE + + +typedef struct c_macro_t { + int level; + char **macro; + char *nline; +} c_macro_t; + +/* + * Undo information for both vi and emacs + */ +typedef struct c_undo_t { + int action; + size_t isize; + size_t dsize; + char *ptr; + char *buf; +} c_undo_t; + +/* + * Current action information for vi + */ +typedef struct c_vcmd_t { + int action; + char *pos; + char *ins; +} c_vcmd_t; + +/* + * Kill buffer for emacs + */ +typedef struct c_kill_t { + char *buf; + char *last; + char *mark; +} c_kill_t; + +/* + * Note that we use both data structures because the user can bind + * commands from both editors! + */ +typedef struct el_chared_t { + c_undo_t c_undo; + c_kill_t c_kill; + c_vcmd_t c_vcmd; + c_macro_t c_macro; +} el_chared_t; + + +#define STReof "^D\b\b" +#define STRQQ "\"\"" + +#define isglob(a) (strchr("*[]?", (a)) != NULL) +#define isword(a) (isprint(a)) + +#define NOP 0x00 +#define DELETE 0x01 +#define INSERT 0x02 +#define CHANGE 0x04 + +#define CHAR_FWD 0 +#define CHAR_BACK 1 + +#define MODE_INSERT 0 +#define MODE_REPLACE 1 +#define MODE_REPLACE_1 2 + +#include "common.h" +#include "vi.h" +#include "emacs.h" +#include "search.h" +#include "fcns.h" + + +protected int cv__isword(int); +protected void cv_delfini(EditLine *); +protected char *cv__endword(char *, char *, int); +protected int ce__isword(int); +protected void cv_undo(EditLine *, int, size_t, char *); +protected char *cv_next_word(EditLine*, char *, char *, int, int (*)(int)); +protected char *cv_prev_word(EditLine*, char *, char *, int, int (*)(int)); +protected char *c__next_word(char *, char *, int, int (*)(int)); +protected char *c__prev_word(char *, char *, int, int (*)(int)); +protected void c_insert(EditLine *, int); +protected void c_delbefore(EditLine *, int); +protected void c_delafter(EditLine *, int); +protected int c_gets(EditLine *, char *); +protected int c_hpos(EditLine *); + +protected int ch_init(EditLine *); +protected void ch_reset(EditLine *); +protected int ch_enlargebufs(EditLine *, size_t); +protected void ch_end(EditLine *); + +#endif /* _h_el_chared */ diff --git a/main/editline/common.c b/main/editline/common.c new file mode 100644 index 000000000..c831e79a3 --- /dev/null +++ b/main/editline/common.c @@ -0,0 +1,951 @@ +/* $NetBSD: common.c,v 1.11 2002/03/18 16:00:51 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)common.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: common.c,v 1.11 2002/03/18 16:00:51 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * common.c: Common Editor functions + */ +#include "el.h" + +/* ed_end_of_file(): + * Indicate end of file + * [^D] + */ +protected el_action_t +/*ARGSUSED*/ +ed_end_of_file(EditLine *el, int c) +{ + + re_goto_bottom(el); + *el->el_line.lastchar = '\0'; + return (CC_EOF); +} + + +/* ed_insert(): + * Add character to the line + * Insert a character [bound to all insert keys] + */ +protected el_action_t +ed_insert(EditLine *el, int c) +{ + int i; + + if (c == '\0') + return (CC_ERROR); + + if (el->el_line.lastchar + el->el_state.argument >= + el->el_line.limit) { + /* end of buffer space, try to allocate more */ + if (!ch_enlargebufs(el, (size_t) el->el_state.argument)) + return CC_ERROR; /* error allocating more */ + } + + if (el->el_state.argument == 1) { + if (el->el_state.inputmode != MODE_INSERT) { + el->el_chared.c_undo.buf[el->el_chared.c_undo.isize++] = + *el->el_line.cursor; + el->el_chared.c_undo.buf[el->el_chared.c_undo.isize] = + '\0'; + c_delafter(el, 1); + } + c_insert(el, 1); + + *el->el_line.cursor++ = c; + el->el_state.doingarg = 0; /* just in case */ + re_fastaddc(el); /* fast refresh for one char. */ + } else { + if (el->el_state.inputmode != MODE_INSERT) { + for (i = 0; i < el->el_state.argument; i++) + el->el_chared.c_undo.buf[el->el_chared.c_undo.isize++] = + el->el_line.cursor[i]; + + el->el_chared.c_undo.buf[el->el_chared.c_undo.isize] = + '\0'; + c_delafter(el, el->el_state.argument); + } + c_insert(el, el->el_state.argument); + + while (el->el_state.argument--) + *el->el_line.cursor++ = c; + re_refresh(el); + } + + if (el->el_state.inputmode == MODE_REPLACE_1) + (void) vi_command_mode(el, 0); + + return (CC_NORM); +} + + +/* ed_delete_prev_word(): + * Delete from beginning of current word to cursor + * [M-^?] [^W] + */ +protected el_action_t +/*ARGSUSED*/ +ed_delete_prev_word(EditLine *el, int c) +{ + char *cp, *p, *kp; + + if (el->el_line.cursor == el->el_line.buffer) + return (CC_ERROR); + + cp = c__prev_word(el->el_line.cursor, el->el_line.buffer, + el->el_state.argument, ce__isword); + + for (p = cp, kp = el->el_chared.c_kill.buf; p < el->el_line.cursor; p++) + *kp++ = *p; + el->el_chared.c_kill.last = kp; + + c_delbefore(el, el->el_line.cursor - cp); /* delete before dot */ + el->el_line.cursor = cp; + if (el->el_line.cursor < el->el_line.buffer) + el->el_line.cursor = el->el_line.buffer; /* bounds check */ + return (CC_REFRESH); +} + + +/* ed_delete_next_char(): + * Delete character under cursor + * [^D] [x] + */ +protected el_action_t +/*ARGSUSED*/ +ed_delete_next_char(EditLine *el, int c) +{ +#ifdef notdef /* XXX */ +#define EL el->el_line + (void) fprintf(el->el_errlfile, + "\nD(b: %x(%s) c: %x(%s) last: %x(%s) limit: %x(%s)\n", + EL.buffer, EL.buffer, EL.cursor, EL.cursor, EL.lastchar, + EL.lastchar, EL.limit, EL.limit); +#endif + if (el->el_line.cursor == el->el_line.lastchar) { + /* if I'm at the end */ + if (el->el_map.type == MAP_VI) { + if (el->el_line.cursor == el->el_line.buffer) { + /* if I'm also at the beginning */ +#ifdef KSHVI + return (CC_ERROR); +#else + term_overwrite(el, STReof, 4); + /* then do a EOF */ + term__flush(); + return (CC_EOF); +#endif + } else { +#ifdef KSHVI + el->el_line.cursor--; +#else + return (CC_ERROR); +#endif + } + } else { + if (el->el_line.cursor != el->el_line.buffer) + el->el_line.cursor--; + else + return (CC_ERROR); + } + } + c_delafter(el, el->el_state.argument); /* delete after dot */ + if (el->el_line.cursor >= el->el_line.lastchar && + el->el_line.cursor > el->el_line.buffer) + /* bounds check */ + el->el_line.cursor = el->el_line.lastchar - 1; + return (CC_REFRESH); +} + + +/* ed_kill_line(): + * Cut to the end of line + * [^K] [^K] + */ +protected el_action_t +/*ARGSUSED*/ +ed_kill_line(EditLine *el, int c) +{ + char *kp, *cp; + + cp = el->el_line.cursor; + kp = el->el_chared.c_kill.buf; + while (cp < el->el_line.lastchar) + *kp++ = *cp++; /* copy it */ + el->el_chared.c_kill.last = kp; + /* zap! -- delete to end */ + el->el_line.lastchar = el->el_line.cursor; + return (CC_REFRESH); +} + + +/* ed_move_to_end(): + * Move cursor to the end of line + * [^E] [^E] + */ +protected el_action_t +/*ARGSUSED*/ +ed_move_to_end(EditLine *el, int c) +{ + + el->el_line.cursor = el->el_line.lastchar; + if (el->el_map.type == MAP_VI) { +#ifdef VI_MOVE + el->el_line.cursor--; +#endif + if (el->el_chared.c_vcmd.action & DELETE) { + cv_delfini(el); + return (CC_REFRESH); + } + } + return (CC_CURSOR); +} + + +/* ed_move_to_beg(): + * Move cursor to the beginning of line + * [^A] [^A] + */ +protected el_action_t +/*ARGSUSED*/ +ed_move_to_beg(EditLine *el, int c) +{ + + el->el_line.cursor = el->el_line.buffer; + + if (el->el_map.type == MAP_VI) { + /* We want FIRST non space character */ + while (isspace((unsigned char) *el->el_line.cursor)) + el->el_line.cursor++; + if (el->el_chared.c_vcmd.action & DELETE) { + cv_delfini(el); + return (CC_REFRESH); + } + } + return (CC_CURSOR); +} + + +/* ed_transpose_chars(): + * Exchange the character to the left of the cursor with the one under it + * [^T] [^T] + */ +protected el_action_t +ed_transpose_chars(EditLine *el, int c) +{ + + if (el->el_line.cursor < el->el_line.lastchar) { + if (el->el_line.lastchar <= &el->el_line.buffer[1]) + return (CC_ERROR); + else + el->el_line.cursor++; + } + if (el->el_line.cursor > &el->el_line.buffer[1]) { + /* must have at least two chars entered */ + c = el->el_line.cursor[-2]; + el->el_line.cursor[-2] = el->el_line.cursor[-1]; + el->el_line.cursor[-1] = c; + return (CC_REFRESH); + } else + return (CC_ERROR); +} + + +/* ed_next_char(): + * Move to the right one character + * [^F] [^F] + */ +protected el_action_t +/*ARGSUSED*/ +ed_next_char(EditLine *el, int c) +{ + + if (el->el_line.cursor >= el->el_line.lastchar) + return (CC_ERROR); + + el->el_line.cursor += el->el_state.argument; + if (el->el_line.cursor > el->el_line.lastchar) + el->el_line.cursor = el->el_line.lastchar; + + if (el->el_map.type == MAP_VI) + if (el->el_chared.c_vcmd.action & DELETE) { + cv_delfini(el); + return (CC_REFRESH); + } + return (CC_CURSOR); +} + + +/* ed_prev_word(): + * Move to the beginning of the current word + * [M-b] [b] + */ +protected el_action_t +/*ARGSUSED*/ +ed_prev_word(EditLine *el, int c) +{ + + if (el->el_line.cursor == el->el_line.buffer) + return (CC_ERROR); + + el->el_line.cursor = c__prev_word(el->el_line.cursor, + el->el_line.buffer, + el->el_state.argument, + ce__isword); + + if (el->el_map.type == MAP_VI) + if (el->el_chared.c_vcmd.action & DELETE) { + cv_delfini(el); + return (CC_REFRESH); + } + return (CC_CURSOR); +} + + +/* ed_prev_char(): + * Move to the left one character + * [^B] [^B] + */ +protected el_action_t +/*ARGSUSED*/ +ed_prev_char(EditLine *el, int c) +{ + + if (el->el_line.cursor > el->el_line.buffer) { + el->el_line.cursor -= el->el_state.argument; + if (el->el_line.cursor < el->el_line.buffer) + el->el_line.cursor = el->el_line.buffer; + + if (el->el_map.type == MAP_VI) + if (el->el_chared.c_vcmd.action & DELETE) { + cv_delfini(el); + return (CC_REFRESH); + } + return (CC_CURSOR); + } else + return (CC_ERROR); +} + + +/* ed_quoted_insert(): + * Add the next character typed verbatim + * [^V] [^V] + */ +protected el_action_t +ed_quoted_insert(EditLine *el, int c) +{ + int num; + char tc; + + tty_quotemode(el); + num = el_getc(el, &tc); + c = (unsigned char) tc; + tty_noquotemode(el); + if (num == 1) + return (ed_insert(el, c)); + else + return (ed_end_of_file(el, 0)); +} + + +/* ed_digit(): + * Adds to argument or enters a digit + */ +protected el_action_t +ed_digit(EditLine *el, int c) +{ + + if (!isdigit(c)) + return (CC_ERROR); + + if (el->el_state.doingarg) { + /* if doing an arg, add this in... */ + if (el->el_state.lastcmd == EM_UNIVERSAL_ARGUMENT) + el->el_state.argument = c - '0'; + else { + if (el->el_state.argument > 1000000) + return (CC_ERROR); + el->el_state.argument = + (el->el_state.argument * 10) + (c - '0'); + } + return (CC_ARGHACK); + } else { + if (el->el_line.lastchar + 1 >= el->el_line.limit) { + if (!ch_enlargebufs(el, 1)) + return (CC_ERROR); + } + + if (el->el_state.inputmode != MODE_INSERT) { + el->el_chared.c_undo.buf[el->el_chared.c_undo.isize++] = + *el->el_line.cursor; + el->el_chared.c_undo.buf[el->el_chared.c_undo.isize] = + '\0'; + c_delafter(el, 1); + } + c_insert(el, 1); + *el->el_line.cursor++ = c; + el->el_state.doingarg = 0; + re_fastaddc(el); + } + return (CC_NORM); +} + + +/* ed_argument_digit(): + * Digit that starts argument + * For ESC-n + */ +protected el_action_t +ed_argument_digit(EditLine *el, int c) +{ + + if (!isdigit(c)) + return (CC_ERROR); + + if (el->el_state.doingarg) { + if (el->el_state.argument > 1000000) + return (CC_ERROR); + el->el_state.argument = (el->el_state.argument * 10) + + (c - '0'); + } else { /* else starting an argument */ + el->el_state.argument = c - '0'; + el->el_state.doingarg = 1; + } + return (CC_ARGHACK); +} + + +/* ed_unassigned(): + * Indicates unbound character + * Bound to keys that are not assigned + */ +protected el_action_t +/*ARGSUSED*/ +ed_unassigned(EditLine *el, int c) +{ + + term_beep(el); + term__flush(); + return (CC_NORM); +} + + +/** + ** TTY key handling. + **/ + +/* ed_tty_sigint(): + * Tty interrupt character + * [^C] + */ +protected el_action_t +/*ARGSUSED*/ +ed_tty_sigint(EditLine *el, int c) +{ + + return (CC_NORM); +} + + +/* ed_tty_dsusp(): + * Tty delayed suspend character + * [^Y] + */ +protected el_action_t +/*ARGSUSED*/ +ed_tty_dsusp(EditLine *el, int c) +{ + + return (CC_NORM); +} + + +/* ed_tty_flush_output(): + * Tty flush output characters + * [^O] + */ +protected el_action_t +/*ARGSUSED*/ +ed_tty_flush_output(EditLine *el, int c) +{ + + return (CC_NORM); +} + + +/* ed_tty_sigquit(): + * Tty quit character + * [^\] + */ +protected el_action_t +/*ARGSUSED*/ +ed_tty_sigquit(EditLine *el, int c) +{ + + return (CC_NORM); +} + + +/* ed_tty_sigtstp(): + * Tty suspend character + * [^Z] + */ +protected el_action_t +/*ARGSUSED*/ +ed_tty_sigtstp(EditLine *el, int c) +{ + + return (CC_NORM); +} + + +/* ed_tty_stop_output(): + * Tty disallow output characters + * [^S] + */ +protected el_action_t +/*ARGSUSED*/ +ed_tty_stop_output(EditLine *el, int c) +{ + + return (CC_NORM); +} + + +/* ed_tty_start_output(): + * Tty allow output characters + * [^Q] + */ +protected el_action_t +/*ARGSUSED*/ +ed_tty_start_output(EditLine *el, int c) +{ + + return (CC_NORM); +} + + +/* ed_newline(): + * Execute command + * [^J] + */ +protected el_action_t +/*ARGSUSED*/ +ed_newline(EditLine *el, int c) +{ + + re_goto_bottom(el); + *el->el_line.lastchar++ = '\n'; + *el->el_line.lastchar = '\0'; + if (el->el_map.type == MAP_VI) + el->el_chared.c_vcmd.ins = el->el_line.buffer; + return (CC_NEWLINE); +} + + +/* ed_delete_prev_char(): + * Delete the character to the left of the cursor + * [^?] + */ +protected el_action_t +/*ARGSUSED*/ +ed_delete_prev_char(EditLine *el, int c) +{ + + if (el->el_line.cursor <= el->el_line.buffer) + return (CC_ERROR); + + c_delbefore(el, el->el_state.argument); + el->el_line.cursor -= el->el_state.argument; + if (el->el_line.cursor < el->el_line.buffer) + el->el_line.cursor = el->el_line.buffer; + return (CC_REFRESH); +} + + +/* ed_clear_screen(): + * Clear screen leaving current line at the top + * [^L] + */ +protected el_action_t +/*ARGSUSED*/ +ed_clear_screen(EditLine *el, int c) +{ + + term_clear_screen(el); /* clear the whole real screen */ + re_clear_display(el); /* reset everything */ + return (CC_REFRESH); +} + + +/* ed_redisplay(): + * Redisplay everything + * ^R + */ +protected el_action_t +/*ARGSUSED*/ +ed_redisplay(EditLine *el, int c) +{ + + return (CC_REDISPLAY); +} + + +/* ed_start_over(): + * Erase current line and start from scratch + * [^G] + */ +protected el_action_t +/*ARGSUSED*/ +ed_start_over(EditLine *el, int c) +{ + + ch_reset(el); + return (CC_REFRESH); +} + + +/* ed_sequence_lead_in(): + * First character in a bound sequence + * Placeholder for external keys + */ +protected el_action_t +/*ARGSUSED*/ +ed_sequence_lead_in(EditLine *el, int c) +{ + + return (CC_NORM); +} + + +/* ed_prev_history(): + * Move to the previous history line + * [^P] [k] + */ +protected el_action_t +/*ARGSUSED*/ +ed_prev_history(EditLine *el, int c) +{ + char beep = 0; + + el->el_chared.c_undo.action = NOP; + *el->el_line.lastchar = '\0'; /* just in case */ + + if (el->el_history.eventno == 0) { /* save the current buffer + * away */ + (void) strncpy(el->el_history.buf, el->el_line.buffer, + EL_BUFSIZ - 1); + el->el_history.last = el->el_history.buf + + (el->el_line.lastchar - el->el_line.buffer); + } + el->el_history.eventno += el->el_state.argument; + + if (hist_get(el) == CC_ERROR) { + beep = 1; + /* el->el_history.eventno was fixed by first call */ + (void) hist_get(el); + } + re_refresh(el); + if (beep) + return (CC_ERROR); + else + return (CC_NORM); /* was CC_UP_HIST */ +} + + +/* ed_next_history(): + * Move to the next history line + * [^N] [j] + */ +protected el_action_t +/*ARGSUSED*/ +ed_next_history(EditLine *el, int c) +{ + + el->el_chared.c_undo.action = NOP; + *el->el_line.lastchar = '\0'; /* just in case */ + + el->el_history.eventno -= el->el_state.argument; + + if (el->el_history.eventno < 0) { + el->el_history.eventno = 0; + return (CC_ERROR);/* make it beep */ + } + return (hist_get(el)); +} + + +/* ed_search_prev_history(): + * Search previous in history for a line matching the current + * next search history [M-P] [K] + */ +protected el_action_t +/*ARGSUSED*/ +ed_search_prev_history(EditLine *el, int c) +{ + const char *hp; + int h; + bool_t found = 0; + + el->el_chared.c_vcmd.action = NOP; + el->el_chared.c_undo.action = NOP; + *el->el_line.lastchar = '\0'; /* just in case */ + if (el->el_history.eventno < 0) { +#ifdef DEBUG_EDIT + (void) fprintf(el->el_errfile, + "e_prev_search_hist(): eventno < 0;\n"); +#endif + el->el_history.eventno = 0; + return (CC_ERROR); + } + if (el->el_history.eventno == 0) { + (void) strncpy(el->el_history.buf, el->el_line.buffer, + EL_BUFSIZ - 1); + el->el_history.last = el->el_history.buf + + (el->el_line.lastchar - el->el_line.buffer); + } + if (el->el_history.ref == NULL) + return (CC_ERROR); + + hp = HIST_FIRST(el); + if (hp == NULL) + return (CC_ERROR); + + c_setpat(el); /* Set search pattern !! */ + + for (h = 1; h <= el->el_history.eventno; h++) + hp = HIST_NEXT(el); + + while (hp != NULL) { +#ifdef SDEBUG + (void) fprintf(el->el_errfile, "Comparing with \"%s\"\n", hp); +#endif + if ((strncmp(hp, el->el_line.buffer, (size_t) + (el->el_line.lastchar - el->el_line.buffer)) || + hp[el->el_line.lastchar - el->el_line.buffer]) && + c_hmatch(el, hp)) { + found++; + break; + } + h++; + hp = HIST_NEXT(el); + } + + if (!found) { +#ifdef SDEBUG + (void) fprintf(el->el_errfile, "not found\n"); +#endif + return (CC_ERROR); + } + el->el_history.eventno = h; + + return (hist_get(el)); +} + + +/* ed_search_next_history(): + * Search next in history for a line matching the current + * [M-N] [J] + */ +protected el_action_t +/*ARGSUSED*/ +ed_search_next_history(EditLine *el, int c) +{ + const char *hp; + int h; + bool_t found = 0; + + el->el_chared.c_vcmd.action = NOP; + el->el_chared.c_undo.action = NOP; + *el->el_line.lastchar = '\0'; /* just in case */ + + if (el->el_history.eventno == 0) + return (CC_ERROR); + + if (el->el_history.ref == NULL) + return (CC_ERROR); + + hp = HIST_FIRST(el); + if (hp == NULL) + return (CC_ERROR); + + c_setpat(el); /* Set search pattern !! */ + + for (h = 1; h < el->el_history.eventno && hp; h++) { +#ifdef SDEBUG + (void) fprintf(el->el_errfile, "Comparing with \"%s\"\n", hp); +#endif + if ((strncmp(hp, el->el_line.buffer, (size_t) + (el->el_line.lastchar - el->el_line.buffer)) || + hp[el->el_line.lastchar - el->el_line.buffer]) && + c_hmatch(el, hp)) + found = h; + hp = HIST_NEXT(el); + } + + if (!found) { /* is it the current history number? */ + if (!c_hmatch(el, el->el_history.buf)) { +#ifdef SDEBUG + (void) fprintf(el->el_errfile, "not found\n"); +#endif + return (CC_ERROR); + } + } + el->el_history.eventno = found; + + return (hist_get(el)); +} + + +/* ed_prev_line(): + * Move up one line + * Could be [k] [^p] + */ +protected el_action_t +/*ARGSUSED*/ +ed_prev_line(EditLine *el, int c) +{ + char *ptr; + int nchars = c_hpos(el); + + /* + * Move to the line requested + */ + if (*(ptr = el->el_line.cursor) == '\n') + ptr--; + + for (; ptr >= el->el_line.buffer; ptr--) + if (*ptr == '\n' && --el->el_state.argument <= 0) + break; + + if (el->el_state.argument > 0) + return (CC_ERROR); + + /* + * Move to the beginning of the line + */ + for (ptr--; ptr >= el->el_line.buffer && *ptr != '\n'; ptr--) + continue; + + /* + * Move to the character requested + */ + for (ptr++; + nchars-- > 0 && ptr < el->el_line.lastchar && *ptr != '\n'; + ptr++) + continue; + + el->el_line.cursor = ptr; + return (CC_CURSOR); +} + + +/* ed_next_line(): + * Move down one line + * Could be [j] [^n] + */ +protected el_action_t +/*ARGSUSED*/ +ed_next_line(EditLine *el, int c) +{ + char *ptr; + int nchars = c_hpos(el); + + /* + * Move to the line requested + */ + for (ptr = el->el_line.cursor; ptr < el->el_line.lastchar; ptr++) + if (*ptr == '\n' && --el->el_state.argument <= 0) + break; + + if (el->el_state.argument > 0) + return (CC_ERROR); + + /* + * Move to the character requested + */ + for (ptr++; + nchars-- > 0 && ptr < el->el_line.lastchar && *ptr != '\n'; + ptr++) + continue; + + el->el_line.cursor = ptr; + return (CC_CURSOR); +} + + +/* ed_command(): + * Editline extended command + * [M-X] [:] + */ +protected el_action_t +/*ARGSUSED*/ +ed_command(EditLine *el, int c) +{ + char tmpbuf[EL_BUFSIZ]; + int tmplen; + + el->el_line.buffer[0] = '\0'; + el->el_line.lastchar = el->el_line.buffer; + el->el_line.cursor = el->el_line.buffer; + + c_insert(el, 3); /* prompt + ": " */ + *el->el_line.cursor++ = '\n'; + *el->el_line.cursor++ = ':'; + *el->el_line.cursor++ = ' '; + re_refresh(el); + + tmplen = c_gets(el, tmpbuf); + tmpbuf[tmplen] = '\0'; + + el->el_line.buffer[0] = '\0'; + el->el_line.lastchar = el->el_line.buffer; + el->el_line.cursor = el->el_line.buffer; + + if (parse_line(el, tmpbuf) == -1) + return (CC_ERROR); + else + return (CC_REFRESH); +} diff --git a/main/editline/config.guess b/main/editline/config.guess new file mode 100755 index 000000000..a6d8a945f --- /dev/null +++ b/main/editline/config.guess @@ -0,0 +1,1449 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +# 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc. + +timestamp='2004-06-24' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Originally written by Per Bothner . +# Please send patches to . Submit a context +# diff and a properly formatted ChangeLog entry. +# +# This script attempts to guess a canonical system name similar to +# config.sub. If it succeeds, it prints the system name on stdout, and +# exits with 0. Otherwise, it exits with 1. +# +# The plan is that this can be called by configure scripts if you +# don't specify an explicit build system type. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit 0 ;; + --version | -v ) + echo "$version" ; exit 0 ;; + --help | --h* | -h ) + echo "$usage"; exit 0 ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + +trap 'exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. Note that the use of a +# compiler to aid in system detection is discouraged as it requires +# temporary files to be created and, as you can see below, it is a +# headache to deal with in a portable fashion. + +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +# Portable tmp directory creation inspired by the Autoconf team. + +set_cc_for_build=' +trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; +trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; +: ${TMPDIR=/tmp} ; + { tmp=`(umask 077 && mktemp -d -q "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || + { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || + { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || + { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; +dummy=$tmp/dummy ; +tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; +case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int x;" > $dummy.c ; + for c in cc gcc c89 c99 ; do + if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then + CC_FOR_BUILD="$c"; break ; + fi ; + done ; + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found ; + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac ;' + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + *:NetBSD:*:*) + # NetBSD (nbsd) targets should (where applicable) match one or + # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # + # Note: NetBSD doesn't particularly care about the vendor + # portion of the name. We always set it to "unknown". + sysctl="sysctl -n hw.machine_arch" + UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ + /usr/sbin/$sysctl 2>/dev/null || echo unknown)` + case "${UNAME_MACHINE_ARCH}" in + armeb) machine=armeb-unknown ;; + arm*) machine=arm-unknown ;; + sh3el) machine=shl-unknown ;; + sh3eb) machine=sh-unknown ;; + *) machine=${UNAME_MACHINE_ARCH}-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently, or will in the future. + case "${UNAME_MACHINE_ARCH}" in + arm*|i386|m68k|ns32k|sh3*|sparc|vax) + eval $set_cc_for_build + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep __ELF__ >/dev/null + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # The OS release + # Debian GNU/NetBSD machines have a different userland, and + # thus, need a distinct triplet. However, they do not need + # kernel version information, so it can be replaced with a + # suitable tag, in the style of linux-gnu. + case "${UNAME_VERSION}" in + Debian*) + release='-gnu' + ;; + *) + release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + ;; + esac + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "${machine}-${os}${release}" + exit 0 ;; + amd64:OpenBSD:*:*) + echo x86_64-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + amiga:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + arc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + cats:OpenBSD:*:*) + echo arm-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + hp300:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + luna88k:OpenBSD:*:*) + echo m88k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mac68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + macppc:OpenBSD:*:*) + echo powerpc-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme88k:OpenBSD:*:*) + echo m88k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvmeppc:OpenBSD:*:*) + echo powerpc-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + pmax:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + sgi:OpenBSD:*:*) + echo mipseb-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + sun3:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + wgrisc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + *:OpenBSD:*:*) + echo ${UNAME_MACHINE}-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + *:ekkoBSD:*:*) + echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE} + exit 0 ;; + macppc:MirBSD:*:*) + echo powerppc-unknown-mirbsd${UNAME_RELEASE} + exit 0 ;; + *:MirBSD:*:*) + echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE} + exit 0 ;; + alpha:OSF1:*:*) + case $UNAME_RELEASE in + *4.0) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + ;; + *5.*) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` + ;; + esac + # According to Compaq, /usr/sbin/psrinfo has been available on + # OSF/1 and Tru64 systems produced since 1995. I hope that + # covers most systems running today. This code pipes the CPU + # types through head -n 1, so we only detect the type of CPU 0. + ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` + case "$ALPHA_CPU_TYPE" in + "EV4 (21064)") + UNAME_MACHINE="alpha" ;; + "EV4.5 (21064)") + UNAME_MACHINE="alpha" ;; + "LCA4 (21066/21068)") + UNAME_MACHINE="alpha" ;; + "EV5 (21164)") + UNAME_MACHINE="alphaev5" ;; + "EV5.6 (21164A)") + UNAME_MACHINE="alphaev56" ;; + "EV5.6 (21164PC)") + UNAME_MACHINE="alphapca56" ;; + "EV5.7 (21164PC)") + UNAME_MACHINE="alphapca57" ;; + "EV6 (21264)") + UNAME_MACHINE="alphaev6" ;; + "EV6.7 (21264A)") + UNAME_MACHINE="alphaev67" ;; + "EV6.8CB (21264C)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8AL (21264B)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8CX (21264D)") + UNAME_MACHINE="alphaev68" ;; + "EV6.9A (21264/EV69A)") + UNAME_MACHINE="alphaev69" ;; + "EV7 (21364)") + UNAME_MACHINE="alphaev7" ;; + "EV7.9 (21364A)") + UNAME_MACHINE="alphaev79" ;; + esac + # A Pn.n version is a patched version. + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + exit 0 ;; + Alpha\ *:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # Should we change UNAME_MACHINE based on the output of uname instead + # of the specific Alpha model? + echo alpha-pc-interix + exit 0 ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit 0 ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit 0;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit 0 ;; + *:[Mm]orph[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-morphos + exit 0 ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit 0 ;; + *:OS400:*:*) + echo powerpc-ibm-os400 + exit 0 ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit 0;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit 0;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit 0 ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit 0 ;; + DRS?6000:unix:4.0:6*) + echo sparc-icl-nx6 + exit 0 ;; + DRS?6000:UNIX_SV:4.2*:7*) + case `/usr/bin/uname -p` in + sparc) echo sparc-icl-nx7 && exit 0 ;; + esac ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + i86pc:SunOS:5.*:*) + echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit 0 ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit 0 ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit 0 ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit 0 ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit 0 ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit 0 ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit 0 ;; + m68k:machten:*:*) + echo m68k-apple-machten${UNAME_RELEASE} + exit 0 ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit 0 ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit 0 ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit 0 ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c \ + && $dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \ + && exit 0 + echo mips-mips-riscos${UNAME_RELEASE} + exit 0 ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit 0 ;; + Motorola:*:4.3:PL8-*) + echo powerpc-harris-powermax + exit 0 ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + echo powerpc-harris-powermax + exit 0 ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit 0 ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit 0 ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit 0 ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit 0 ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] + then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ + [ ${TARGET_BINARY_INTERFACE}x = x ] + then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else + echo i586-dg-dgux${UNAME_RELEASE} + fi + exit 0 ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit 0 ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit 0 ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit 0 ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit 0 ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit 0 ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit 0 ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit 0 ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} + exit 0 ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0 + echo rs6000-ibm-aix3.2.5 + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit 0 ;; + *:AIX:*:[45]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` + if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit 0 ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit 0 ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit 0 ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit 0 ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit 0 ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit 0 ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit 0 ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit 0 ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "${sc_cpu_version}" in + 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 + 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "${sc_kernel_bits}" in + 32) HP_ARCH="hppa2.0n" ;; + 64) HP_ARCH="hppa2.0w" ;; + '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 + esac ;; + esac + fi + if [ "${HP_ARCH}" = "" ]; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` + test -z "$HP_ARCH" && HP_ARCH=hppa + fi ;; + esac + if [ ${HP_ARCH} = "hppa2.0w" ] + then + # avoid double evaluation of $set_cc_for_build + test -n "$CC_FOR_BUILD" || eval $set_cc_for_build + if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E -) | grep __LP64__ >/dev/null + then + HP_ARCH="hppa2.0w" + else + HP_ARCH="hppa64" + fi + fi + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit 0 ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux${HPUX_REV} + exit 0 ;; + 3050*:HI-UX:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0 + echo unknown-hitachi-hiuxwe2 + exit 0 ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit 0 ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit 0 ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit 0 ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit 0 ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit 0 ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit 0 ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit 0 ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit 0 ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit 0 ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit 0 ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit 0 ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + *:UNICOS/mp:*:*) + echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit 0 ;; + 5000:UNIX_System_V:4.*:*) + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` + echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit 0 ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit 0 ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:FreeBSD:*:*) + echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit 0 ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit 0 ;; + i*:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit 0 ;; + i*:PW*:*) + echo ${UNAME_MACHINE}-pc-pw32 + exit 0 ;; + x86:Interix*:[34]*) + echo i586-pc-interix${UNAME_RELEASE}|sed -e 's/\..*//' + exit 0 ;; + [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) + echo i${UNAME_MACHINE}-pc-mks + exit 0 ;; + i*:Windows_NT*:* | Pentium*:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we + # UNAME_MACHINE based on the output of uname instead of i386? + echo i586-pc-interix + exit 0 ;; + i*:UWIN*:*) + echo ${UNAME_MACHINE}-pc-uwin + exit 0 ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit 0 ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + *:GNU:*:*) + # the GNU system + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit 0 ;; + *:GNU/*:*:*) + # other systems with GNU libc and userland + echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-gnu + exit 0 ;; + i*86:Minix:*:*) + echo ${UNAME_MACHINE}-pc-minix + exit 0 ;; + arm*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + cris:Linux:*:*) + echo cris-axis-linux-gnu + exit 0 ;; + ia64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + m32r*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + m68*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + mips:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef mips + #undef mipsel + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=mipsel + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=mips + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` + test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0 + ;; + mips64:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef mips64 + #undef mips64el + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=mips64el + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=mips64 + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` + test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0 + ;; + ppc:Linux:*:*) + echo powerpc-unknown-linux-gnu + exit 0 ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-gnu + exit 0 ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null + if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi + echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} + exit 0 ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-unknown-linux-gnu ;; + PA8*) echo hppa2.0-unknown-linux-gnu ;; + *) echo hppa-unknown-linux-gnu ;; + esac + exit 0 ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-unknown-linux-gnu + exit 0 ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo ${UNAME_MACHINE}-ibm-linux + exit 0 ;; + sh64*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + sh*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + x86_64:Linux:*:*) + echo x86_64-unknown-linux-gnu + exit 0 ;; + i*86:Linux:*:*) + # The BFD linker knows what the default object file format is, so + # first see if it will tell us. cd to the root directory to prevent + # problems with other programs or directories called `ld' in the path. + # Set LC_ALL=C to ensure ld outputs messages in English. + ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \ + | sed -ne '/supported targets:/!d + s/[ ][ ]*/ /g + s/.*supported targets: *// + s/ .*// + p'` + case "$ld_supported_targets" in + elf32-i386) + TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu" + ;; + a.out-i386-linux) + echo "${UNAME_MACHINE}-pc-linux-gnuaout" + exit 0 ;; + coff-i386) + echo "${UNAME_MACHINE}-pc-linux-gnucoff" + exit 0 ;; + "") + # Either a pre-BFD a.out linker (linux-gnuoldld) or + # one that does not give us useful --help. + echo "${UNAME_MACHINE}-pc-linux-gnuoldld" + exit 0 ;; + esac + # Determine whether the default compiler is a.out or elf + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + #ifdef __ELF__ + # ifdef __GLIBC__ + # if __GLIBC__ >= 2 + LIBC=gnu + # else + LIBC=gnulibc1 + # endif + # else + LIBC=gnulibc1 + # endif + #else + #ifdef __INTEL_COMPILER + LIBC=gnu + #else + LIBC=gnuaout + #endif + #endif + #ifdef __dietlibc__ + LIBC=dietlibc + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=` + test x"${LIBC}" != x && echo "${UNAME_MACHINE}-pc-linux-${LIBC}" && exit 0 + test x"${TENTATIVE}" != x && echo "${TENTATIVE}" && exit 0 + ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + echo i386-sequent-sysv4 + exit 0 ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit 0 ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo ${UNAME_MACHINE}-pc-os2-emx + exit 0 ;; + i*86:XTS-300:*:STOP) + echo ${UNAME_MACHINE}-unknown-stop + exit 0 ;; + i*86:atheos:*:*) + echo ${UNAME_MACHINE}-unknown-atheos + exit 0 ;; + i*86:syllable:*:*) + echo ${UNAME_MACHINE}-pc-syllable + exit 0 ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + i*86:*DOS:*:*) + echo ${UNAME_MACHINE}-pc-msdosdjgpp + exit 0 ;; + i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) + UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} + fi + exit 0 ;; + i*86:*:5:[78]*) + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} + exit 0 ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` + (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit 0 ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i386. + echo i386-pc-msdosdjgpp + exit 0 ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit 0 ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit 0 ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit 0 ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit 0 ;; + mc68k:UNIX:SYSTEM5:3.51m) + echo m68k-convergent-sysv + exit 0 ;; + M680?0:D-NIX:5.3:*) + echo m68k-diab-dnix + exit 0 ;; + M68*:*:R3V[5678]*:*) + test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;; + 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4.3${OS_REL} && exit 0 + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4 && exit 0 ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit 0 ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*) + echo powerpc-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit 0 ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit 0 ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit 0 ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit 0 ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit 0 ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit 0 ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit 0 ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit 0 ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit 0 ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit 0 ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit 0 ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit 0 ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit 0 ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit 0 ;; + SX-6:SUPER-UX:*:*) + echo sx6-nec-superux${UNAME_RELEASE} + exit 0 ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; + *:Darwin:*:*) + case `uname -p` in + *86) UNAME_PROCESSOR=i686 ;; + powerpc) UNAME_PROCESSOR=powerpc ;; + esac + echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} + exit 0 ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=`uname -p` + if test "$UNAME_PROCESSOR" = "x86"; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} + exit 0 ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit 0 ;; + NSR-?:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk${UNAME_RELEASE} + exit 0 ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit 0 ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit 0 ;; + DS/*:UNIX_System_V:*:*) + echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} + exit 0 ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = "386"; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo ${UNAME_MACHINE}-unknown-plan9 + exit 0 ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit 0 ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit 0 ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit 0 ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit 0 ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit 0 ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit 0 ;; + SEI:*:*:SEIUX) + echo mips-sei-seiux${UNAME_RELEASE} + exit 0 ;; + *:DragonFly:*:*) + echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit 0 ;; + *:*VMS:*:*) + UNAME_MACHINE=`(uname -p) 2>/dev/null` + case "${UNAME_MACHINE}" in + A*) echo alpha-dec-vms && exit 0 ;; + I*) echo ia64-dec-vms && exit 0 ;; + V*) echo vax-dec-vms && exit 0 ;; + esac +esac + +#echo '(No uname command or uname output not recognized.)' 1>&2 +#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 + +eval $set_cc_for_build +cat >$dummy.c < +# include +#endif +main () +{ +#if defined (sony) +#if defined (MIPSEB) + /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, + I don't know.... */ + printf ("mips-sony-bsd\n"); exit (0); +#else +#include + printf ("m68k-sony-newsos%s\n", +#ifdef NEWSOS4 + "4" +#else + "" +#endif + ); exit (0); +#endif +#endif + +#if defined (__arm) && defined (__acorn) && defined (__unix) + printf ("arm-acorn-riscix"); exit (0); +#endif + +#if defined (hp300) && !defined (hpux) + printf ("m68k-hp-bsd\n"); exit (0); +#endif + +#if defined (NeXT) +#if !defined (__ARCHITECTURE__) +#define __ARCHITECTURE__ "m68k" +#endif + int version; + version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; + if (version < 4) + printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); + else + printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); + exit (0); +#endif + +#if defined (MULTIMAX) || defined (n16) +#if defined (UMAXV) + printf ("ns32k-encore-sysv\n"); exit (0); +#else +#if defined (CMU) + printf ("ns32k-encore-mach\n"); exit (0); +#else + printf ("ns32k-encore-bsd\n"); exit (0); +#endif +#endif +#endif + +#if defined (__386BSD__) + printf ("i386-pc-bsd\n"); exit (0); +#endif + +#if defined (sequent) +#if defined (i386) + printf ("i386-sequent-dynix\n"); exit (0); +#endif +#if defined (ns32000) + printf ("ns32k-sequent-dynix\n"); exit (0); +#endif +#endif + +#if defined (_SEQUENT_) + struct utsname un; + + uname(&un); + + if (strncmp(un.version, "V2", 2) == 0) { + printf ("i386-sequent-ptx2\n"); exit (0); + } + if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ + printf ("i386-sequent-ptx1\n"); exit (0); + } + printf ("i386-sequent-ptx\n"); exit (0); + +#endif + +#if defined (vax) +# if !defined (ultrix) +# include +# if defined (BSD) +# if BSD == 43 + printf ("vax-dec-bsd4.3\n"); exit (0); +# else +# if BSD == 199006 + printf ("vax-dec-bsd4.3reno\n"); exit (0); +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# endif +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# else + printf ("vax-dec-ultrix\n"); exit (0); +# endif +#endif + +#if defined (alliant) && defined (i860) + printf ("i860-alliant-bsd\n"); exit (0); +#endif + + exit (1); +} +EOF + +$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && $dummy && exit 0 + +# Apollos put the system type in the environment. + +test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; } + +# Convex versions that predate uname can use getsysinfo(1) + +if [ -x /usr/convex/getsysinfo ] +then + case `getsysinfo -f cpu_type` in + c1*) + echo c1-convex-bsd + exit 0 ;; + c2*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + c34*) + echo c34-convex-bsd + exit 0 ;; + c38*) + echo c38-convex-bsd + exit 0 ;; + c4*) + echo c4-convex-bsd + exit 0 ;; + esac +fi + +cat >&2 < in order to provide the needed +information to handle your system. + +config.guess timestamp = $timestamp + +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = ${UNAME_MACHINE} +UNAME_RELEASE = ${UNAME_RELEASE} +UNAME_SYSTEM = ${UNAME_SYSTEM} +UNAME_VERSION = ${UNAME_VERSION} +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/main/editline/config.h.in b/main/editline/config.h.in new file mode 100644 index 000000000..151fb226d --- /dev/null +++ b/main/editline/config.h.in @@ -0,0 +1,21 @@ +#undef SUNOS +#undef CYGWIN + +#undef HAVE_SYS_CDEFS_H +#undef HAVE_TERMCAP_H +#undef HAVE_CURSES_H +#undef HAVE_NCURSES_H +#undef HAVE_TERM_H +#undef HAVE_VIS_H +#undef HAVE_ISSETUGID + +#undef HAVE_STRLCAT +#undef HAVE_STRLCPY +#undef HAVE_FGETLN +#undef HAVE_STRVIS +#undef HAVE_STRUNVIS + +#include "sys.h" +#ifdef CYGWIN +# include "cygdef.h" +#endif diff --git a/main/editline/config.sub b/main/editline/config.sub new file mode 100755 index 000000000..838237e98 --- /dev/null +++ b/main/editline/config.sub @@ -0,0 +1,1412 @@ +#! /bin/sh +# Configuration validation subroutine script. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 +# Free Software Foundation, Inc. + +timestamp='2001-09-14' + +# This file is (in principle) common to ALL GNU software. +# The presence of a machine in this file suggests that SOME GNU software +# can handle that machine. It does not imply ALL GNU software can. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Please send patches to . +# +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] CPU-MFR-OPSYS + $0 [OPTION] ALIAS + +Canonicalize a configuration name. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.sub ($timestamp) + +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit 0 ;; + --version | -v ) + echo "$version" ; exit 0 ;; + --help | --h* | -h ) + echo "$usage"; exit 0 ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" + exit 1 ;; + + *local*) + # First pass through any local machine types. + echo $1 + exit 0;; + + * ) + break ;; + esac +done + +case $# in + 0) echo "$me: missing argument$help" >&2 + exit 1;; + 1) ;; + *) echo "$me: too many arguments$help" >&2 + exit 1;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + nto-qnx* | linux-gnu* | storm-chaos* | os2-emx* | windows32-*) + os=-$maybe_os + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + *) + basic_machine=`echo $1 | sed 's/-[^-]*$//'` + if [ $basic_machine != $1 ] + then os=`echo $1 | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple | -axis) + os= + basic_machine=$1 + ;; + -sim | -cisco | -oki | -wec | -winbond) + os= + basic_machine=$1 + ;; + -scout) + ;; + -wrs) + os=-vxworks + basic_machine=$1 + ;; + -chorusos*) + os=-chorusos + basic_machine=$1 + ;; + -chorusrdb) + os=-chorusrdb + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco5) + os=-sco3.2v5 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -udk*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` + ;; + -windowsnt*) + os=`echo $os | sed -e 's/windowsnt/winnt/'` + ;; + -psos*) + os=-psos + ;; + -mint | -mint[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + 1750a | 580 \ + | a29k \ + | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ + | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr \ + | c4x | clipper \ + | d10v | d30v | dsp16xx \ + | fr30 \ + | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | i370 | i860 | i960 | ia64 \ + | m32r | m68000 | m68k | m88k | mcore \ + | mips16 | mips64 | mips64el | mips64orion | mips64orionel \ + | mips64vr4100 | mips64vr4100el | mips64vr4300 \ + | mips64vr4300el | mips64vr5000 | mips64vr5000el \ + | mipsbe | mipseb | mipsel | mipsle | mipstx39 | mipstx39el \ + | mipsisa32 \ + | mn10200 | mn10300 \ + | ns16k | ns32k \ + | openrisc \ + | pdp10 | pdp11 | pj | pjl \ + | powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \ + | pyramid \ + | s390 | s390x \ + | sh | sh[34] | sh[34]eb | shbe | shle \ + | sparc | sparc64 | sparclet | sparclite | sparcv9 | sparcv9b \ + | stormy16 | strongarm \ + | tahoe | thumb | tic80 | tron \ + | v850 \ + | we32k \ + | x86 | xscale \ + | z8k) + basic_machine=$basic_machine-unknown + ;; + m6811 | m68hc11 | m6812 | m68hc12) + # Motorola 68HC11/12. + basic_machine=$basic_machine-unknown + os=-none + ;; + m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k) + ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i*86 | x86_64) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + 580-* \ + | a29k-* \ + | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ + | alphapca5[67]-* | arc-* \ + | arm-* | armbe-* | armle-* | armv*-* \ + | avr-* \ + | bs2000-* \ + | c[123]* | c30-* | [cjt]90-* | c54x-* \ + | clipper-* | cray2-* | cydra-* \ + | d10v-* | d30v-* \ + | elxsi-* \ + | f30[01]-* | f700-* | fr30-* | fx80-* \ + | h8300-* | h8500-* \ + | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ + | i*86-* | i860-* | i960-* | ia64-* \ + | m32r-* \ + | m68000-* | m680[01234]0-* | m68360-* | m683?2-* | m68k-* \ + | m88110-* | m88k-* | mcore-* \ + | mips-* | mips16-* | mips64-* | mips64el-* | mips64orion-* \ + | mips64orionel-* | mips64vr4100-* | mips64vr4100el-* \ + | mips64vr4300-* | mips64vr4300el-* | mipsbe-* | mipseb-* \ + | mipsle-* | mipsel-* | mipstx39-* | mipstx39el-* \ + | none-* | np1-* | ns16k-* | ns32k-* \ + | orion-* \ + | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ + | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \ + | pyramid-* \ + | romp-* | rs6000-* \ + | s390-* | s390x-* \ + | sh-* | sh[34]-* | sh[34]eb-* | shbe-* | shle-* \ + | sparc-* | sparc64-* | sparc86x-* | sparclite-* \ + | sparcv9-* | sparcv9b-* | stormy16-* | strongarm-* | sv1-* \ + | t3e-* | tahoe-* | thumb-* | tic30-* | tic54x-* | tic80-* | tron-* \ + | v850-* | vax-* \ + | we32k-* \ + | x86-* | x86_64-* | xmp-* | xps100-* | xscale-* \ + | ymp-* \ + | z8k-*) + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 386bsd) + basic_machine=i386-unknown + os=-bsd + ;; + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + a29khif) + basic_machine=a29k-amd + os=-udi + ;; + adobe68k) + basic_machine=m68010-adobe + os=-scout + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-unknown + ;; + amigaos | amigados) + basic_machine=m68k-unknown + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=-bsd + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | ymp) + basic_machine=ymp-cray + os=-unicos + ;; + cray2) + basic_machine=cray2-cray + os=-unicos + ;; + [cjt]90) + basic_machine=${basic_machine}-cray + os=-unicos + ;; + crds | unos) + basic_machine=m68k-crds + ;; + cris | cris-* | etrax*) + basic_machine=cris-axis + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2* | dpx2*-bull) + basic_machine=m68k-bull + os=-sysv3 + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=-ose + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + go32) + basic_machine=i386-pc + os=-go32 + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=-xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + basic_machine=hppa1.1-hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppa-next) + os=-nextstep3 + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=-osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=-proelf + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + ;; +# I'm not sure what "Sysv32" means. Should this be sysv3.2? + i*86v32) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i*86v4*) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i*86v) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i*86sol2) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + i386mach) + basic_machine=i386-mach + os=-mach + ;; + i386-vsta | vsta) + basic_machine=i386-unknown + os=-vsta + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + m88k-omron*) + basic_machine=m88k-omron + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + mingw32) + basic_machine=i386-pc + os=-mingw32 + ;; + miniframe) + basic_machine=m68000-convergent + ;; + *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; + mipsel*-linux*) + basic_machine=mipsel-unknown + os=-linux-gnu + ;; + mips*-linux*) + basic_machine=mips-unknown + os=-linux-gnu + ;; + mips3*-*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown + ;; + mmix*) + basic_machine=mmix-knuth + os=-mmixware + ;; + monitor) + basic_machine=m68k-rom68k + os=-coff + ;; + msdos) + basic_machine=i386-pc + os=-msdos + ;; + mvs) + basic_machine=i370-ibm + os=-mvs + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=-netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + necv70) + basic_machine=v70-nec + os=-sysv + ;; + next | m*-next ) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + mon960) + basic_machine=i960-intel + os=-mon960 + ;; + nonstopux) + basic_machine=mips-compaq + os=-nonstopux + ;; + np1) + basic_machine=np1-gould + ;; + nsr-tandem) + basic_machine=nsr-tandem + ;; + op50n-* | op60c-*) + basic_machine=hppa1.1-oki + os=-proelf + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=-ose + ;; + os68k) + basic_machine=m68k-none + os=-os68k + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pentium | p5 | k5 | k6 | nexgen) + basic_machine=i586-pc + ;; + pentiumpro | p6 | 6x86 | athlon) + basic_machine=i686-pc + ;; + pentiumii | pentium2) + basic_machine=i686-pc + ;; + pentium-* | p5-* | k5-* | k6-* | nexgen-*) + basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | 6x86-* | athlon-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=power-ibm + ;; + ppc) basic_machine=powerpc-unknown + ;; + ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle | ppc-le | powerpc-little) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64) basic_machine=powerpc64-unknown + ;; + ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64le | powerpc64little | ppc64-le | powerpc64-little) + basic_machine=powerpc64le-unknown + ;; + ppc64le-* | powerpc64little-*) + basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + pw32) + basic_machine=i586-unknown + os=-pw32 + ;; + rom68k) + basic_machine=m68k-rom68k + os=-coff + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + sa29200) + basic_machine=a29k-amd + os=-udi + ;; + sequent) + basic_machine=i386-sequent + ;; + sh) + basic_machine=sh-hitachi + os=-hms + ;; + sparclite-wrs) + basic_machine=sparclite-wrs + os=-vxworks + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + st2000) + basic_machine=m68k-tandem + ;; + stratus) + basic_machine=i860-stratus + os=-sysv4 + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + sv1) + basic_machine=sv1-cray + os=-unicos + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + t3e) + basic_machine=t3e-cray + os=-unicos + ;; + tic54x | c54x*) + basic_machine=tic54x-unknown + os=-coff + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=-none + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + w65*) + basic_machine=w65-wdc + os=-none + ;; + w89k-*) + basic_machine=hppa1.1-winbond + os=-proelf + ;; + windows32) + basic_machine=i386-pc + os=-windows32-msvcrt + ;; + xmp) + basic_machine=xmp-cray + os=-unicos + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + z8k-*-coff) + basic_machine=z8k-unknown + os=-sim + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + w89k) + basic_machine=hppa1.1-winbond + ;; + op50n) + basic_machine=hppa1.1-oki + ;; + op60c) + basic_machine=hppa1.1-oki + ;; + mips) + if [ x$os = x-linux-gnu ]; then + basic_machine=mips-unknown + else + basic_machine=mips-mips + fi + ;; + romp) + basic_machine=romp-ibm + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp10) + # there are many clones, so DEC is not a safe bet + basic_machine=pdp10-unknown + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sh3 | sh4 | sh3eb | sh4eb) + basic_machine=sh-unknown + ;; + sparc | sparcv9 | sparcv9b) + basic_machine=sparc-sun + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + mac | mpw | mac-mpw) + basic_machine=m68k-apple + ;; + pmac | pmac-mpw) + basic_machine=powerpc-apple + ;; + c4x*) + basic_machine=c4x-none + os=-coff + ;; + *-unknown) + # Make sure to match an already-canonicalized machine name. + ;; + *) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases + # that might get confused with valid system types. + # -solaris* is a basic system type, with this one exception. + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -svr4*) + os=-sysv4 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # First accept the basic system types. + # The portable systems comes first. + # Each alternative MUST END IN A *, to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -386bsd* | -netbsd* | -openbsd* | -freebsd* | -riscix* \ + | -lynxos* | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ + | -chorusos* | -chorusrdb* \ + | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -mingw32* | -linux-gnu* | -uxpv* | -beos* | -mpeix* | -udk* \ + | -interix* | -uwin* | -rhapsody* | -darwin* | -opened* \ + | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ + | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ + | -os2* | -vos*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -qnx*) + case $basic_machine in + x86-* | i*86-*) + ;; + *) + os=-nto$os + ;; + esac + ;; + -nto*) + os=-nto-qnx + ;; + -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ + | -windows* | -osx | -abug | -netware* | -os9* | -beos* \ + | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) + ;; + -mac*) + os=`echo $os | sed -e 's|mac|macos|'` + ;; + -linux*) + os=`echo $os | sed -e 's|linux|linux-gnu|'` + ;; + -sunos5*) + os=`echo $os | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo $os | sed -e 's|sunos6|solaris3|'` + ;; + -opened*) + os=-openedition + ;; + -wince*) + os=-wince + ;; + -osfrose*) + os=-osfrose + ;; + -osf*) + os=-osf + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -386bsd) + os=-bsd + ;; + -ctix* | -uts*) + os=-sysv + ;; + -ns2 ) + os=-nextstep2 + ;; + -nsk*) + os=-nsk + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -ose*) + os=-ose + ;; + -es1800*) + os=-ose + ;; + -xenix) + os=-xenix + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + os=-mint + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + *-acorn) + os=-riscix1.2 + ;; + arm*-rebel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + pdp10-*) + os=-tops20 + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + # This also exists in the configure program, but was not the + # default. + # os=-sunos4 + ;; + m68*-cisco) + os=-aout + ;; + mips*-cisco) + os=-elf + ;; + mips*-*) + os=-elf + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + *-be) + os=-beos + ;; + *-ibm) + os=-aix + ;; + *-wec) + os=-proelf + ;; + *-winbond) + os=-proelf + ;; + *-oki) + os=-proelf + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next ) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-next) + os=-nextstep3 + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f30[01]-fujitsu | f700-fujitsu) + os=-uxpv + ;; + *-rom68k) + os=-coff + ;; + *-*bug) + os=-coff + ;; + *-apple) + os=-macos + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -aix*) + vendor=ibm + ;; + -beos*) + vendor=be + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs* | -opened*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -vxsim* | -vxworks*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -hms*) + vendor=hitachi + ;; + -mpw* | -macos*) + vendor=apple + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + vendor=atari + ;; + -vos*) + vendor=stratus + ;; + esac + basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` + ;; +esac + +echo $basic_machine$os +exit 0 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/main/editline/configure b/main/editline/configure new file mode 100755 index 000000000..8f9075c5f --- /dev/null +++ b/main/editline/configure @@ -0,0 +1,2421 @@ +#! /bin/sh + +# Guess values for system-dependent variables and create Makefiles. +# Generated automatically using autoconf version 2.13 +# Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc. +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. + +# Defaults: +ac_help= +ac_default_prefix=/usr/local +# Any additions from configure.in: +ac_help="$ac_help + --disable-readline Disable readline compatibility" +ac_help="$ac_help + --enable-debug Enable debugging code" + +# Initialize some variables set by options. +# The variables have the same names as the options, with +# dashes changed to underlines. +build=NONE +cache_file=./config.cache +exec_prefix=NONE +host=NONE +no_create= +nonopt=NONE +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +target=NONE +verbose= +x_includes=NONE +x_libraries=NONE +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datadir='${prefix}/share' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +libdir='${exec_prefix}/lib' +includedir='${prefix}/include' +oldincludedir='/usr/include' +infodir='${prefix}/info' +mandir='${prefix}/man' + +# Initialize some other variables. +subdirs= +MFLAGS= MAKEFLAGS= +SHELL=${CONFIG_SHELL-/bin/sh} +# Maximum number of lines to put in a shell here document. +ac_max_here_lines=12 + +ac_prev= +for ac_option +do + + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval "$ac_prev=\$ac_option" + ac_prev= + continue + fi + + case "$ac_option" in + -*=*) ac_optarg=`echo "$ac_option" | sed 's/[-_a-zA-Z0-9]*=//'` ;; + *) ac_optarg= ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case "$ac_option" in + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir="$ac_optarg" ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build="$ac_optarg" ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file="$ac_optarg" ;; + + -datadir | --datadir | --datadi | --datad | --data | --dat | --da) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \ + | --da=*) + datadir="$ac_optarg" ;; + + -disable-* | --disable-*) + ac_feature=`echo $ac_option|sed -e 's/-*disable-//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_feature| sed 's/[-a-zA-Z0-9_]//g'`"; then + { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; } + fi + ac_feature=`echo $ac_feature| sed 's/-/_/g'` + eval "enable_${ac_feature}=no" ;; + + -enable-* | --enable-*) + ac_feature=`echo $ac_option|sed -e 's/-*enable-//' -e 's/=.*//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_feature| sed 's/[-_a-zA-Z0-9]//g'`"; then + { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; } + fi + ac_feature=`echo $ac_feature| sed 's/-/_/g'` + case "$ac_option" in + *=*) ;; + *) ac_optarg=yes ;; + esac + eval "enable_${ac_feature}='$ac_optarg'" ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix="$ac_optarg" ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he) + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat << EOF +Usage: configure [options] [host] +Options: [defaults in brackets after descriptions] +Configuration: + --cache-file=FILE cache test results in FILE + --help print this message + --no-create do not create output files + --quiet, --silent do not print \`checking...' messages + --version print the version of autoconf that created configure +Directory and file names: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [same as prefix] + --bindir=DIR user executables in DIR [EPREFIX/bin] + --sbindir=DIR system admin executables in DIR [EPREFIX/sbin] + --libexecdir=DIR program executables in DIR [EPREFIX/libexec] + --datadir=DIR read-only architecture-independent data in DIR + [PREFIX/share] + --sysconfdir=DIR read-only single-machine data in DIR [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data in DIR + [PREFIX/com] + --localstatedir=DIR modifiable single-machine data in DIR [PREFIX/var] + --libdir=DIR object code libraries in DIR [EPREFIX/lib] + --includedir=DIR C header files in DIR [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc in DIR [/usr/include] + --infodir=DIR info documentation in DIR [PREFIX/info] + --mandir=DIR man documentation in DIR [PREFIX/man] + --srcdir=DIR find the sources in DIR [configure dir or ..] + --program-prefix=PREFIX prepend PREFIX to installed program names + --program-suffix=SUFFIX append SUFFIX to installed program names + --program-transform-name=PROGRAM + run sed PROGRAM on installed program names +EOF + cat << EOF +Host type: + --build=BUILD configure for building on BUILD [BUILD=HOST] + --host=HOST configure for HOST [guessed] + --target=TARGET configure for TARGET [TARGET=HOST] +Features and packages: + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --x-includes=DIR X include files are in DIR + --x-libraries=DIR X library files are in DIR +EOF + if test -n "$ac_help"; then + echo "--enable and --with options recognized:$ac_help" + fi + exit 0 ;; + + -host | --host | --hos | --ho) + ac_prev=host ;; + -host=* | --host=* | --hos=* | --ho=*) + host="$ac_optarg" ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir="$ac_optarg" ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir="$ac_optarg" ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir="$ac_optarg" ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir="$ac_optarg" ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst \ + | --locals | --local | --loca | --loc | --lo) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* \ + | --locals=* | --local=* | --loca=* | --loc=* | --lo=*) + localstatedir="$ac_optarg" ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir="$ac_optarg" ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir="$ac_optarg" ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix="$ac_optarg" ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix="$ac_optarg" ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix="$ac_optarg" ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name="$ac_optarg" ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir="$ac_optarg" ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir="$ac_optarg" ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site="$ac_optarg" ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir="$ac_optarg" ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir="$ac_optarg" ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target="$ac_optarg" ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers) + echo "configure generated by autoconf version 2.13" + exit 0 ;; + + -with-* | --with-*) + ac_package=`echo $ac_option|sed -e 's/-*with-//' -e 's/=.*//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_package| sed 's/[-_a-zA-Z0-9]//g'`"; then + { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; } + fi + ac_package=`echo $ac_package| sed 's/-/_/g'` + case "$ac_option" in + *=*) ;; + *) ac_optarg=yes ;; + esac + eval "with_${ac_package}='$ac_optarg'" ;; + + -without-* | --without-*) + ac_package=`echo $ac_option|sed -e 's/-*without-//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_package| sed 's/[-a-zA-Z0-9_]//g'`"; then + { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; } + fi + ac_package=`echo $ac_package| sed 's/-/_/g'` + eval "with_${ac_package}=no" ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes="$ac_optarg" ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries="$ac_optarg" ;; + + -*) { echo "configure: error: $ac_option: invalid option; use --help to show usage" 1>&2; exit 1; } + ;; + + *) + if test -n "`echo $ac_option| sed 's/[-a-z0-9.]//g'`"; then + echo "configure: warning: $ac_option: invalid host type" 1>&2 + fi + if test "x$nonopt" != xNONE; then + { echo "configure: error: can only configure for one host and one target at a time" 1>&2; exit 1; } + fi + nonopt="$ac_option" + ;; + + esac +done + +if test -n "$ac_prev"; then + { echo "configure: error: missing argument to --`echo $ac_prev | sed 's/_/-/g'`" 1>&2; exit 1; } +fi + +trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15 + +# File descriptor usage: +# 0 standard input +# 1 file creation +# 2 errors and warnings +# 3 some systems may open it to /dev/tty +# 4 used on the Kubota Titan +# 6 checking for... messages and results +# 5 compiler messages saved in config.log +if test "$silent" = yes; then + exec 6>/dev/null +else + exec 6>&1 +fi +exec 5>./config.log + +echo "\ +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. +" 1>&5 + +# Strip out --no-create and --no-recursion so they do not pile up. +# Also quote any args containing shell metacharacters. +ac_configure_args= +for ac_arg +do + case "$ac_arg" in + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c) ;; + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) ;; + *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?]*) + ac_configure_args="$ac_configure_args '$ac_arg'" ;; + *) ac_configure_args="$ac_configure_args $ac_arg" ;; + esac +done + +# NLS nuisances. +# Only set these to C if already set. These must not be set unconditionally +# because not all systems understand e.g. LANG=C (notably SCO). +# Fixing LC_MESSAGES prevents Solaris sh from translating var values in `set'! +# Non-C LC_CTYPE values break the ctype check. +if test "${LANG+set}" = set; then LANG=C; export LANG; fi +if test "${LC_ALL+set}" = set; then LC_ALL=C; export LC_ALL; fi +if test "${LC_MESSAGES+set}" = set; then LC_MESSAGES=C; export LC_MESSAGES; fi +if test "${LC_CTYPE+set}" = set; then LC_CTYPE=C; export LC_CTYPE; fi + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -rf conftest* confdefs.h +# AIX cpp loses on an empty file, so make sure it contains at least a newline. +echo > confdefs.h + +# A filename unique to this package, relative to the directory that +# configure is in, which we can look for to find out if srcdir is correct. +ac_unique_file=Makefile.in + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then its parent. + ac_prog=$0 + ac_confdir=`echo $ac_prog|sed 's%/[^/][^/]*$%%'` + test "x$ac_confdir" = "x$ac_prog" && ac_confdir=. + srcdir=$ac_confdir + if test ! -r $srcdir/$ac_unique_file; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r $srcdir/$ac_unique_file; then + if test "$ac_srcdir_defaulted" = yes; then + { echo "configure: error: can not find sources in $ac_confdir or .." 1>&2; exit 1; } + else + { echo "configure: error: can not find sources in $srcdir" 1>&2; exit 1; } + fi +fi +srcdir=`echo "${srcdir}" | sed 's%\([^/]\)/*$%\1%'` + +# Prefer explicitly selected file to automatically selected ones. +if test -z "$CONFIG_SITE"; then + if test "x$prefix" != xNONE; then + CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site" + else + CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" + fi +fi +for ac_site_file in $CONFIG_SITE; do + if test -r "$ac_site_file"; then + echo "loading site script $ac_site_file" + . "$ac_site_file" + fi +done + +if test -r "$cache_file"; then + echo "loading cache $cache_file" + . $cache_file +else + echo "creating cache $cache_file" + > $cache_file +fi + +ac_ext=c +# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. +ac_cpp='$CPP $CPPFLAGS' +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5' +ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5' +cross_compiling=$ac_cv_prog_cc_cross + +ac_exeext= +ac_objext=o +if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null; then + # Stardent Vistra SVR4 grep lacks -e, says ghazi@caip.rutgers.edu. + if (echo -n testing; echo 1,2,3) | sed s/-n/xn/ | grep xn >/dev/null; then + ac_n= ac_c=' +' ac_t=' ' + else + ac_n=-n ac_c= ac_t= + fi +else + ac_n= ac_c='\c' ac_t= +fi + + + +CFLAGS=$CFLAGS +# Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:534: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_CC="gcc" + break + fi + done + IFS="$ac_save_ifs" +fi +fi +CC="$ac_cv_prog_CC" +if test -n "$CC"; then + echo "$ac_t""$CC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:564: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_prog_rejected=no + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + if test "$ac_dir/$ac_word" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + break + fi + done + IFS="$ac_save_ifs" +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# -gt 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + set dummy "$ac_dir/$ac_word" "$@" + shift + ac_cv_prog_CC="$@" + fi +fi +fi +fi +CC="$ac_cv_prog_CC" +if test -n "$CC"; then + echo "$ac_t""$CC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + + if test -z "$CC"; then + case "`uname -s`" in + *win32* | *WIN32*) + # Extract the first word of "cl", so it can be a program name with args. +set dummy cl; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:615: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_CC="cl" + break + fi + done + IFS="$ac_save_ifs" +fi +fi +CC="$ac_cv_prog_CC" +if test -n "$CC"; then + echo "$ac_t""$CC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + ;; + esac + fi + test -z "$CC" && { echo "configure: error: no acceptable cc found in \$PATH" 1>&2; exit 1; } +fi + +echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works""... $ac_c" 1>&6 +echo "configure:647: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works" >&5 + +ac_ext=c +# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. +ac_cpp='$CPP $CPPFLAGS' +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5' +ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5' +cross_compiling=$ac_cv_prog_cc_cross + +cat > conftest.$ac_ext << EOF + +#line 658 "configure" +#include "confdefs.h" + +main(){return(0);} +EOF +if { (eval echo configure:663: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + ac_cv_prog_cc_works=yes + # If we can't run a trivial program, we are probably using a cross compiler. + if (./conftest; exit) 2>/dev/null; then + ac_cv_prog_cc_cross=no + else + ac_cv_prog_cc_cross=yes + fi +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + ac_cv_prog_cc_works=no +fi +rm -fr conftest* +ac_ext=c +# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. +ac_cpp='$CPP $CPPFLAGS' +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5' +ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5' +cross_compiling=$ac_cv_prog_cc_cross + +echo "$ac_t""$ac_cv_prog_cc_works" 1>&6 +if test $ac_cv_prog_cc_works = no; then + { echo "configure: error: installation or configuration problem: C compiler cannot create executables." 1>&2; exit 1; } +fi +echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler""... $ac_c" 1>&6 +echo "configure:689: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler" >&5 +echo "$ac_t""$ac_cv_prog_cc_cross" 1>&6 +cross_compiling=$ac_cv_prog_cc_cross + +echo $ac_n "checking whether we are using GNU C""... $ac_c" 1>&6 +echo "configure:694: checking whether we are using GNU C" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_gcc'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.c <&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then + ac_cv_prog_gcc=yes +else + ac_cv_prog_gcc=no +fi +fi + +echo "$ac_t""$ac_cv_prog_gcc" 1>&6 + +if test $ac_cv_prog_gcc = yes; then + GCC=yes +else + GCC= +fi + +ac_test_CFLAGS="${CFLAGS+set}" +ac_save_CFLAGS="$CFLAGS" +CFLAGS= +echo $ac_n "checking whether ${CC-cc} accepts -g""... $ac_c" 1>&6 +echo "configure:722: checking whether ${CC-cc} accepts -g" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_cc_g'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + echo 'void f(){}' > conftest.c +if test -z "`${CC-cc} -g -c conftest.c 2>&1`"; then + ac_cv_prog_cc_g=yes +else + ac_cv_prog_cc_g=no +fi +rm -f conftest* + +fi + +echo "$ac_t""$ac_cv_prog_cc_g" 1>&6 +if test "$ac_test_CFLAGS" = set; then + CFLAGS="$ac_save_CFLAGS" +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi + +if test "x$CFLAGS" = "x" ; then + no_CFLAGS="yes" +fi +if test "x$no_CFLAGS" = "xyes" -a "x$GCC" = "xyes" ; then + CFLAGS="-Wall -pipe -g3" +fi +A_CFLAGS="" + +S_CFLAGS="-fPIC -DPIC" + +echo $ac_n "checking how to run the C preprocessor""... $ac_c" 1>&6 +echo "configure:764: checking how to run the C preprocessor" >&5 +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then +if eval "test \"`echo '$''{'ac_cv_prog_CPP'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + # This must be in double quotes, not single quotes, because CPP may get + # substituted into the Makefile and "${CC-cc}" will confuse make. + CPP="${CC-cc} -E" + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. + cat > conftest.$ac_ext < +Syntax Error +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:785: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + : +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + CPP="${CC-cc} -E -traditional-cpp" + cat > conftest.$ac_ext < +Syntax Error +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:802: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + : +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + CPP="${CC-cc} -nologo -E" + cat > conftest.$ac_ext < +Syntax Error +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:819: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + : +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + CPP=/lib/cpp +fi +rm -f conftest* +fi +rm -f conftest* +fi +rm -f conftest* + ac_cv_prog_CPP="$CPP" +fi + CPP="$ac_cv_prog_CPP" +else + ac_cv_prog_CPP="$CPP" +fi +echo "$ac_t""$CPP" 1>&6 + + +ac_aux_dir= +for ac_dir in ${GNUSYSTEM_AUX_DIR} $srcdir $srcdir/.. $srcdir/../..; do + if test -f $ac_dir/install-sh; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f $ac_dir/install.sh; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + fi +done +if test -z "$ac_aux_dir"; then + { echo "configure: error: can not find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." 1>&2; exit 1; } +fi +ac_config_guess=$ac_aux_dir/config.guess +ac_config_sub=$ac_aux_dir/config.sub +ac_configure=$ac_aux_dir/configure # This should be Cygnus configure. + + +# Make sure we can run config.sub. +if ${CONFIG_SHELL-/bin/sh} $ac_config_sub sun4 >/dev/null 2>&1; then : +else { echo "configure: error: can not run $ac_config_sub" 1>&2; exit 1; } +fi + +echo $ac_n "checking host system type""... $ac_c" 1>&6 +echo "configure:870: checking host system type" >&5 + +host_alias=$host +case "$host_alias" in +NONE) + case $nonopt in + NONE) + if host_alias=`${CONFIG_SHELL-/bin/sh} $ac_config_guess`; then : + else { echo "configure: error: can not guess host type; you must specify one" 1>&2; exit 1; } + fi ;; + *) host_alias=$nonopt ;; + esac ;; +esac + +host=`${CONFIG_SHELL-/bin/sh} $ac_config_sub $host_alias` +host_cpu=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` +host_vendor=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` +host_os=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` +echo "$ac_t""$host" 1>&6 + +case "${host}" in + *-*-darwin*) + CFLAGS="$CFLAGS -fno-common -no-cpp-precomp" + ABI="macho" + ;; + *-*-freebsd*) + ABI="elf" + ;; + *-*-linux* | *cygwin*) + if echo ${host} | grep -q cygwin ; then \ + echo "cygwin detected"; \ + S_CFLAGS=""; \ + echo "/* cygdef.h. Generated automatically by configure. */ +#ifndef _CYGDEF_H_ +#define _CYGDEF_H_ 1 +#include +#define __linux__ 1 + + +typedef void (*sig_t)(int); + + +#endif /* _CYGDEF_H_ */" > cygdef.h; \ + echo " + #define CYGWIN 1 +" > confdefs.h; \ + fi + ABI="elf" + ;; + *-*-netbsd*) + echo $ac_n "checking ABI""... $ac_c" 1>&6 +echo "configure:903: checking ABI" >&5 + cat > conftest.$ac_ext <&5 | + egrep "yes" >/dev/null 2>&1; then + rm -rf conftest* + ABI="elf" +else + rm -rf conftest* + ABI="aout" +fi +rm -f conftest* + + echo "$ac_t""$ABI" 1>&6 + ;; + *-*-solaris2*) + ABI="elf" + cat >> confdefs.h <<\EOF +#define SUNOS 1 +EOF + + ;; + *) + echo "$ac_t""Unsupported operating system: ${host}" 1>&6 + ABI="elf" + ;; +esac + +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# ./install, which can be erroneously created by make from ./install.sh. +echo $ac_n "checking for a BSD compatible install""... $ac_c" 1>&6 +echo "configure:949: checking for a BSD compatible install" >&5 +if test -z "$INSTALL"; then +if eval "test \"`echo '$''{'ac_cv_path_install'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + IFS="${IFS= }"; ac_save_IFS="$IFS"; IFS=":" + for ac_dir in $PATH; do + # Account for people who put trailing slashes in PATH elements. + case "$ac_dir/" in + /|./|.//|/etc/*|/usr/sbin/*|/usr/etc/*|/sbin/*|/usr/afsws/bin/*|/usr/ucb/*) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + # Don't use installbsd from OSF since it installs stuff as root + # by default. + for ac_prog in ginstall scoinst install; do + if test -f $ac_dir/$ac_prog; then + if test $ac_prog = install && + grep dspmsg $ac_dir/$ac_prog >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + : + else + ac_cv_path_install="$ac_dir/$ac_prog -c" + break 2 + fi + fi + done + ;; + esac + done + IFS="$ac_save_IFS" + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL="$ac_cv_path_install" + else + # As a last resort, use the slow shell script. We don't cache a + # path for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the path is relative. + INSTALL="$ac_install_sh" + fi +fi +echo "$ac_t""$INSTALL" 1>&6 + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL_PROGRAM}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + +# Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:1004: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_RANLIB'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_RANLIB="ranlib" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_prog_RANLIB" && ac_cv_prog_RANLIB=":" +fi +fi +RANLIB="$ac_cv_prog_RANLIB" +if test -n "$RANLIB"; then + echo "$ac_t""$RANLIB" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +# Extract the first word of "ar", so it can be a program name with args. +set dummy ar; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:1034: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_path_AR'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + case "$AR" in + /*) + ac_cv_path_AR="$AR" # Let the user override the test with a path. + ;; + ?:/*) + ac_cv_path_AR="$AR" # Let the user override the test with a dos path. + ;; + *) + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" + ac_dummy="$PATH" + for ac_dir in $ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_path_AR="$ac_dir/$ac_word" + break + fi + done + IFS="$ac_save_ifs" + ;; +esac +fi +AR="$ac_cv_path_AR" +if test -n "$AR"; then + echo "$ac_t""$AR" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + + +echo $ac_n "checking for tgetent in -ltermcap""... $ac_c" 1>&6 +echo "configure:1068: checking for tgetent in -ltermcap" >&5 +ac_lib_var=`echo termcap'_'tgetent | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-ltermcap $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo termcap | sed -e 's/[^a-zA-Z0-9_]/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +\ + echo $ac_n "checking for tgetent in -ltinfo""... $ac_c" 1>&6 +echo "configure:1114: checking for tgetent in -ltinfo" >&5 +ac_lib_var=`echo tinfo'_'tgetent | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-ltinfo $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo tinfo | sed -e 's/^a-zA-Z0-9_/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +\ + echo $ac_n "checking for tgetent in -lcurses""... $ac_c" 1>&6 +echo "configure:1160: checking for tgetent in -lcurses" >&5 +ac_lib_var=`echo curses'_'tgetent | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lcurses $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo curses | sed -e 's/^a-zA-Z0-9_/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +\ + echo $ac_n "checking for tgetent in -lncurses""... $ac_c" 1>&6 +echo "configure:1206: checking for tgetent in -lncurses" >&5 +ac_lib_var=`echo ncurses'_'tgetent | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lncurses $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo ncurses | sed -e 's/^a-zA-Z0-9_/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +\ + { echo "configure: error: termcap support not found" 1>&2; exit 1; } +fi + +fi + +fi + +fi + + +for ac_hdr in termcap.h +do +ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'` +echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6 +echo "configure:1265: checking for $ac_hdr" >&5 +if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1275: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + rm -rf conftest* + eval "ac_cv_header_$ac_safe=yes" +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_header_$ac_safe=no" +fi +rm -f conftest* +fi +if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'` + cat >> confdefs.h <&6 +\ + for ac_hdr in term.h +do +ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'` +echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6 +echo "configure:1303: checking for $ac_hdr" >&5 +if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1313: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + rm -rf conftest* + eval "ac_cv_header_$ac_safe=yes" +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_header_$ac_safe=no" +fi +rm -f conftest* +fi +if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'` + cat >> confdefs.h <&6 +\ + echo "$ac_t""Need term.h since termcap.h is missing" 1>&6 +fi +done + + for ac_hdr in curses.h +do +ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'` +echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6 +echo "configure:1345: checking for $ac_hdr" >&5 +if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1355: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + rm -rf conftest* + eval "ac_cv_header_$ac_safe=yes" +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_header_$ac_safe=no" +fi +rm -f conftest* +fi +if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'` + cat >> confdefs.h <&6 +\ + for ac_hdr in ncurses.h +do +ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'` +echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6 +echo "configure:1383: checking for $ac_hdr" >&5 +if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1393: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + rm -rf conftest* + eval "ac_cv_header_$ac_safe=yes" +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_header_$ac_safe=no" +fi +rm -f conftest* +fi +if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'` + cat >> confdefs.h <&6 +\ + echo "$ac_t""Need curses.h or ncurses.h" 1>&6 +fi +done + +fi +done + +fi +done + + +for ac_hdr in sys/cdefs.h vis.h +do +ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'` +echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6 +echo "configure:1432: checking for $ac_hdr" >&5 +if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1442: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"` +if test -z "$ac_err"; then + rm -rf conftest* + eval "ac_cv_header_$ac_safe=yes" +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_header_$ac_safe=no" +fi +rm -f conftest* +fi +if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'` + cat >> confdefs.h <&6 +fi +done + + +for ac_func in issetugid +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +echo "configure:1472: checking for $ac_func" >&5 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $ac_func(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:1500: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <&6 +fi +done + +for ac_func in strlcat +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +echo "configure:1527: checking for $ac_func" >&5 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $ac_func(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:1555: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <&6 +CCSRCS="$CCSRCS np/strlcat.c" +fi +done + +for ac_func in strlcpy +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +echo "configure:1583: checking for $ac_func" >&5 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $ac_func(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:1611: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <&6 +CCSRCS="$CCSRCS np/strlcpy.c" +fi +done + +for ac_func in fgetln +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +echo "configure:1639: checking for $ac_func" >&5 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $ac_func(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:1667: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <&6 +CCSRCS="$CCSRCS np/fgetln.c" +fi +done + +for ac_func in strvis +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +echo "configure:1695: checking for $ac_func" >&5 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $ac_func(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:1723: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <&6 +CCSRCS="$CCSRCS np/vis.c" +fi +done + +for ac_func in strunvis +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +echo "configure:1751: checking for $ac_func" >&5 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $ac_func(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:1779: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <&6 +CCSRCS="$CCSRCS np/unvis.c" +fi +done + + +cat > conftest.$ac_ext < +#ifdef __RCSID + yes +#endif + +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "yes" >/dev/null 2>&1; then + : +else + rm -rf conftest* + CPPFLAGS="$CPPFLAGS '-D__RCSID(x)='" +fi +rm -f conftest* + + +cat > conftest.$ac_ext < +#ifdef __COPYRIGHT + yes +#endif + +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "yes" >/dev/null 2>&1; then + : +else + rm -rf conftest* + CPPFLAGS="$CPPFLAGS '-D__COPYRIGHT(x)='" +fi +rm -f conftest* + + +cat > conftest.$ac_ext < +#ifdef __RENAME + yes +#endif + +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "yes" >/dev/null 2>&1; then + : +else + rm -rf conftest* + CPPFLAGS="$CPPFLAGS '-D__RENAME(x)='" +fi +rm -f conftest* + + +cat > conftest.$ac_ext < +#ifdef _DIAGASSERT + yes +#endif + +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "yes" >/dev/null 2>&1; then + : +else + rm -rf conftest* + CPPFLAGS="$CPPFLAGS '-D_DIAGASSERT(x)='" +fi +rm -f conftest* + + +# Check whether --enable-readline or --disable-readline was given. +if test "${enable_readline+set}" = set; then + enableval="$enable_readline" + if test "x$enable_readline" != "xyes" ; then + enable_readline="no" +fi + +else + enable_readline="yes" + +fi + + +# Check whether --enable-debug or --disable-debug was given. +if test "${enable_debug+set}" = set; then + enableval="$enable_debug" + if test "x$enable_debug" != "xyes" ; then + enable_debug="no" +fi + +else + enable_debug="no" + +fi + +if test "x$enable_debug" = "xyes" ; then + CPPFLAGS="$CPPFLAGS -DDEBUG_TTY -DDEBUG_KEY -DDEBUG_READ -DDEBUG" + CPPFLAGS="$CPPFLAGS -DDEBUG_REFRESH -DDEBUG_PASTE" +fi + + + +ACSRCS="common.c emacs.c vi.c" +BCSRCS="chared.c el.c hist.c key.c map.c parse.c prompt.c read.c refresh.c search.c sig.c term.c tty.c" +CCSRCS="$CCSRCS history.c tokenizer.c" + +AGCSRCS="fcns.c help.c" +BGCSRCS="editline.c" + +HDRS="chared.h el.h hist.h key.h map.h parse.h prompt.h refresh.h search.h sig.h sys.h term.h tokenizer.h tty.h" + +IHDRS="histedit.h" + +IHDR_LINKS= + +AGHDRS="common.h emacs.h vi.h" +BGHDRS="fcns.h help.h" + +HDR_DIRS="include" + +MAN3="editline.3" +MAN5="editrc.5" + +MAN3_LINKS= +for i in el_init.3 el_end.3 el_reset.3 el_gets.3 el_getc.3 el_push.3 \ + el_parse.3 el_set.3 el_get.3 el_source.3 el_resize.3 el_line.3 \ + el_insertstr.3 el_deletestr.3 history_init.3 history_end.3 \ + history.3 ; do + MAN3_LINKS="$MAN3_LINKS editline.3 $i" +done + +MAN_DIRS="man/man3 man/man5" + +LIB_DIRS="lib" +LIB_MAJOR="2" +LIB_MINOR="6" +LIB_A="libedit.a" +LIB_A_LINKS= + +if test "x$ABI" = "xelf" ; then + LIB_S="libedit.so.$LIB_MAJOR" + LIB_S_LINK="libedit.so" + LIB_S_LINKS="$LIB_S $LIB_S_LINK" + S_LDFLAGS="-shared" +elif test "x$ABI" = "xaout" ; then + LIB_S="libedit.so.$LIB_MAJOR.$LIB_MINOR" + LIB_S_LINKS= + S_LDFLAGS="-shared" +elif test "x$ABI" = "xmacho" ; then + S_LDFLAGS="-shared" + LIB_S="libedit.$LIB_MAJOR.dylib" + LIB_S_LINK="libedit.dylib" + LIB_S_LINKS="$LIB_S $LIB_S_LINK" + if test "x$prefix" = "xNONE" ; then + S_LDFLAGS="-undefined suppress -flat_namespace -dynamiclib -compatibility_version $LIB_MAJOR -current_version $LIB_MAJOR -install_name /usr/local/lib/$LIB_S" + else + S_LDFLAGS="-undefined suppress -flat_namespace -dynamiclib -compatibility_version $LIB_MAJOR -current_version $LIB_MAJOR -install_name $prefix/lib/$LIB_S" + fi +fi + +TEST="TEST/test" +TCSRCS="TEST/test.c" + +if test "x$enable_readline" = "xyes" ; then + CCSRCS="$CCSRCS readline.c" + IHDRS="$IHDRS readline/readline.h" + IHDR_LINKS="readline.h readline/history.h" + HDR_DIRS="$HDR_DIRS include/readline" + LIB_A_LINKS="$LIB_A_LINKS libedit.a libreadline.a" + if test "x$ABI" = "xelf" ; then + LIB_S_LINKS="$LIB_S_LINKS $LIB_S_LINK libreadline.so" + elif test "x$ABI" = "xaout" ; then + LIB_S_LINKS="$LIB_S_LINKS $LIB_S libreadline.so.$LIB_MAJOR.$LIB_MINOR" + elif test "x$ABI" = "xmacho" ; then + LIB_S_LINKS="$LIB_S_LINKS $LIB_S_LINK libreadline.dylib" + fi +fi + + + + + + + + + + + + + + + + + + + + + + + + + + + +trap '' 1 2 15 +cat > confcache <<\EOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs. It is not useful on other systems. +# If it contains results you don't want to keep, you may remove or edit it. +# +# By default, configure uses ./config.cache as the cache file, +# creating it if it does not exist already. You can give configure +# the --cache-file=FILE option to use a different cache file; that is +# what configure does when it calls configure scripts in +# subdirectories, so they share the cache. +# Giving --cache-file=/dev/null disables caching, for debugging configure. +# config.status only pays attention to the cache file if you give it the +# --recheck option to rerun configure. +# +EOF +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, don't put newlines in cache variables' values. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +(set) 2>&1 | + case `(ac_space=' '; set | grep ac_space) 2>&1` in + *ac_space=\ *) + # `set' does not quote correctly, so add quotes (double-quote substitution + # turns \\\\ into \\, and sed turns \\ into \). + sed -n \ + -e "s/'/'\\\\''/g" \ + -e "s/^\\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\\)=\\(.*\\)/\\1=\${\\1='\\2'}/p" + ;; + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n -e 's/^\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\)=\(.*\)/\1=${\1=\2}/p' + ;; + esac >> confcache +if cmp -s $cache_file confcache; then + : +else + if test -w $cache_file; then + echo "updating cache $cache_file" + cat confcache > $cache_file + else + echo "not updating unwritable cache $cache_file" + fi +fi +rm -f confcache + +trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15 + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Any assignment to VPATH causes Sun make to only execute +# the first set of double-colon rules, so remove it if not needed. +# If there is a colon in the path, we need to keep it. +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[^:]*$/d' +fi + +trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15 + +DEFS=-DHAVE_CONFIG_H + +# Without the "./", some shells look in PATH for config.status. +: ${CONFIG_STATUS=./config.status} + +echo creating $CONFIG_STATUS +rm -f $CONFIG_STATUS +cat > $CONFIG_STATUS </dev/null | sed 1q`: +# +# $0 $ac_configure_args +# +# Compiler output produced by configure, useful for debugging +# configure, is in ./config.log if it exists. + +ac_cs_usage="Usage: $CONFIG_STATUS [--recheck] [--version] [--help]" +for ac_option +do + case "\$ac_option" in + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + echo "running \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion" + exec \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion ;; + -version | --version | --versio | --versi | --vers | --ver | --ve | --v) + echo "$CONFIG_STATUS generated by autoconf version 2.13" + exit 0 ;; + -help | --help | --hel | --he | --h) + echo "\$ac_cs_usage"; exit 0 ;; + *) echo "\$ac_cs_usage"; exit 1 ;; + esac +done + +ac_given_srcdir=$srcdir +ac_given_INSTALL="$INSTALL" + +trap 'rm -fr `echo "Makefile config.h" | sed "s/:[^ ]*//g"` conftest*; exit 1' 1 2 15 +EOF +cat >> $CONFIG_STATUS < conftest.subs <<\\CEOF +$ac_vpsub +$extrasub +s%@SHELL@%$SHELL%g +s%@CFLAGS@%$CFLAGS%g +s%@CPPFLAGS@%$CPPFLAGS%g +s%@CXXFLAGS@%$CXXFLAGS%g +s%@FFLAGS@%$FFLAGS%g +s%@DEFS@%$DEFS%g +s%@LDFLAGS@%$LDFLAGS%g +s%@LIBS@%$LIBS%g +s%@exec_prefix@%$exec_prefix%g +s%@prefix@%$prefix%g +s%@program_transform_name@%$program_transform_name%g +s%@bindir@%$bindir%g +s%@sbindir@%$sbindir%g +s%@libexecdir@%$libexecdir%g +s%@datadir@%$datadir%g +s%@sysconfdir@%$sysconfdir%g +s%@sharedstatedir@%$sharedstatedir%g +s%@localstatedir@%$localstatedir%g +s%@libdir@%$libdir%g +s%@includedir@%$includedir%g +s%@oldincludedir@%$oldincludedir%g +s%@infodir@%$infodir%g +s%@mandir@%$mandir%g +s%@CC@%$CC%g +s%@A_CFLAGS@%$A_CFLAGS%g +s%@S_CFLAGS@%$S_CFLAGS%g +s%@CPP@%$CPP%g +s%@host@%$host%g +s%@host_alias@%$host_alias%g +s%@host_cpu@%$host_cpu%g +s%@host_vendor@%$host_vendor%g +s%@host_os@%$host_os%g +s%@INSTALL_PROGRAM@%$INSTALL_PROGRAM%g +s%@INSTALL_SCRIPT@%$INSTALL_SCRIPT%g +s%@INSTALL_DATA@%$INSTALL_DATA%g +s%@RANLIB@%$RANLIB%g +s%@AR@%$AR%g +s%@ACSRCS@%$ACSRCS%g +s%@BCSRCS@%$BCSRCS%g +s%@CCSRCS@%$CCSRCS%g +s%@AGCSRCS@%$AGCSRCS%g +s%@BGCSRCS@%$BGCSRCS%g +s%@HDRS@%$HDRS%g +s%@IHDRS@%$IHDRS%g +s%@IHDR_LINKS@%$IHDR_LINKS%g +s%@AGHDRS@%$AGHDRS%g +s%@BGHDRS@%$BGHDRS%g +s%@HDR_DIRS@%$HDR_DIRS%g +s%@MAN3@%$MAN3%g +s%@MAN5@%$MAN5%g +s%@MAN3_LINKS@%$MAN3_LINKS%g +s%@MAN_DIRS@%$MAN_DIRS%g +s%@LIB_DIRS@%$LIB_DIRS%g +s%@LIB_VER@%$LIB_VER%g +s%@LIB_A@%$LIB_A%g +s%@LIB_A_LINKS@%$LIB_A_LINKS%g +s%@LIB_S@%$LIB_S%g +s%@LIB_S_LINKS@%$LIB_S_LINKS%g +s%@S_LDFLAGS@%$S_LDFLAGS%g +s%@TEST@%$TEST%g +s%@TCSRCS@%$TCSRCS%g + +CEOF +EOF + +cat >> $CONFIG_STATUS <<\EOF + +# Split the substitutions into bite-sized pieces for seds with +# small command number limits, like on Digital OSF/1 and HP-UX. +ac_max_sed_cmds=90 # Maximum number of lines to put in a sed script. +ac_file=1 # Number of current file. +ac_beg=1 # First line for current file. +ac_end=$ac_max_sed_cmds # Line after last line for current file. +ac_more_lines=: +ac_sed_cmds="" +while $ac_more_lines; do + if test $ac_beg -gt 1; then + sed "1,${ac_beg}d; ${ac_end}q" conftest.subs > conftest.s$ac_file + else + sed "${ac_end}q" conftest.subs > conftest.s$ac_file + fi + if test ! -s conftest.s$ac_file; then + ac_more_lines=false + rm -f conftest.s$ac_file + else + if test -z "$ac_sed_cmds"; then + ac_sed_cmds="sed -f conftest.s$ac_file" + else + ac_sed_cmds="$ac_sed_cmds | sed -f conftest.s$ac_file" + fi + ac_file=`expr $ac_file + 1` + ac_beg=$ac_end + ac_end=`expr $ac_end + $ac_max_sed_cmds` + fi +done +if test -z "$ac_sed_cmds"; then + ac_sed_cmds=cat +fi +EOF + +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF +for ac_file in .. $CONFIG_FILES; do if test "x$ac_file" != x..; then + # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in". + case "$ac_file" in + *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'` + ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; + *) ac_file_in="${ac_file}.in" ;; + esac + + # Adjust a relative srcdir, top_srcdir, and INSTALL for subdirectories. + + # Remove last slash and all that follows it. Not all systems have dirname. + ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'` + if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then + # The file is in a subdirectory. + test ! -d "$ac_dir" && mkdir "$ac_dir" + ac_dir_suffix="/`echo $ac_dir|sed 's%^\./%%'`" + # A "../" for each directory in $ac_dir_suffix. + ac_dots=`echo $ac_dir_suffix|sed 's%/[^/]*%../%g'` + else + ac_dir_suffix= ac_dots= + fi + + case "$ac_given_srcdir" in + .) srcdir=. + if test -z "$ac_dots"; then top_srcdir=. + else top_srcdir=`echo $ac_dots|sed 's%/$%%'`; fi ;; + /*) srcdir="$ac_given_srcdir$ac_dir_suffix"; top_srcdir="$ac_given_srcdir" ;; + *) # Relative path. + srcdir="$ac_dots$ac_given_srcdir$ac_dir_suffix" + top_srcdir="$ac_dots$ac_given_srcdir" ;; + esac + + case "$ac_given_INSTALL" in + [/$]*) INSTALL="$ac_given_INSTALL" ;; + *) INSTALL="$ac_dots$ac_given_INSTALL" ;; + esac + + echo creating "$ac_file" + rm -f "$ac_file" + configure_input="Generated automatically from `echo $ac_file_in|sed 's%.*/%%'` by configure." + case "$ac_file" in + *Makefile*) ac_comsub="1i\\ +# $configure_input" ;; + *) ac_comsub= ;; + esac + + ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"` + sed -e "$ac_comsub +s%@configure_input@%$configure_input%g +s%@srcdir@%$srcdir%g +s%@top_srcdir@%$top_srcdir%g +s%@INSTALL@%$INSTALL%g +" $ac_file_inputs | (eval "$ac_sed_cmds") > $ac_file +fi; done +rm -f conftest.s* + +# These sed commands are passed to sed as "A NAME B NAME C VALUE D", where +# NAME is the cpp macro being defined and VALUE is the value it is being given. +# +# ac_d sets the value in "#define NAME VALUE" lines. +ac_dA='s%^\([ ]*\)#\([ ]*define[ ][ ]*\)' +ac_dB='\([ ][ ]*\)[^ ]*%\1#\2' +ac_dC='\3' +ac_dD='%g' +# ac_u turns "#undef NAME" with trailing blanks into "#define NAME VALUE". +ac_uA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)' +ac_uB='\([ ]\)%\1#\2define\3' +ac_uC=' ' +ac_uD='\4%g' +# ac_e turns "#undef NAME" without trailing blanks into "#define NAME VALUE". +ac_eA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)' +ac_eB='$%\1#\2define\3' +ac_eC=' ' +ac_eD='%g' + +if test "${CONFIG_HEADERS+set}" != set; then +EOF +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF +fi +for ac_file in .. $CONFIG_HEADERS; do if test "x$ac_file" != x..; then + # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in". + case "$ac_file" in + *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'` + ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; + *) ac_file_in="${ac_file}.in" ;; + esac + + echo creating $ac_file + + rm -f conftest.frag conftest.in conftest.out + ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"` + cat $ac_file_inputs > conftest.in + +EOF + +# Transform confdefs.h into a sed script conftest.vals that substitutes +# the proper values into config.h.in to produce config.h. And first: +# Protect against being on the right side of a sed subst in config.status. +# Protect against being in an unquoted here document in config.status. +rm -f conftest.vals +cat > conftest.hdr <<\EOF +s/[\\&%]/\\&/g +s%[\\$`]%\\&%g +s%#define \([A-Za-z_][A-Za-z0-9_]*\) *\(.*\)%${ac_dA}\1${ac_dB}\1${ac_dC}\2${ac_dD}%gp +s%ac_d%ac_u%gp +s%ac_u%ac_e%gp +EOF +sed -n -f conftest.hdr confdefs.h > conftest.vals +rm -f conftest.hdr + +# This sed command replaces #undef with comments. This is necessary, for +# example, in the case of _POSIX_SOURCE, which is predefined and required +# on some systems where configure will not decide to define it. +cat >> conftest.vals <<\EOF +s%^[ ]*#[ ]*undef[ ][ ]*[a-zA-Z_][a-zA-Z_0-9]*%/* & */% +EOF + +# Break up conftest.vals because some shells have a limit on +# the size of here documents, and old seds have small limits too. + +rm -f conftest.tail +while : +do + ac_lines=`grep -c . conftest.vals` + # grep -c gives empty output for an empty file on some AIX systems. + if test -z "$ac_lines" || test "$ac_lines" -eq 0; then break; fi + # Write a limited-size here document to conftest.frag. + echo ' cat > conftest.frag <> $CONFIG_STATUS + sed ${ac_max_here_lines}q conftest.vals >> $CONFIG_STATUS + echo 'CEOF + sed -f conftest.frag conftest.in > conftest.out + rm -f conftest.in + mv conftest.out conftest.in +' >> $CONFIG_STATUS + sed 1,${ac_max_here_lines}d conftest.vals > conftest.tail + rm -f conftest.vals + mv conftest.tail conftest.vals +done +rm -f conftest.vals + +cat >> $CONFIG_STATUS <<\EOF + rm -f conftest.frag conftest.h + echo "/* $ac_file. Generated automatically by configure. */" > conftest.h + cat conftest.in >> conftest.h + rm -f conftest.in + if cmp -s $ac_file conftest.h 2>/dev/null; then + echo "$ac_file is unchanged" + rm -f conftest.h + else + # Remove last slash and all that follows it. Not all systems have dirname. + ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'` + if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then + # The file is in a subdirectory. + test ! -d "$ac_dir" && mkdir "$ac_dir" + fi + rm -f $ac_file + mv conftest.h $ac_file + fi +fi; done + +EOF +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF + +exit 0 +EOF +chmod +x $CONFIG_STATUS +rm -fr confdefs* $ac_clean_files +test "$no_create" = yes || ${CONFIG_SHELL-/bin/sh} $CONFIG_STATUS || exit 1 + diff --git a/main/editline/configure.in b/main/editline/configure.in new file mode 100644 index 000000000..2e37d6b2a --- /dev/null +++ b/main/editline/configure.in @@ -0,0 +1,276 @@ +dnl +dnl Process this file with autoconf to produce a configure script. +dnl +AC_INIT(Makefile.in) + +dnl If CFLAGS isn't defined and using gcc, set CFLAGS to something reasonable. +dnl Otherwise, just prevent autoconf from molesting CFLAGS. +CFLAGS=$CFLAGS +AC_PROG_CC +if test "x$CFLAGS" = "x" ; then + no_CFLAGS="yes" +fi +if test "x$no_CFLAGS" = "xyes" -a "x$GCC" = "xyes" ; then + CFLAGS="-Wall -pipe -g3" +fi +A_CFLAGS="" +AC_SUBST(A_CFLAGS) +S_CFLAGS="-fPIC -DPIC" +AC_SUBST(S_CFLAGS) +AC_PROG_CPP + +dnl Platform-specific settings. The ABI can probably be determined +dnl programmatically, but doing so is error-prone, which makes it generally +dnl not worth the trouble. +AC_CANONICAL_HOST +case "${host}" in + *-*-darwin*) + CFLAGS="$CFLAGS -fno-common -no-cpp-precomp" + ABI="macho" + ;; + *-*-freebsd*) + ABI="elf" + ;; + *-*-linux* | *cygwin*) + if echo ${host} | grep -q cygwin ; then \ + echo "cygwin detected"; \ + S_CFLAGS=""; \ + echo "/* cygdef.h. Generated automatically by configure. */ +#ifndef _CYGDEF_H_ +#define _CYGDEF_H_ 1 +#include +#define __linux__ 1 + + +typedef void (*sig_t)(int); + + +#endif /* _CYGDEF_H_ */" > cygdef.h; \ + echo " + #define CYGWIN 1 +" > confdefs.h; \ + fi + ABI="elf" + ;; + *-*-netbsd*) + AC_MSG_CHECKING(ABI) + AC_EGREP_CPP(yes, +[#ifdef __ELF__ + yes +#endif +], + ABI="elf", + ABI="aout") + AC_MSG_RESULT($ABI) + ;; + *-*-solaris2*) + ABI="elf" + AC_DEFINE(SUNOS) + ;; + *) + AC_MSG_RESULT(Unsupported operating system: ${host}) + ABI="elf" + ;; +esac + +AC_PROG_INSTALL +AC_PROG_RANLIB +AC_PATH_PROG(AR, ar, , $PATH) + +dnl Search for termcap access routines in termcap, tinfo, curses, and ncurses. +AC_CHECK_LIB(termcap, tgetent, , \ + AC_CHECK_LIB(tinfo, tgetent, , \ + AC_CHECK_LIB(curses, tgetent, , \ + AC_CHECK_LIB(ncurses, tgetent, , \ + AC_MSG_ERROR(termcap support not found))))) + +dnl Use termcap.h if it exists; otherwise we need both term.h and [n]curses.h. +AC_CHECK_HEADERS(termcap.h, , \ + AC_CHECK_HEADERS(term.h, , \ + AC_MSG_RESULT(Need term.h since termcap.h is missing)) + AC_CHECK_HEADERS(curses.h, , \ + AC_CHECK_HEADERS(ncurses.h, , \ + AC_MSG_RESULT(Need curses.h or ncurses.h)))) + +AC_CHECK_HEADERS(sys/cdefs.h vis.h) + +AC_CHECK_FUNCS(issetugid) +AC_CHECK_FUNCS(strlcat, , CCSRCS="$CCSRCS np/strlcat.c") +AC_CHECK_FUNCS(strlcpy, , CCSRCS="$CCSRCS np/strlcpy.c") +AC_CHECK_FUNCS(fgetln, , CCSRCS="$CCSRCS np/fgetln.c") +AC_CHECK_FUNCS(strvis, , CCSRCS="$CCSRCS np/vis.c") +AC_CHECK_FUNCS(strunvis, , CCSRCS="$CCSRCS np/unvis.c") + +AC_EGREP_CPP(yes, +[#include +#ifdef __RCSID + yes +#endif +], , [CPPFLAGS="$CPPFLAGS '-D__RCSID(x)='"]) + +AC_EGREP_CPP(yes, +[#include +#ifdef __COPYRIGHT + yes +#endif +], , [CPPFLAGS="$CPPFLAGS '-D__COPYRIGHT(x)='"]) + +AC_EGREP_CPP(yes, +[#include +#ifdef __RENAME + yes +#endif +], , [CPPFLAGS="$CPPFLAGS '-D__RENAME(x)='"]) + +AC_EGREP_CPP(yes, +[#include +#ifdef _DIAGASSERT + yes +#endif +], , [CPPFLAGS="$CPPFLAGS '-D_DIAGASSERT(x)='"]) + +dnl Enable readline compatibility by default. +AC_ARG_ENABLE(readline, [ --disable-readline Disable readline compatibility], +if test "x$enable_readline" != "xyes" ; then + enable_readline="no" +fi +, +enable_readline="yes" +) + +dnl Optionally enable debugging. +AC_ARG_ENABLE(debug, [ --enable-debug Enable debugging code], +if test "x$enable_debug" != "xyes" ; then + enable_debug="no" +fi +, +enable_debug="no" +) +if test "x$enable_debug" = "xyes" ; then + CPPFLAGS="$CPPFLAGS -DDEBUG_TTY -DDEBUG_KEY -DDEBUG_READ -DDEBUG" + CPPFLAGS="$CPPFLAGS -DDEBUG_REFRESH -DDEBUG_PASTE" +else + CFLAGS="$CFLAGS -O" +fi + + +dnl +dnl File lists. This is done here instead of in the Makefile in order to avoid +dnl the need for conditionals. +dnl + +dnl .c files. +ACSRCS="common.c emacs.c vi.c" +BCSRCS="chared.c el.c hist.c key.c map.c parse.c prompt.c read.c refresh.c search.c sig.c term.c tty.c" +CCSRCS="$CCSRCS history.c tokenizer.c" + +dnl Generated .c files. +AGCSRCS="fcns.c help.c" +BGCSRCS="editline.c" + +dnl .h files. +HDRS="chared.h el.h hist.h key.h map.h parse.h prompt.h refresh.h search.h sig.h sys.h term.h tokenizer.h tty.h" + +dnl Installed .h files. +IHDRS="histedit.h" + +dnl Installed headers for readline compatibility. +IHDR_LINKS= + +dnl Generated .h files. +AGHDRS="common.h emacs.h vi.h" +BGHDRS="fcns.h help.h" + +dnl Header installation directories. +HDR_DIRS="include" + +dnl Man pages. +MAN3="editline.3" +MAN5="editrc.5" + +MAN3_LINKS= +for i in el_init.3 el_end.3 el_reset.3 el_gets.3 el_getc.3 el_push.3 \ + el_parse.3 el_set.3 el_get.3 el_source.3 el_resize.3 el_line.3 \ + el_insertstr.3 el_deletestr.3 history_init.3 history_end.3 \ + history.3 ; do + MAN3_LINKS="$MAN3_LINKS editline.3 $i" +done + +dnl Man page installation directories. +MAN_DIRS="man/man3 man/man5" + +dnl Library settings. +LIB_DIRS="lib" +LIB_MAJOR="2" +LIB_MINOR="6" +LIB_A="libedit.a" +LIB_A_LINKS= + +if test "x$ABI" = "xelf" ; then + LIB_S="libedit.so.$LIB_MAJOR" + LIB_S_LINK="libedit.so" + LIB_S_LINKS="$LIB_S $LIB_S_LINK" + S_LDFLAGS="-shared" +elif test "x$ABI" = "xaout" ; then + LIB_S="libedit.so.$LIB_MAJOR.$LIB_MINOR" + LIB_S_LINKS= + S_LDFLAGS="-shared" +elif test "x$ABI" = "xmacho" ; then + S_LDFLAGS="-shared" + LIB_S="libedit.$LIB_MAJOR.dylib" + LIB_S_LINK="libedit.dylib" + LIB_S_LINKS="$LIB_S $LIB_S_LINK" + if test "x$prefix" = "xNONE" ; then + S_LDFLAGS="-undefined suppress -flat_namespace -dynamiclib -compatibility_version $LIB_MAJOR -current_version $LIB_MAJOR -install_name /usr/local/lib/$LIB_S" + else + S_LDFLAGS="-undefined suppress -flat_namespace -dynamiclib -compatibility_version $LIB_MAJOR -current_version $LIB_MAJOR -install_name $prefix/lib/$LIB_S" + fi +fi + +dnl Test program. +TEST="TEST/test" +TCSRCS="TEST/test.c" + +dnl Add files to the lists if readline compatibility is enabled. +if test "x$enable_readline" = "xyes" ; then + CCSRCS="$CCSRCS readline.c" + IHDRS="$IHDRS readline/readline.h" + IHDR_LINKS="readline.h readline/history.h" + HDR_DIRS="$HDR_DIRS include/readline" + LIB_A_LINKS="$LIB_A_LINKS libedit.a libreadline.a" + if test "x$ABI" = "xelf" ; then + LIB_S_LINKS="$LIB_S_LINKS $LIB_S_LINK libreadline.so" + elif test "x$ABI" = "xaout" ; then + LIB_S_LINKS="$LIB_S_LINKS $LIB_S libreadline.so.$LIB_MAJOR.$LIB_MINOR" + elif test "x$ABI" = "xmacho" ; then + LIB_S_LINKS="$LIB_S_LINKS $LIB_S_LINK libreadline.dylib" + fi +fi + +AC_SUBST(ACSRCS) +AC_SUBST(BCSRCS) +AC_SUBST(CCSRCS) +AC_SUBST(AGCSRCS) +AC_SUBST(BGCSRCS) +AC_SUBST(HDRS) +AC_SUBST(IHDRS) +AC_SUBST(IHDR_LINKS) +AC_SUBST(AGHDRS) +AC_SUBST(BGHDRS) +AC_SUBST(HDR_DIRS) +AC_SUBST(MAN3) +AC_SUBST(MAN5) +AC_SUBST(MAN3_LINKS) +AC_SUBST(MAN_DIRS) +AC_SUBST(LIB_DIRS) +AC_SUBST(LIB_VER) +AC_SUBST(LIB_A) +AC_SUBST(LIB_A_LINKS) +AC_SUBST(LIB_S) +AC_SUBST(LIB_S_LINKS) +AC_SUBST(S_LDFLAGS) +AC_SUBST(TEST) +AC_SUBST(TCSRCS) + +AC_CONFIG_HEADER(config.h) +AC_OUTPUT(Makefile) diff --git a/main/editline/editline.3 b/main/editline/editline.3 new file mode 100644 index 000000000..28f6ddb84 --- /dev/null +++ b/main/editline/editline.3 @@ -0,0 +1,646 @@ +.\" $NetBSD: editline.3,v 1.25 2002/01/15 02:46:22 wiz Exp $ +.\" +.\" Copyright (c) 1997-1999 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This file was contributed to The NetBSD Foundation by Luke Mewburn. +.\" +.\" 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. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by the NetBSD +.\" Foundation, Inc. and its contributors. +.\" 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION OR CONTRIBUTORS +.\" 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. +.\" +.Dd November 12, 1999 +.Os +.Dt EDITLINE 3 +.Sh NAME +.Nm editline , +.Nm el_init , +.Nm el_end , +.Nm el_reset , +.Nm el_gets , +.Nm el_getc , +.Nm el_push , +.Nm el_parse , +.Nm el_set , +.Nm el_source , +.Nm el_resize , +.Nm el_line , +.Nm el_insertstr , +.Nm el_deletestr , +.Nm history_init , +.Nm history_end , +.Nm history +.Nd line editor and history functions +.Sh LIBRARY +.Lb libedit +.Sh SYNOPSIS +.Fd #include +.Ft EditLine * +.Fn el_init "const char *prog" "FILE *fin" "FILE *fout" "FILE *ferr" +.Ft void +.Fn el_end "EditLine *e" +.Ft void +.Fn el_reset "EditLine *e" +.Ft const char * +.Fn el_gets "EditLine *e" "int *count" +.Ft int +.Fn el_getc "EditLine *e" "char *ch" +.Ft void +.Fn el_push "EditLine *e" "const char *str" +.Ft int +.Fn el_parse "EditLine *e" "int argc" "char *argv[]" +.Ft int +.Fn el_set "EditLine *e" "int op" "..." +.Ft int +.Fn el_get "EditLine *e" "int op" "void *result" +.Ft int +.Fn el_source "EditLine *e" "const char *file" +.Ft void +.Fn el_resize "EditLine *e" +.Ft const LineInfo * +.Fn el_line "EditLine *e" +.Ft int +.Fn el_insertstr "EditLine *e" "const char *str" +.Ft void +.Fn el_deletestr "EditLine *e" "int count" +.Ft History * +.Fn history_init +.Ft void +.Fn history_end "History *h" +.Ft int +.Fn history "History *h" "HistEvent *ev" "int op" "..." +.Sh DESCRIPTION +The +.Nm +library provides generic line editing and history functions, +similar to those found in +.Xr sh 1 . +.Pp +These functions are available in the +.Nm libedit +library (which needs the +.Nm libtermcap +library). +Programs should be linked with +.Fl ledit ltermcap . +.Sh LINE EDITING FUNCTIONS +The line editing functions use a common data structure, +.Fa EditLine , +which is created by +.Fn el_init +and freed by +.Fn el_end . +.Pp +The following functions are available: +.Bl -tag -width 4n +.It Fn el_init +Initialise the line editor, and return a data structure +to be used by all other line editing functions. +.Fa prog +is the name of the invoking program, used when reading the +.Xr editrc 5 +file to determine which settings to use. +.Fa fin , +.Fa fout +and +.Fa ferr +are the input, output, and error streams (respectively) to use. +In this documentation, references to +.Dq the tty +are actually to this input/output stream combination. +.It Fn el_end +Clean up and finish with +.Fa e , +assumed to have been created with +.Fn el_init . +.It Fn el_reset +Reset the tty and the parser. +This should be called after an error which may have upset the tty's +state. +.It Fn el_gets +Read a line from the tty. +.Fa count +is modified to contain the number of characters read. +Returns the line read if successful, or +.Dv NULL +if no characters were read or if an error occurred. +.It Fn el_getc +Read a character from the tty. +.Fa ch +is modified to contain the character read. +Returns the number of characters read if successful, -1 otherwise. +.It Fn el_push +Pushes +.Fa str +back onto the input stream. +This is used by the macro expansion mechanism. +Refer to the description of +.Ic bind +.Fl s +in +.Xr editrc 5 +for more information. +.It Fn el_parse +Parses the +.Fa argv +array (which is +.Fa argc +elements in size) +to execute builtin +.Nm +commands. +If the command is prefixed with +.Dq prog : +then +.Fn el_parse +will only execute the command if +.Dq prog +matches the +.Fa prog +argument supplied to +.Fn el_init . +The return value is +-1 if the command is unknown, +0 if there was no error or +.Dq prog +didn't match, or +1 if the command returned an error. +Refer to +.Xr editrc 5 +for more information. +.It Fn el_set +Set +.Nm +parameters. +.Fa op +determines which parameter to set, and each operation has its +own parameter list. +.Pp +The following values for +.Fa op +are supported, along with the required argument list: +.Bl -tag -width 4n +.It Dv EL_PROMPT , Fa "char *(*f)(EditLine *)" +Define prompt printing function as +.Fa f , +which is to return a string that contains the prompt. +.It Dv EL_RPROMPT , Fa "char *(*f)(EditLine *)" +Define right side prompt printing function as +.Fa f , +which is to return a string that contains the prompt. +.It Dv EL_TERMINAL , Fa "const char *type" +Define terminal type of the tty to be +.Fa type , +or to +.Ev TERM +if +.Fa type +is +.Dv NULL . +.It Dv EL_EDITOR , Fa "const char *mode" +Set editing mode to +.Fa mode , +which must be one of +.Dq emacs +or +.Dq vi . +.It Dv EL_SIGNAL , Fa "int flag" +If +.Fa flag +is non-zero, +.Nm +will install its own signal handler for the following signals when +reading command input: +.Dv SIGCONT , +.Dv SIGHUP , +.Dv SIGINT , +.Dv SIGQUIT , +.Dv SIGSTOP , +.Dv SIGTERM , +.Dv SIGTSTP , +and +.Dv SIGWINCH . +Otherwise, the current signal handlers will be used. +.It Dv EL_BIND , Xo +.Fa "const char *" , +.Fa "..." , +.Dv NULL +.Xc +Perform the +.Ic bind +builtin command. +Refer to +.Xr editrc 5 +for more information. +.It Dv EL_ECHOTC , Xo +.Fa "const char *" , +.Fa "..." , +.Dv NULL +.Xc +Perform the +.Ic echotc +builtin command. +Refer to +.Xr editrc 5 +for more information. +.It Dv EL_SETTC , Xo +.Fa "const char *" , +.Fa "..." , +.Dv NULL +.Xc +Perform the +.Ic settc +builtin command. +Refer to +.Xr editrc 5 +for more information. +.It Dv EL_SETTY , Xo +.Fa "const char *" , +.Fa "..." , +.Dv NULL +.Xc +Perform the +.Ic setty +builtin command. +Refer to +.Xr editrc 5 +for more information. +.It Dv EL_TELLTC , Xo +.Fa "const char *" , +.Fa "..." , +.Dv NULL +.Xc +Perform the +.Ic telltc +builtin command. +Refer to +.Xr editrc 5 +for more information. +.It Dv EL_ADDFN , Xo +.Fa "const char *name" , +.Fa "const char *help" , +.Fa "unsigned char (*func)(EditLine *e, int ch) +.Xc +Add a user defined function, +.Fn func , +referred to as +.Fa name +which is invoked when a key which is bound to +.Fa name +is entered. +.Fa help +is a description of +.Fa name . +At invocation time, +.Fa ch +is the key which caused the invocation. +The return value of +.Fn func +should be one of: +.Bl -tag -width "CC_REDISPLAY" +.It Dv CC_NORM +Add a normal character. +.It Dv CC_NEWLINE +End of line was entered. +.It Dv CC_EOF +EOF was entered. +.It Dv CC_ARGHACK +Expecting further command input as arguments, do nothing visually. +.It Dv CC_REFRESH +Refresh display. +.It Dv CC_REFRESH_BEEP +Refresh display, and beep. +.It Dv CC_CURSOR +Cursor moved, so update and perform +.Dv CC_REFRESH . +.It Dv CC_REDISPLAY +Redisplay entire input line. +This is useful if a key binding outputs extra information. +.It Dv CC_ERROR +An error occurred. +Beep, and flush tty. +.It Dv CC_FATAL +Fatal error, reset tty to known state. +.El +.It Dv EL_HIST , Xo +.Fa "History *(*func)(History *, int op, ...)" , +.Fa "const char *ptr" +.Xc +Defines which history function to use, which is usually +.Fn history . +.Fa ptr +should be the value returned by +.Fn history_init . +.It Dv EL_EDITMODE , Fa "int flag" +If +.Fa flag +is non-zero, +editing is enabled (the default). +Note that this is only an indication, and does not +affect the operation of +.Nm "" . +At this time, it is the caller's responsibility to +check this +(using +.Fn el_get ) +to determine if editing should be enabled or not. +.It Dv EL_GETCFN , Fa "int (*f)(EditLine *, char *c)" +Define the character reading function as +.Fa f , +which is to return the number of characters read and store them in +.Fa c . +This function is called internally by +.Fn el_gets +and +.Fn el_getc . +The builtin function can be set or restored with the special function +name ``EL_BUILTIN_GETCFN''. +.It Dv EL_CLIENTDATA , Fa "void *data" +Register +.Fa data +to be associated with this EditLine structure. It can be retrieved with +the corresponding +.Fn el_get +call. +.El +.It Fn el_get +Get +.Nm +parameters. +.Fa op +determines which parameter to retrieve into +.Fa result . +.Pp +The following values for +.Fa op +are supported, along with actual type of +.Fa result : +.Bl -tag -width 4n +.It Dv EL_PROMPT , Fa "char *(*f)(EditLine *)" +Return a pointer to the function that displays the prompt. +.It Dv EL_RPROMPT , Fa "char *(*f)(EditLine *)" +Return a pointer to the function that displays the rightside prompt. +.It Dv EL_EDITOR , Fa "const char *" +Return the name of the editor, which will be one of +.Dq emacs +or +.Dq vi . +.It Dv EL_SIGNAL , Fa "int *" +Return non-zero if +.Nm +has installed private signal handlers (see +.Fn el_get +above). +.It Dv EL_EDITMODE, Fa "int *" +Return non-zero if editing is enabled. +.It Dv EL_GETCFN, Fa "int (**f)(EditLine *, char *)" +Return a pointer to the function that read characters, which is equal to +``EL_BUILTIN_GETCFN'' in the case of the default builtin function. +.It Dv EL_CLIENTDATA , Fa "void **data" +Retrieve +.Fa data +previously registered with the corresponding +.Fn el_set +call. +.El +.It Fn el_source +Initialise +.Nm +by reading the contents of +.Fa file . +.Fn el_parse +is called for each line in +.Fa file . +If +.Fa file +is +.Dv NULL , +try +.Pa $PWD/.editrc +then +.Pa $HOME/.editrc . +Refer to +.Xr editrc 5 +for details on the format of +.Fa file . +.It Fn el_resize +Must be called if the terminal size changes. +If +.Dv EL_SIGNAL +has been set with +.Fn el_set , +then this is done automatically. +Otherwise, it's the responsibility of the application to call +.Fn el_resize +on the appropriate occasions. +.It Fn el_line +Return the editing information for the current line in a +.Fa LineInfo +structure, which is defined as follows: +.Bd -literal +typedef struct lineinfo { + const char *buffer; /* address of buffer */ + const char *cursor; /* address of cursor */ + const char *lastchar; /* address of last character */ +} LineInfo; +.Ed +.It Fn el_insertstr +Insert +.Fa str +into the line at the cursor. +Returns -1 if +.Fa str +is empty or won't fit, and 0 otherwise. +.It Fn el_deletestr +Delete +.Fa num +characters before the cursor. +.El +.Sh HISTORY LIST FUNCTIONS +The history functions use a common data structure, +.Fa History , +which is created by +.Fn history_init +and freed by +.Fn history_end . +.Pp +The following functions are available: +.Bl -tag -width 4n +.It Fn history_init +Initialise the history list, and return a data structure +to be used by all other history list functions. +.It Fn history_end +Clean up and finish with +.Fa h , +assumed to have been created with +.Fn history_init . +.It Fn history +Perform operation +.Fa op +on the history list, with optional arguments as needed by the +operation. +.Fa ev +is changed accordingly to operation. +The following values for +.Fa op +are supported, along with the required argument list: +.Bl -tag -width 4n +.It Dv H_SETSIZE , Fa "int size" +Set size of history to +.Fa size +elements. +.It Dv H_GETSIZE +Get number of events currently in history. +.It Dv H_END +Cleans up and finishes with +.Fa h , +assumed to be created with +.Fn history_init . +.It Dv H_CLEAR +Clear the history. +.It Dv H_FUNC , Xo +.Fa "void *ptr" , +.Fa "history_gfun_t first" , +.Fa "history_gfun_t next" , +.Fa "history_gfun_t last" , +.Fa "history_gfun_t prev" , +.Fa "history_gfun_t curr" , +.Fa "history_sfun_t set" , +.Fa "history_vfun_t clear" , +.Fa "history_efun_t enter" , +.Fa "history_efun_t add" +.Xc +Define functions to perform various history operations. +.Fa ptr +is the argument given to a function when it's invoked. +.It Dv H_FIRST +Return the first element in the history. +.It Dv H_LAST +Return the last element in the history. +.It Dv H_PREV +Return the previous element in the history. +.It Dv H_NEXT +Return the next element in the history. +.It Dv H_CURR +Return the current element in the history. +.It Dv H_SET +Set the cursor to point to the requested element. +.It Dv H_ADD , Fa "const char *str" +Append +.Fa str +to the current element of the history, or create an element with +.It Dv H_APPEND , Fa "const char *str" +Append +.Fa str +to the last new element of the history. +.It Dv H_ENTER , Fa "const char *str" +Add +.Fa str +as a new element to the history, and, if necessary, +removing the oldest entry to keep the list to the created size. +.It Dv H_PREV_STR , Fa "const char *str" +Return the closest previous event that starts with +.Fa str . +.It Dv H_NEXT_STR , Fa "const char *str" +Return the closest next event that starts with +.Fa str . +.It Dv H_PREV_EVENT , Fa "int e" +Return the previous event numbered +.Fa e . +.It Dv H_NEXT_EVENT , Fa "int e" +Return the next event numbered +.Fa e . +.It Dv H_LOAD , Fa "const char *file" +Load the history list stored in +.Fa file . +.It Dv H_SAVE , Fa "const char *file" +Save the history list to +.Fa file . +.El +.Pp +.Fn history +returns 0 if the operation +.Fa op +succeeds. Otherwise, -1 is returned and +.Fa ev +is updated to contain more details about the error. +.El +.\"XXX.Sh EXAMPLES +.\"XXX: provide some examples +.Sh SEE ALSO +.Xr sh 1 , +.Xr signal 3 , +.Xr termcap 3 , +.Xr editrc 5 +.Sh HISTORY +The +.Nm +library first appeared in +.Bx 4.4 . +.Dv CC_REDISPLAY +appeared in +.Nx 1.3 . +.Dv CC_REFRESH_BEEP , +.Dv EL_EDITMODE +and the readline emulation appeared in +.Nx 1.4 . +.Dv EL_RPROMPT +appeared in +.Nx 1.5 . +.Sh AUTHORS +The +.Nm +library was written by Christos Zoulas. +Luke Mewburn wrote this manual and implemented +.Dv CC_REDISPLAY , +.Dv CC_REFRESH_BEEP , +.Dv EL_EDITMODE , +and +.Dv EL_RPROMPT . +Jaromir Dolecek implemented the readline emulation. +.Sh BUGS +The tokenization functions are not publicly defined in +.Fd . +.Pp +At this time, it is the responsibility of the caller to +check the result of the +.Dv EL_EDITMODE +operation of +.Fn el_get +(after an +.Fn el_source +or +.Fn el_parse ) +to determine if +.Nm +should be used for further input. +I.e., +.Dv EL_EDITMODE +is purely an indication of the result of the most recent +.Xr editrc 5 +.Ic edit +command. diff --git a/main/editline/editrc.5 b/main/editline/editrc.5 new file mode 100644 index 000000000..ddd12897b --- /dev/null +++ b/main/editline/editrc.5 @@ -0,0 +1,491 @@ +.\" $NetBSD: editrc.5,v 1.12 2002/01/15 02:46:44 wiz Exp $ +.\" +.\" Copyright (c) 1997-2000 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This file was contributed to The NetBSD Foundation by Luke Mewburn. +.\" +.\" 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. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by the NetBSD +.\" Foundation, Inc. and its contributors. +.\" 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION OR CONTRIBUTORS +.\" 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. +.\" +.Dd November 8, 2000 +.Os +.Dt EDITRC 5 +.Sh NAME +.Nm editrc +.Nd configuration file for editline library +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +The +.Nm +file defines various settings to be used by the +.Xr editline 3 +library. +.Pp +The format of each line is: +.Dl [prog:]command [arg [...]] +.Pp +.Ar command +is one of the +.Xr editline 3 +builtin commands. +Refer to +.Sx BUILTIN COMMANDS +for more information. +.Pp +.Ar prog +is the program name string that a program defines when it calls +.Xr el_init 3 +to setup +.Xr editline 3 , +which is usually +.Va argv[0] . +.Ar command +will be executed for any program which matches +.Ar prog . +.Pp +.Ar prog +may also be a +.Xr regex 3 +style +regular expression, in which case +.Ar command +will be executed for any program that matches the regular expression. +.Pp +If +.Ar prog +is absent, +.Ar command +is executed for all programs. +.Sh BUILTIN COMMANDS +The +.Nm editline +library has some builtin commands, which affect the way +that the line editing and history functions operate. +These are based on similar named builtins present in the +.Xr tcsh 1 +shell. +.Pp +The following builtin commands are available: +.Bl -tag -width 4n +.It Ic bind Xo +.Op Fl a +.Op Fl e +.Op Fl k +.Op Fl l +.Op Fl r +.Op Fl s +.Op Fl v +.Op Ar key Op Ar command +.Xc +Without options, list all bound keys, and the editor command to which +each is bound. +If +.Ar key +is supplied, show the bindings for +.Ar key . +If +.Ar key command +is supplied, bind +.Ar command +to +.Ar key . +Options include: +.Bl -tag -width 4n +.It Fl e +Bind all keys to the standard GNU Emacs-like bindings. +.It Fl v +Bind all keys to the standard +.Xr vi 1 -like +bindings. +.It Fl a +List or change key bindings in the +.Xr vi 1 +mode alternate (command mode) key map. +.It Fl k +.Ar key +is interpreted as a symbolic arrow key name, which may be one of +.Sq up , +.Sq down , +.Sq left +or +.Sq right . +.It Fl l +List all editor commands and a short description of each. +.It Fl r +Remove a key's binding. +.It Fl s +.Ar command +is taken as a literal string and treated as terminal input when +.Ar key +is typed. +Bound keys in +.Ar command +are themselves reinterpreted, and this continues for ten levels of +interpretation. +.El +.Pp +.Ar command +may be one of the commands documented in +.Sx "EDITOR COMMANDS" +below, or another key. +.Pp +.Ar key +and +.Ar command +can contain control characters of the form +.Sm off +.Sq No ^ Ar character +.Sm on +.Po +e.g. +.Sq ^A +.Pc , +and the following backslashed escape sequences: +.Pp +.Bl -tag -compact -offset indent -width 4n +.It Ic \ea +Bell +.It Ic \eb +Backspace +.It Ic \ee +Escape +.It Ic \ef +Formfeed +.It Ic \en +Newline +.It Ic \er +Carriage return +.It Ic \et +Horizontal tab +.It Ic \ev +Vertical tab +.Sm off +.It Sy \e Ar nnn +.Sm on +The ASCII character corresponding to the octal number +.Ar nnn . +.El +.Pp +.Sq \e +nullifies the special meaning of the following character, +if it has any, notably +.Sq \e +and +.Sq ^ . +.It Ic echotc Xo +.Op Fl sv +.Ar arg +.Ar ... +.Xc +Exercise terminal capabilities given in +.Ar arg Ar ... . +If +.Ar arg +is +.Sq baud , +.Sq cols , +.Sq lines , +.Sq rows , +.Sq meta or +.Sq tabs , +the value of that capability is printed, with +.Dq yes +or +.Dq no +indicating that the terminal does or does not have that capability. +.Pp +.Fl s +returns an emptry string for non-existent capabilities, rather than +causing an error. +.Fl v +causes messages to be verbose. +.It Ic edit Op Li on | Li off +Enable or disable the +.Nm editline +functionality in a program. +.It Ic history +List the history. +.It Ic telltc +List the values of all the terminal capabilities (see +.Xr termcap 5 ) . +.It Ic settc Ar cap Ar val +Set the terminal capability +.Ar cap +to +.Ar val , +as defined in +.Xr termcap 5 . +No sanity checking is done. +.It Ic setty Xo +.Op Fl a +.Op Fl d +.Op Fl q +.Op Fl x +.Op Ar +mode +.Op Ar -mode +.Op Ar mode +.Xc +Control which tty modes that +.Nm +won't allow the user to change. +.Fl d , +.Fl q +or +.Fl x +tells +.Ic setty +to act on the +.Sq edit , +.Sq quote +or +.Sq execute +set of tty modes respectively; defaulting to +.Fl x . +.Pp +Without other arguments, +.Ic setty +lists the modes in the chosen set which are fixed on +.Po +.Sq +mode +.Pc +or off +.Po +.Sq -mode +.Pc . +.Fl a +lists all tty modes in the chosen set regardless of the setting. +With +.Ar +mode , +.Ar -mode +or +.Ar mode , +fixes +.Ar mode +on or off or removes control of +.Ar mode +in the chosen set. +.El +.Sh EDITOR COMMANDS +The following editor commands are available for use in key bindings: +.\" Section automatically generated with makelist +.Bl -tag -width 4n +.It Ic vi-paste-next +Vi paste previous deletion to the right of the cursor. +.It Ic vi-paste-prev +Vi paste previous deletion to the left of the cursor. +.It Ic vi-prev-space-word +Vi move to the previous space delimited word. +.It Ic vi-prev-word +Vi move to the previous word. +.It Ic vi-next-space-word +Vi move to the next space delimited word. +.It Ic vi-next-word +Vi move to the next word. +.It Ic vi-change-case +Vi change case of character under the cursor and advance one character. +.It Ic vi-change-meta +Vi change prefix command. +.It Ic vi-insert-at-bol +Vi enter insert mode at the beginning of line. +.It Ic vi-replace-char +Vi replace character under the cursor with the next character typed. +.It Ic vi-replace-mode +Vi enter replace mode. +.It Ic vi-substitute-char +Vi replace character under the cursor and enter insert mode. +.It Ic vi-substitute-line +Vi substitute entire line. +.It Ic vi-change-to-eol +Vi change to end of line. +.It Ic vi-insert +Vi enter insert mode. +.It Ic vi-add +Vi enter insert mode after the cursor. +.It Ic vi-add-at-eol +Vi enter insert mode at end of line. +.It Ic vi-delete-meta +Vi delete prefix command. +.It Ic vi-end-word +Vi move to the end of the current space delimited word. +.It Ic vi-to-end-word +Vi move to the end of the current word. +.It Ic vi-undo +Vi undo last change. +.It Ic vi-command-mode +Vi enter command mode (use alternative key bindings). +.It Ic vi-zero +Vi move to the beginning of line. +.It Ic vi-delete-prev-char +Vi move to previous character (backspace). +.It Ic vi-list-or-eof +Vi list choices for completion or indicate end of file if empty line. +.It Ic vi-kill-line-prev +Vi cut from beginning of line to cursor. +.It Ic vi-search-prev +Vi search history previous. +.It Ic vi-search-next +Vi search history next. +.It Ic vi-repeat-search-next +Vi repeat current search in the same search direction. +.It Ic vi-repeat-search-prev +Vi repeat current search in the opposite search direction. +.It Ic vi-next-char +Vi move to the character specified next. +.It Ic vi-prev-char +Vi move to the character specified previous. +.It Ic vi-to-next-char +Vi move up to the character specified next. +.It Ic vi-to-prev-char +Vi move up to the character specified previous. +.It Ic vi-repeat-next-char +Vi repeat current character search in the same search direction. +.It Ic vi-repeat-prev-char +Vi repeat current character search in the opposite search direction. +.It Ic em-delete-or-list +Delete character under cursor or list completions if at end of line. +.It Ic em-delete-next-word +Cut from cursor to end of current word. +.It Ic em-yank +Paste cut buffer at cursor position. +.It Ic em-kill-line +Cut the entire line and save in cut buffer. +.It Ic em-kill-region +Cut area between mark and cursor and save in cut buffer. +.It Ic em-copy-region +Copy area between mark and cursor to cut buffer. +.It Ic em-gosmacs-traspose +Exchange the two characters before the cursor. +.It Ic em-next-word +Move next to end of current word. +.It Ic em-upper-case +Uppercase the characters from cursor to end of current word. +.It Ic em-capitol-case +Capitalize the characters from cursor to end of current word. +.It Ic em-lower-case +Lowercase the characters from cursor to end of current word. +.It Ic em-set-mark +Set the mark at cursor. +.It Ic em-exchange-mark +Exchange the cursor and mark. +.It Ic em-universal-argument +Universal argument (argument times 4). +.It Ic em-meta-next +Add 8th bit to next character typed. +.It Ic em-toggle-overwrite +Switch from insert to overwrite mode or vice versa. +.It Ic em-copy-prev-word +Copy current word to cursor. +.It Ic em-inc-search-next +Emacs incremental next search. +.It Ic em-inc-search-prev +Emacs incremental reverse search. +.It Ic ed-end-of-file +Indicate end of file. +.It Ic ed-insert +Add character to the line. +.It Ic ed-delete-prev-word +Delete from beginning of current word to cursor. +.It Ic ed-delete-next-char +Delete character under cursor. +.It Ic ed-kill-line +Cut to the end of line. +.It Ic ed-move-to-end +Move cursor to the end of line. +.It Ic ed-move-to-beg +Move cursor to the beginning of line. +.It Ic ed-transpose-chars +Exchange the character to the left of the cursor with the one under it. +.It Ic ed-next-char +Move to the right one character. +.It Ic ed-prev-word +Move to the beginning of the current word. +.It Ic ed-prev-char +Move to the left one character. +.It Ic ed-quoted-insert +Add the next character typed verbatim. +.It Ic ed-digit +Adds to argument or enters a digit. +.It Ic ed-argument-digit +Digit that starts argument. +.It Ic ed-unassigned +Indicates unbound character. +.It Ic ed-tty-sigint +Tty interrupt character. +.It Ic ed-tty-dsusp +Tty delayed suspend character. +.It Ic ed-tty-flush-output +Tty flush output characters. +.It Ic ed-tty-sigquit +Tty quit character. +.It Ic ed-tty-sigtstp +Tty suspend character. +.It Ic ed-tty-stop-output +Tty disallow output characters. +.It Ic ed-tty-start-output +Tty allow output characters. +.It Ic ed-newline +Execute command. +.It Ic ed-delete-prev-char +Delete the character to the left of the cursor. +.It Ic ed-clear-screen +Clear screen leaving current line at the top. +.It Ic ed-redisplay +Redisplay everything. +.It Ic ed-start-over +Erase current line and start from scratch. +.It Ic ed-sequence-lead-in +First character in a bound sequence. +.It Ic ed-prev-history +Move to the previous history line. +.It Ic ed-next-history +Move to the next history line. +.It Ic ed-search-prev-history +Search previous in history for a line matching the current. +.It Ic ed-search-next-history +Search next in history for a line matching the current. +.It Ic ed-prev-line +Move up one line. +.It Ic ed-next-line +Move down one line. +.It Ic ed-command +Editline extended command. +.El +.\" End of section automatically generated with makelist +.Sh SEE ALSO +.Xr editline 3 , +.Xr regex 3 , +.Xr termcap 5 +.Sh AUTHORS +The +.Nm editline +library was written by Christos Zoulas, +and this manual was written by Luke Mewburn, +with some sections inspired by +.Xr tcsh 1 . diff --git a/main/editline/el.c b/main/editline/el.c new file mode 100644 index 000000000..514316fbe --- /dev/null +++ b/main/editline/el.c @@ -0,0 +1,509 @@ +/* $NetBSD: el.c,v 1.29 2002/03/18 16:00:52 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)el.c 8.2 (Berkeley) 1/3/94"; +#else +__RCSID("$NetBSD: el.c,v 1.29 2002/03/18 16:00:52 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * el.c: EditLine interface functions + */ +#include +#include +#include +#include +#include +#include "el.h" + +/* el_init(): + * Initialize editline and set default parameters. + */ +public EditLine * +el_init(const char *prog, FILE *fin, FILE *fout, FILE *ferr) +{ + + EditLine *el = (EditLine *) el_malloc(sizeof(EditLine)); + + if (el == NULL) + return (NULL); + + memset(el, 0, sizeof(EditLine)); + + el->el_infd = fileno(fin); + el->el_outfile = fout; + el->el_errfile = ferr; + el->el_prog = strdup(prog); + + /* + * Initialize all the modules. Order is important!!! + */ + el->el_flags = 0; + + if (term_init(el) == -1) { + free(el->el_prog); + el_free(el); + return NULL; + } + (void) key_init(el); + (void) map_init(el); + if (tty_init(el) == -1) + el->el_flags |= NO_TTY; + (void) ch_init(el); + (void) search_init(el); + (void) hist_init(el); + (void) prompt_init(el); + (void) sig_init(el); + (void) read_init(el); + + return (el); +} + + +/* el_end(): + * Clean up. + */ +public void +el_end(EditLine *el) +{ + + if (el == NULL) + return; + + el_reset(el); + + term_end(el); + key_end(el); + map_end(el); + tty_end(el); + ch_end(el); + search_end(el); + hist_end(el); + prompt_end(el); + sig_end(el); + + if (el->el_prog) + el_free((ptr_t) el->el_prog); + el_free((ptr_t) el); +} + + +/* el_reset(): + * Reset the tty and the parser + */ +public void +el_reset(EditLine *el) +{ + + tty_cookedmode(el); + ch_reset(el); /* XXX: Do we want that? */ +} + + +/* el_set(): + * set the editline parameters + */ +public int +el_set(EditLine *el, int op, ...) +{ + va_list va; + int rv = 0; + + if (el == NULL) + return (-1); + va_start(va, op); + + switch (op) { + case EL_PROMPT: + case EL_RPROMPT: + rv = prompt_set(el, va_arg(va, el_pfunc_t), op); + break; + + case EL_TERMINAL: + rv = term_set(el, va_arg(va, char *)); + break; + + case EL_EDITOR: + rv = map_set_editor(el, va_arg(va, char *)); + break; + + case EL_SIGNAL: + if (va_arg(va, int)) + el->el_flags |= HANDLE_SIGNALS; + else + el->el_flags &= ~HANDLE_SIGNALS; + break; + + case EL_BIND: + case EL_TELLTC: + case EL_SETTC: + case EL_ECHOTC: + case EL_SETTY: + { + const char *argv[20]; + int i; + + for (i = 1; i < 20; i++) + if ((argv[i] = va_arg(va, char *)) == NULL) + break; + + switch (op) { + case EL_BIND: + argv[0] = "bind"; + rv = map_bind(el, i, argv); + break; + + case EL_TELLTC: + argv[0] = "telltc"; + rv = term_telltc(el, i, argv); + break; + + case EL_SETTC: + argv[0] = "settc"; + rv = term_settc(el, i, argv); + break; + + case EL_ECHOTC: + argv[0] = "echotc"; + rv = term_echotc(el, i, argv); + break; + + case EL_SETTY: + argv[0] = "setty"; + rv = tty_stty(el, i, argv); + break; + + default: + rv = -1; + EL_ABORT((el->el_errfile, "Bad op %d\n", op)); + break; + } + break; + } + + case EL_ADDFN: + { + char *name = va_arg(va, char *); + char *help = va_arg(va, char *); + el_func_t func = va_arg(va, el_func_t); + + rv = map_addfunc(el, name, help, func); + break; + } + + case EL_HIST: + { + hist_fun_t func = va_arg(va, hist_fun_t); + ptr_t ptr = va_arg(va, char *); + + rv = hist_set(el, func, ptr); + break; + } + + case EL_EDITMODE: + if (va_arg(va, int)) + el->el_flags &= ~EDIT_DISABLED; + else + el->el_flags |= EDIT_DISABLED; + rv = 0; + break; + + case EL_GETCFN: + { + el_rfunc_t rc = va_arg(va, el_rfunc_t); + rv = el_read_setfn(el, rc); + break; + } + + case EL_CLIENTDATA: + el->el_data = va_arg(va, void *); + break; + + default: + rv = -1; + break; + } + + va_end(va); + return (rv); +} + + +/* el_get(): + * retrieve the editline parameters + */ +public int +el_get(EditLine *el, int op, void *ret) +{ + int rv; + + if (el == NULL || ret == NULL) + return (-1); + switch (op) { + case EL_PROMPT: + case EL_RPROMPT: + rv = prompt_get(el, (el_pfunc_t *) & ret, op); + break; + + case EL_EDITOR: + rv = map_get_editor(el, (const char **) &ret); + break; + + case EL_SIGNAL: + *((int *) ret) = (el->el_flags & HANDLE_SIGNALS); + rv = 0; + break; + + case EL_EDITMODE: + *((int *) ret) = (!(el->el_flags & EDIT_DISABLED)); + rv = 0; + break; + +#if 0 /* XXX */ + case EL_TERMINAL: + rv = term_get(el, (const char *) &ret); + break; + + case EL_BIND: + case EL_TELLTC: + case EL_SETTC: + case EL_ECHOTC: + case EL_SETTY: + { + char *argv[20]; + int i; + + for (i = 1; i < 20; i++) + if ((argv[i] = va_arg(va, char *)) == NULL) + break; + + switch (op) { + case EL_BIND: + argv[0] = "bind"; + rv = map_bind(el, i, argv); + break; + + case EL_TELLTC: + argv[0] = "telltc"; + rv = term_telltc(el, i, argv); + break; + + case EL_SETTC: + argv[0] = "settc"; + rv = term_settc(el, i, argv); + break; + + case EL_ECHOTC: + argv[0] = "echotc"; + rv = term_echotc(el, i, argv); + break; + + case EL_SETTY: + argv[0] = "setty"; + rv = tty_stty(el, i, argv); + break; + + default: + rv = -1; + EL_ABORT((el->errfile, "Bad op %d\n", op)); + break; + } + break; + } + + case EL_ADDFN: + { + char *name = va_arg(va, char *); + char *help = va_arg(va, char *); + el_func_t func = va_arg(va, el_func_t); + + rv = map_addfunc(el, name, help, func); + break; + } + + case EL_HIST: + { + hist_fun_t func = va_arg(va, hist_fun_t); + ptr_t ptr = va_arg(va, char *); + rv = hist_set(el, func, ptr); + } + break; +#endif /* XXX */ + + case EL_GETCFN: + *((el_rfunc_t *)ret) = el_read_getfn(el); + rv = 0; + break; + + case EL_CLIENTDATA: + *((void **)ret) = el->el_data; + rv = 0; + break; + + default: + rv = -1; + } + + return (rv); +} + + +/* el_line(): + * Return editing info + */ +public const LineInfo * +el_line(EditLine *el) +{ + + return (const LineInfo *) (void *) &el->el_line; +} + + +/* el_source(): + * Source a file + */ +public int +el_source(EditLine *el, const char *fname) +{ + FILE *fp; + size_t len; + char *ptr; + + fp = NULL; + if (fname == NULL) { +#ifdef HAVE_ISSETUGID + static const char elpath[] = "/.editrc"; + char path[MAXPATHLEN]; + + if (issetugid()) + return (-1); + if ((ptr = getenv("HOME")) == NULL) + return (-1); + if (strlcpy(path, ptr, sizeof(path)) >= sizeof(path)) + return (-1); + if (strlcat(path, elpath, sizeof(path)) >= sizeof(path)) + return (-1); + fname = path; +#else + /* + * If issetugid() is missing, always return an error, in order + * to keep from inadvertently opening up the user to a security + * hole. + */ + return (-1); +#endif + } + if (fp == NULL) + fp = fopen(fname, "r"); + if (fp == NULL) + return (-1); + + while ((ptr = fgetln(fp, &len)) != NULL) { + if (len > 0 && ptr[len - 1] == '\n') + --len; + ptr[len] = '\0'; + if (parse_line(el, ptr) == -1) { + (void) fclose(fp); + return (-1); + } + } + + (void) fclose(fp); + return (0); +} + + +/* el_resize(): + * Called from program when terminal is resized + */ +public void +el_resize(EditLine *el) +{ + int lins, cols; + sigset_t oset, nset; + + (void) sigemptyset(&nset); + (void) sigaddset(&nset, SIGWINCH); + (void) sigprocmask(SIG_BLOCK, &nset, &oset); + + /* get the correct window size */ + if (term_get_size(el, &lins, &cols)) + term_change_size(el, lins, cols); + + (void) sigprocmask(SIG_SETMASK, &oset, NULL); +} + + +/* el_beep(): + * Called from the program to beep + */ +public void +el_beep(EditLine *el) +{ + + term_beep(el); +} + + +/* el_editmode() + * Set the state of EDIT_DISABLED from the `edit' command. + */ +protected int +/*ARGSUSED*/ +el_editmode(EditLine *el, int argc, const char **argv) +{ + const char *how; + + if (argv == NULL || argc != 2 || argv[1] == NULL) + return (-1); + + how = argv[1]; + if (strcmp(how, "on") == 0) + el->el_flags &= ~EDIT_DISABLED; + else if (strcmp(how, "off") == 0) + el->el_flags |= EDIT_DISABLED; + else { + (void) fprintf(el->el_errfile, "edit: Bad value `%s'.\n", how); + return (-1); + } + return (0); +} diff --git a/main/editline/el.h b/main/editline/el.h new file mode 100644 index 000000000..641081e87 --- /dev/null +++ b/main/editline/el.h @@ -0,0 +1,145 @@ +/* $NetBSD: el.h,v 1.11 2002/03/18 16:00:52 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)el.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.h: Internal structures. + */ +#ifndef _h_el +#define _h_el +/* + * Local defaults + */ +#define KSHVI +#define VIDEFAULT +#define ANCHOR + +#include +#include + +#define EL_BUFSIZ 1024 /* Maximum line size */ + +#define HANDLE_SIGNALS 1<<0 +#define NO_TTY 1<<1 +#define EDIT_DISABLED 1<<2 + +typedef int bool_t; /* True or not */ + +typedef unsigned char el_action_t; /* Index to command array */ + +typedef struct coord_t { /* Position on the screen */ + int h; + int v; +} coord_t; + +typedef struct el_line_t { + char *buffer; /* Input line */ + char *cursor; /* Cursor position */ + char *lastchar; /* Last character */ + const char *limit; /* Max position */ +} el_line_t; + +/* + * Editor state + */ +typedef struct el_state_t { + int inputmode; /* What mode are we in? */ + int doingarg; /* Are we getting an argument? */ + int argument; /* Numeric argument */ + int metanext; /* Is the next char a meta char */ + el_action_t lastcmd; /* Previous command */ +} el_state_t; + +/* + * Until we come up with something better... + */ +#define el_malloc(a) malloc(a) +#define el_realloc(a,b) realloc(a, b) +#define el_free(a) free(a) + +#include "tty.h" +#include "prompt.h" +#include "key.h" +#include "term.h" +#include "refresh.h" +#include "chared.h" +#include "common.h" +#include "search.h" +#include "hist.h" +#include "map.h" +#include "parse.h" +#include "sig.h" +#include "help.h" +#include "read.h" + +struct editline { + char *el_prog; /* the program name */ + FILE *el_outfile; /* Stdio stuff */ + FILE *el_errfile; /* Stdio stuff */ + int el_infd; /* Input file descriptor */ + int el_flags; /* Various flags. */ + coord_t el_cursor; /* Cursor location */ + char **el_display; /* Real screen image = what is there */ + char **el_vdisplay; /* Virtual screen image = what we see */ + void *el_data; /* Client data */ + el_line_t el_line; /* The current line information */ + el_state_t el_state; /* Current editor state */ + el_term_t el_term; /* Terminal dependent stuff */ + el_tty_t el_tty; /* Tty dependent stuff */ + el_refresh_t el_refresh; /* Refresh stuff */ + el_prompt_t el_prompt; /* Prompt stuff */ + el_prompt_t el_rprompt; /* Prompt stuff */ + el_chared_t el_chared; /* Characted editor stuff */ + el_map_t el_map; /* Key mapping stuff */ + el_key_t el_key; /* Key binding stuff */ + el_history_t el_history; /* History stuff */ + el_search_t el_search; /* Search stuff */ + el_signal_t el_signal; /* Signal handling stuff */ + el_read_t el_read; /* Character reading stuff */ +}; + +protected int el_editmode(EditLine *, int, const char **); + +#ifdef DEBUG +#define EL_ABORT(a) (void) (fprintf(el->el_errfile, "%s, %d: ", \ + __FILE__, __LINE__), fprintf a, abort()) +#else +#define EL_ABORT(a) abort() +#endif +#endif /* _h_el */ diff --git a/main/editline/emacs.c b/main/editline/emacs.c new file mode 100644 index 000000000..f520d024b --- /dev/null +++ b/main/editline/emacs.c @@ -0,0 +1,488 @@ +/* $NetBSD: emacs.c,v 1.10 2002/03/18 16:00:52 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)emacs.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: emacs.c,v 1.10 2002/03/18 16:00:52 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * emacs.c: Emacs functions + */ +#include "el.h" + +/* em_delete_or_list(): + * Delete character under cursor or list completions if at end of line + * [^D] + */ +protected el_action_t +/*ARGSUSED*/ +em_delete_or_list(EditLine *el, int c) +{ + + if (el->el_line.cursor == el->el_line.lastchar) { + /* if I'm at the end */ + if (el->el_line.cursor == el->el_line.buffer) { + /* and the beginning */ + term_overwrite(el, STReof, 4); /* then do a EOF */ + term__flush(); + return (CC_EOF); + } else { + /* + * Here we could list completions, but it is an + * error right now + */ + term_beep(el); + return (CC_ERROR); + } + } else { + c_delafter(el, el->el_state.argument); /* delete after dot */ + if (el->el_line.cursor > el->el_line.lastchar) + el->el_line.cursor = el->el_line.lastchar; + /* bounds check */ + return (CC_REFRESH); + } +} + + +/* em_delete_next_word(): + * Cut from cursor to end of current word + * [M-d] + */ +protected el_action_t +/*ARGSUSED*/ +em_delete_next_word(EditLine *el, int c) +{ + char *cp, *p, *kp; + + if (el->el_line.cursor == el->el_line.lastchar) + return (CC_ERROR); + + cp = c__next_word(el->el_line.cursor, el->el_line.lastchar, + el->el_state.argument, ce__isword); + + for (p = el->el_line.cursor, kp = el->el_chared.c_kill.buf; p < cp; p++) + /* save the text */ + *kp++ = *p; + el->el_chared.c_kill.last = kp; + + c_delafter(el, cp - el->el_line.cursor); /* delete after dot */ + if (el->el_line.cursor > el->el_line.lastchar) + el->el_line.cursor = el->el_line.lastchar; + /* bounds check */ + return (CC_REFRESH); +} + + +/* em_yank(): + * Paste cut buffer at cursor position + * [^Y] + */ +protected el_action_t +/*ARGSUSED*/ +em_yank(EditLine *el, int c) +{ + char *kp, *cp; + + if (el->el_chared.c_kill.last == el->el_chared.c_kill.buf) { + if (!ch_enlargebufs(el, 1)) + return (CC_ERROR); + } + + if (el->el_line.lastchar + + (el->el_chared.c_kill.last - el->el_chared.c_kill.buf) >= + el->el_line.limit) + return (CC_ERROR); + + el->el_chared.c_kill.mark = el->el_line.cursor; + cp = el->el_line.cursor; + + /* open the space, */ + c_insert(el, el->el_chared.c_kill.last - el->el_chared.c_kill.buf); + /* copy the chars */ + for (kp = el->el_chared.c_kill.buf; kp < el->el_chared.c_kill.last; kp++) + *cp++ = *kp; + + /* if an arg, cursor at beginning else cursor at end */ + if (el->el_state.argument == 1) + el->el_line.cursor = cp; + + return (CC_REFRESH); +} + + +/* em_kill_line(): + * Cut the entire line and save in cut buffer + * [^U] + */ +protected el_action_t +/*ARGSUSED*/ +em_kill_line(EditLine *el, int c) +{ + char *kp, *cp; + + cp = el->el_line.buffer; + kp = el->el_chared.c_kill.buf; + while (cp < el->el_line.lastchar) + *kp++ = *cp++; /* copy it */ + el->el_chared.c_kill.last = kp; + /* zap! -- delete all of it */ + el->el_line.lastchar = el->el_line.buffer; + el->el_line.cursor = el->el_line.buffer; + return (CC_REFRESH); +} + + +/* em_kill_region(): + * Cut area between mark and cursor and save in cut buffer + * [^W] + */ +protected el_action_t +/*ARGSUSED*/ +em_kill_region(EditLine *el, int c) +{ + char *kp, *cp; + + if (!el->el_chared.c_kill.mark) + return (CC_ERROR); + + if (el->el_chared.c_kill.mark > el->el_line.cursor) { + cp = el->el_line.cursor; + kp = el->el_chared.c_kill.buf; + while (cp < el->el_chared.c_kill.mark) + *kp++ = *cp++; /* copy it */ + el->el_chared.c_kill.last = kp; + c_delafter(el, cp - el->el_line.cursor); + } else { /* mark is before cursor */ + cp = el->el_chared.c_kill.mark; + kp = el->el_chared.c_kill.buf; + while (cp < el->el_line.cursor) + *kp++ = *cp++; /* copy it */ + el->el_chared.c_kill.last = kp; + c_delbefore(el, cp - el->el_chared.c_kill.mark); + el->el_line.cursor = el->el_chared.c_kill.mark; + } + return (CC_REFRESH); +} + + +/* em_copy_region(): + * Copy area between mark and cursor to cut buffer + * [M-W] + */ +protected el_action_t +/*ARGSUSED*/ +em_copy_region(EditLine *el, int c) +{ + char *kp, *cp; + + if (el->el_chared.c_kill.mark) + return (CC_ERROR); + + if (el->el_chared.c_kill.mark > el->el_line.cursor) { + cp = el->el_line.cursor; + kp = el->el_chared.c_kill.buf; + while (cp < el->el_chared.c_kill.mark) + *kp++ = *cp++; /* copy it */ + el->el_chared.c_kill.last = kp; + } else { + cp = el->el_chared.c_kill.mark; + kp = el->el_chared.c_kill.buf; + while (cp < el->el_line.cursor) + *kp++ = *cp++; /* copy it */ + el->el_chared.c_kill.last = kp; + } + return (CC_NORM); +} + + +/* em_gosmacs_traspose(): + * Exchange the two characters before the cursor + * Gosling emacs transpose chars [^T] + */ +protected el_action_t +em_gosmacs_traspose(EditLine *el, int c) +{ + + if (el->el_line.cursor > &el->el_line.buffer[1]) { + /* must have at least two chars entered */ + c = el->el_line.cursor[-2]; + el->el_line.cursor[-2] = el->el_line.cursor[-1]; + el->el_line.cursor[-1] = c; + return (CC_REFRESH); + } else + return (CC_ERROR); +} + + +/* em_next_word(): + * Move next to end of current word + * [M-f] + */ +protected el_action_t +/*ARGSUSED*/ +em_next_word(EditLine *el, int c) +{ + if (el->el_line.cursor == el->el_line.lastchar) + return (CC_ERROR); + + el->el_line.cursor = c__next_word(el->el_line.cursor, + el->el_line.lastchar, + el->el_state.argument, + ce__isword); + + if (el->el_map.type == MAP_VI) + if (el->el_chared.c_vcmd.action & DELETE) { + cv_delfini(el); + return (CC_REFRESH); + } + return (CC_CURSOR); +} + + +/* em_upper_case(): + * Uppercase the characters from cursor to end of current word + * [M-u] + */ +protected el_action_t +/*ARGSUSED*/ +em_upper_case(EditLine *el, int c) +{ + char *cp, *ep; + + ep = c__next_word(el->el_line.cursor, el->el_line.lastchar, + el->el_state.argument, ce__isword); + + for (cp = el->el_line.cursor; cp < ep; cp++) + if (islower((unsigned char) *cp)) + *cp = toupper(*cp); + + el->el_line.cursor = ep; + if (el->el_line.cursor > el->el_line.lastchar) + el->el_line.cursor = el->el_line.lastchar; + return (CC_REFRESH); +} + + +/* em_capitol_case(): + * Capitalize the characters from cursor to end of current word + * [M-c] + */ +protected el_action_t +/*ARGSUSED*/ +em_capitol_case(EditLine *el, int c) +{ + char *cp, *ep; + + ep = c__next_word(el->el_line.cursor, el->el_line.lastchar, + el->el_state.argument, ce__isword); + + for (cp = el->el_line.cursor; cp < ep; cp++) { + if (isalpha((unsigned char) *cp)) { + if (islower((unsigned char) *cp)) + *cp = toupper(*cp); + cp++; + break; + } + } + for (; cp < ep; cp++) + if (isupper((unsigned char) *cp)) + *cp = tolower(*cp); + + el->el_line.cursor = ep; + if (el->el_line.cursor > el->el_line.lastchar) + el->el_line.cursor = el->el_line.lastchar; + return (CC_REFRESH); +} + + +/* em_lower_case(): + * Lowercase the characters from cursor to end of current word + * [M-l] + */ +protected el_action_t +/*ARGSUSED*/ +em_lower_case(EditLine *el, int c) +{ + char *cp, *ep; + + ep = c__next_word(el->el_line.cursor, el->el_line.lastchar, + el->el_state.argument, ce__isword); + + for (cp = el->el_line.cursor; cp < ep; cp++) + if (isupper((unsigned char) *cp)) + *cp = tolower(*cp); + + el->el_line.cursor = ep; + if (el->el_line.cursor > el->el_line.lastchar) + el->el_line.cursor = el->el_line.lastchar; + return (CC_REFRESH); +} + + +/* em_set_mark(): + * Set the mark at cursor + * [^@] + */ +protected el_action_t +/*ARGSUSED*/ +em_set_mark(EditLine *el, int c) +{ + + el->el_chared.c_kill.mark = el->el_line.cursor; + return (CC_NORM); +} + + +/* em_exchange_mark(): + * Exchange the cursor and mark + * [^X^X] + */ +protected el_action_t +/*ARGSUSED*/ +em_exchange_mark(EditLine *el, int c) +{ + char *cp; + + cp = el->el_line.cursor; + el->el_line.cursor = el->el_chared.c_kill.mark; + el->el_chared.c_kill.mark = cp; + return (CC_CURSOR); +} + + +/* em_universal_argument(): + * Universal argument (argument times 4) + * [^U] + */ +protected el_action_t +/*ARGSUSED*/ +em_universal_argument(EditLine *el, int c) +{ /* multiply current argument by 4 */ + + if (el->el_state.argument > 1000000) + return (CC_ERROR); + el->el_state.doingarg = 1; + el->el_state.argument *= 4; + return (CC_ARGHACK); +} + + +/* em_meta_next(): + * Add 8th bit to next character typed + * [] + */ +protected el_action_t +/*ARGSUSED*/ +em_meta_next(EditLine *el, int c) +{ + + el->el_state.metanext = 1; + return (CC_ARGHACK); +} + + +/* em_toggle_overwrite(): + * Switch from insert to overwrite mode or vice versa + */ +protected el_action_t +/*ARGSUSED*/ +em_toggle_overwrite(EditLine *el, int c) +{ + + el->el_state.inputmode = (el->el_state.inputmode == MODE_INSERT) ? + MODE_REPLACE : MODE_INSERT; + return (CC_NORM); +} + + +/* em_copy_prev_word(): + * Copy current word to cursor + */ +protected el_action_t +/*ARGSUSED*/ +em_copy_prev_word(EditLine *el, int c) +{ + char *cp, *oldc, *dp; + + if (el->el_line.cursor == el->el_line.buffer) + return (CC_ERROR); + + oldc = el->el_line.cursor; + /* does a bounds check */ + cp = c__prev_word(el->el_line.cursor, el->el_line.buffer, + el->el_state.argument, ce__isword); + + c_insert(el, oldc - cp); + for (dp = oldc; cp < oldc && dp < el->el_line.lastchar; cp++) + *dp++ = *cp; + + el->el_line.cursor = dp;/* put cursor at end */ + + return (CC_REFRESH); +} + + +/* em_inc_search_next(): + * Emacs incremental next search + */ +protected el_action_t +/*ARGSUSED*/ +em_inc_search_next(EditLine *el, int c) +{ + + el->el_search.patlen = 0; + return (ce_inc_search(el, ED_SEARCH_NEXT_HISTORY)); +} + + +/* em_inc_search_prev(): + * Emacs incremental reverse search + */ +protected el_action_t +/*ARGSUSED*/ +em_inc_search_prev(EditLine *el, int c) +{ + + el->el_search.patlen = 0; + return (ce_inc_search(el, ED_SEARCH_PREV_HISTORY)); +} diff --git a/main/editline/hist.c b/main/editline/hist.c new file mode 100644 index 000000000..11f39ae10 --- /dev/null +++ b/main/editline/hist.c @@ -0,0 +1,197 @@ +/* $NetBSD: hist.c,v 1.10 2002/03/18 16:00:53 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)hist.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: hist.c,v 1.10 2002/03/18 16:00:53 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * hist.c: History access functions + */ +#include +#include "el.h" + +/* hist_init(): + * Initialization function. + */ +protected int +hist_init(EditLine *el) +{ + + el->el_history.fun = NULL; + el->el_history.ref = NULL; + el->el_history.buf = (char *) el_malloc(EL_BUFSIZ); + el->el_history.sz = EL_BUFSIZ; + if (el->el_history.buf == NULL) + return (-1); + el->el_history.last = el->el_history.buf; + return (0); +} + + +/* hist_end(): + * clean up history; + */ +protected void +hist_end(EditLine *el) +{ + + el_free((ptr_t) el->el_history.buf); + el->el_history.buf = NULL; +} + + +/* hist_set(): + * Set new history interface + */ +protected int +hist_set(EditLine *el, hist_fun_t fun, ptr_t ptr) +{ + + el->el_history.ref = ptr; + el->el_history.fun = fun; + return (0); +} + + +/* hist_get(): + * Get a history line and update it in the buffer. + * eventno tells us the event to get. + */ +protected el_action_t +hist_get(EditLine *el) +{ + const char *hp; + int h; + + if (el->el_history.eventno == 0) { /* if really the current line */ + (void) strncpy(el->el_line.buffer, el->el_history.buf, + el->el_history.sz - 1); + el->el_line.lastchar = el->el_line.buffer + + (el->el_history.last - el->el_history.buf); + +#ifdef KSHVI + if (el->el_map.type == MAP_VI) + el->el_line.cursor = el->el_line.buffer; + else +#endif /* KSHVI */ + el->el_line.cursor = el->el_line.lastchar; + + return (CC_REFRESH); + } + if (el->el_history.ref == NULL) + return (CC_ERROR); + + hp = HIST_FIRST(el); + + if (hp == NULL) + return (CC_ERROR); + + for (h = 1; h < el->el_history.eventno; h++) + if ((hp = HIST_NEXT(el)) == NULL) { + el->el_history.eventno = h; + return (CC_ERROR); + } + (void) strncpy(el->el_line.buffer, hp, + (size_t)(el->el_line.limit - el->el_line.buffer)); + el->el_line.lastchar = el->el_line.buffer + strlen(el->el_line.buffer); + + if (el->el_line.lastchar > el->el_line.buffer) { + if (el->el_line.lastchar[-1] == '\n') + el->el_line.lastchar--; + if ((el->el_line.lastchar > el->el_line.buffer)&&(el->el_line.lastchar[-1] == ' ')) /* bill heckel */ + el->el_line.lastchar--; + if (el->el_line.lastchar < el->el_line.buffer) + el->el_line.lastchar = el->el_line.buffer; + } +#ifdef KSHVI + if (el->el_map.type == MAP_VI) + el->el_line.cursor = el->el_line.buffer; + else +#endif /* KSHVI */ + el->el_line.cursor = el->el_line.lastchar; + + return (CC_REFRESH); +} + + +/* hist_list() + * List history entries + */ +protected int +/*ARGSUSED*/ +hist_list(EditLine *el, int argc, const char **argv) +{ + const char *str; + + if (el->el_history.ref == NULL) + return (-1); + for (str = HIST_LAST(el); str != NULL; str = HIST_PREV(el)) + (void) fprintf(el->el_outfile, "%d %s", + el->el_history.ev.num, str); + return (0); +} + +/* hist_enlargebuf() + * Enlarge history buffer to specified value. Called from el_enlargebufs(). + * Return 0 for failure, 1 for success. + */ +protected int +/*ARGSUSED*/ +hist_enlargebuf(EditLine *el, size_t oldsz, size_t newsz) +{ + char *newbuf; + + newbuf = realloc(el->el_history.buf, newsz); + if (!newbuf) + return 0; + + (void) memset(&newbuf[oldsz], '\0', newsz - oldsz); + + el->el_history.last = newbuf + + (el->el_history.last - el->el_history.buf); + el->el_history.buf = newbuf; + el->el_history.sz = newsz; + + return 1; +} diff --git a/main/editline/hist.h b/main/editline/hist.h new file mode 100644 index 000000000..5fdccd08e --- /dev/null +++ b/main/editline/hist.h @@ -0,0 +1,80 @@ +/* $NetBSD: hist.h,v 1.7 2002/03/18 16:00:53 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)hist.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.hist.c: History functions + */ +#ifndef _h_el_hist +#define _h_el_hist + +#include "histedit.h" + +typedef int (*hist_fun_t)(ptr_t, HistEvent *, int, ...); + +typedef struct el_history_t { + char *buf; /* The history buffer */ + size_t sz; /* Size of history buffer */ + char *last; /* The last character */ + int eventno; /* Event we are looking for */ + ptr_t ref; /* Argument for history fcns */ + hist_fun_t fun; /* Event access */ + HistEvent ev; /* Event cookie */ +} el_history_t; + +#define HIST_FUN(el, fn, arg) \ + ((((*(el)->el_history.fun) ((el)->el_history.ref, &(el)->el_history.ev, \ + fn, arg)) == -1) ? NULL : (el)->el_history.ev.str) + +#define HIST_NEXT(el) HIST_FUN(el, H_NEXT, NULL) +#define HIST_FIRST(el) HIST_FUN(el, H_FIRST, NULL) +#define HIST_LAST(el) HIST_FUN(el, H_LAST, NULL) +#define HIST_PREV(el) HIST_FUN(el, H_PREV, NULL) +#define HIST_EVENT(el, num) HIST_FUN(el, H_EVENT, num) +#define HIST_LOAD(el, fname) HIST_FUN(el, H_LOAD fname) +#define HIST_SAVE(el, fname) HIST_FUN(el, H_SAVE fname) + +protected int hist_init(EditLine *); +protected void hist_end(EditLine *); +protected el_action_t hist_get(EditLine *); +protected int hist_set(EditLine *, hist_fun_t, ptr_t); +protected int hist_list(EditLine *, int, const char **); +protected int hist_enlargebuf(EditLine *, size_t, size_t); + +#endif /* _h_el_hist */ diff --git a/main/editline/histedit.h b/main/editline/histedit.h new file mode 100644 index 000000000..e387e3b81 --- /dev/null +++ b/main/editline/histedit.h @@ -0,0 +1,197 @@ +/* $NetBSD: histedit.h,v 1.19 2002/03/18 16:00:54 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)histedit.h 8.2 (Berkeley) 1/3/94 + */ + +/* + * histedit.h: Line editor and history interface. + */ +#ifndef _HISTEDIT_H_ +#define _HISTEDIT_H_ + +#define LIBEDIT_MAJOR 2 +#define LIBEDIT_MINOR 6 + +#include +#include + +/* + * ==== Editing ==== + */ +typedef struct editline EditLine; + +/* + * For user-defined function interface + */ +typedef struct lineinfo { + char *buffer; + char *cursor; + char *lastchar; +} LineInfo; + + +/* + * EditLine editor function return codes. + * For user-defined function interface + */ +#define CC_NORM 0 +#define CC_NEWLINE 1 +#define CC_EOF 2 +#define CC_ARGHACK 3 +#define CC_REFRESH 4 +#define CC_CURSOR 5 +#define CC_ERROR 6 +#define CC_FATAL 7 +#define CC_REDISPLAY 8 +#define CC_REFRESH_BEEP 9 + +/* + * Initialization, cleanup, and resetting + */ +EditLine *el_init(const char *, FILE *, FILE *, FILE *); +void el_reset(EditLine *); +void el_end(EditLine *); + + +/* + * Get a line, a character or push a string back in the input queue + */ +const char *el_gets(EditLine *, int *); +int el_getc(EditLine *, char *); +void el_push(EditLine *, char *); + +/* + * Beep! + */ +void el_beep(EditLine *); + +/* + * High level function internals control + * Parses argc, argv array and executes builtin editline commands + */ +int el_parse(EditLine *, int, const char **); + +/* + * Low level editline access functions + */ +int el_set(EditLine *, int, ...); +int el_get(EditLine *, int, void *); + +/* + * el_set/el_get parameters + */ +#define EL_PROMPT 0 /* , el_pfunc_t); */ +#define EL_TERMINAL 1 /* , const char *); */ +#define EL_EDITOR 2 /* , const char *); */ +#define EL_SIGNAL 3 /* , int); */ +#define EL_BIND 4 /* , const char *, ..., NULL); */ +#define EL_TELLTC 5 /* , const char *, ..., NULL); */ +#define EL_SETTC 6 /* , const char *, ..., NULL); */ +#define EL_ECHOTC 7 /* , const char *, ..., NULL); */ +#define EL_SETTY 8 /* , const char *, ..., NULL); */ +#define EL_ADDFN 9 /* , const char *, const char * */ + /* , el_func_t); */ +#define EL_HIST 10 /* , hist_fun_t, const char *); */ +#define EL_EDITMODE 11 /* , int); */ +#define EL_RPROMPT 12 /* , el_pfunc_t); */ +#define EL_GETCFN 13 /* , el_rfunc_t); */ +#define EL_CLIENTDATA 14 /* , void *); */ + +#define EL_BUILTIN_GETCFN (NULL) + +/* + * Source named file or $PWD/.editrc or $HOME/.editrc + */ +int el_source(EditLine *, const char *); + +/* + * Must be called when the terminal changes size; If EL_SIGNAL + * is set this is done automatically otherwise it is the responsibility + * of the application + */ +void el_resize(EditLine *); + + +/* + * User-defined function interface. + */ +const LineInfo *el_line(EditLine *); +int el_insertstr(EditLine *, const char *); +void el_deletestr(EditLine *, int); + +/* + * ==== History ==== + */ + +typedef struct history History; + +typedef struct HistEvent { + int num; + const char *str; +} HistEvent; + +/* + * History access functions. + */ +History * history_init(void); +void history_end(History *); + +int history(History *, HistEvent *, int, ...); + +#define H_FUNC 0 /* , UTSL */ +#define H_SETSIZE 1 /* , const int); */ +#define H_GETSIZE 2 /* , void); */ +#define H_FIRST 3 /* , void); */ +#define H_LAST 4 /* , void); */ +#define H_PREV 5 /* , void); */ +#define H_NEXT 6 /* , void); */ +#define H_CURR 8 /* , const int); */ +#define H_SET 7 /* , void); */ +#define H_ADD 9 /* , const char *); */ +#define H_ENTER 10 /* , const char *); */ +#define H_APPEND 11 /* , const char *); */ +#define H_END 12 /* , void); */ +#define H_NEXT_STR 13 /* , const char *); */ +#define H_PREV_STR 14 /* , const char *); */ +#define H_NEXT_EVENT 15 /* , const int); */ +#define H_PREV_EVENT 16 /* , const int); */ +#define H_LOAD 17 /* , const char *); */ +#define H_SAVE 18 /* , const char *); */ +#define H_CLEAR 19 /* , void); */ + +#endif /* _HISTEDIT_H_ */ diff --git a/main/editline/history.c b/main/editline/history.c new file mode 100644 index 000000000..f133d2eb0 --- /dev/null +++ b/main/editline/history.c @@ -0,0 +1,875 @@ +/* $NetBSD: history.c,v 1.19 2002/03/18 16:00:54 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)history.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: history.c,v 1.19 2002/03/18 16:00:54 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * hist.c: History access functions + */ +#include +#include +#include +#ifdef HAVE_VIS_H +#include +#else +#include "np/vis.h" +#endif +#include + +static const char hist_cookie[] = "_HiStOrY_V2_\n"; + +#include "histedit.h" + +typedef int (*history_gfun_t)(ptr_t, HistEvent *); +typedef int (*history_efun_t)(ptr_t, HistEvent *, const char *); +typedef void (*history_vfun_t)(ptr_t, HistEvent *); +typedef int (*history_sfun_t)(ptr_t, HistEvent *, const int); + +struct history { + ptr_t h_ref; /* Argument for history fcns */ + int h_ent; /* Last entry point for history */ + history_gfun_t h_first; /* Get the first element */ + history_gfun_t h_next; /* Get the next element */ + history_gfun_t h_last; /* Get the last element */ + history_gfun_t h_prev; /* Get the previous element */ + history_gfun_t h_curr; /* Get the current element */ + history_sfun_t h_set; /* Set the current element */ + history_vfun_t h_clear; /* Clear the history list */ + history_efun_t h_enter; /* Add an element */ + history_efun_t h_add; /* Append to an element */ +}; +#define HNEXT(h, ev) (*(h)->h_next)((h)->h_ref, ev) +#define HFIRST(h, ev) (*(h)->h_first)((h)->h_ref, ev) +#define HPREV(h, ev) (*(h)->h_prev)((h)->h_ref, ev) +#define HLAST(h, ev) (*(h)->h_last)((h)->h_ref, ev) +#define HCURR(h, ev) (*(h)->h_curr)((h)->h_ref, ev) +#define HSET(h, ev, n) (*(h)->h_set)((h)->h_ref, ev, n) +#define HCLEAR(h, ev) (*(h)->h_clear)((h)->h_ref, ev) +#define HENTER(h, ev, str) (*(h)->h_enter)((h)->h_ref, ev, str) +#define HADD(h, ev, str) (*(h)->h_add)((h)->h_ref, ev, str) + +#define h_malloc(a) malloc(a) +#define h_realloc(a, b) realloc((a), (b)) +#define h_free(a) free(a) + +typedef struct { + int num; + char *str; +} HistEventPrivate; + + + +private int history_setsize(History *, HistEvent *, int); +private int history_getsize(History *, HistEvent *); +private int history_set_fun(History *, History *); +private int history_load(History *, const char *); +private int history_save(History *, const char *); +private int history_prev_event(History *, HistEvent *, int); +private int history_next_event(History *, HistEvent *, int); +private int history_next_string(History *, HistEvent *, const char *); +private int history_prev_string(History *, HistEvent *, const char *); + + +/***********************************************************************/ + +/* + * Builtin- history implementation + */ +typedef struct hentry_t { + HistEvent ev; /* What we return */ + struct hentry_t *next; /* Next entry */ + struct hentry_t *prev; /* Previous entry */ +} hentry_t; + +typedef struct history_t { + hentry_t list; /* Fake list header element */ + hentry_t *cursor; /* Current element in the list */ + int max; /* Maximum number of events */ + int cur; /* Current number of events */ + int eventid; /* For generation of unique event id */ +} history_t; + +private int history_def_first(ptr_t, HistEvent *); +private int history_def_last(ptr_t, HistEvent *); +private int history_def_next(ptr_t, HistEvent *); +private int history_def_prev(ptr_t, HistEvent *); +private int history_def_curr(ptr_t, HistEvent *); +private int history_def_set(ptr_t, HistEvent *, const int n); +private int history_def_enter(ptr_t, HistEvent *, const char *); +private int history_def_add(ptr_t, HistEvent *, const char *); +private void history_def_init(ptr_t *, HistEvent *, int); +private void history_def_clear(ptr_t, HistEvent *); +private int history_def_insert(history_t *, HistEvent *, const char *); +private void history_def_delete(history_t *, HistEvent *, hentry_t *); + +#define history_def_setsize(p, num)(void) (((history_t *) p)->max = (num)) +#define history_def_getsize(p) (((history_t *) p)->cur) + +#define he_strerror(code) he_errlist[code] +#define he_seterrev(evp, code) {\ + evp->num = code;\ + evp->str = he_strerror(code);\ + } + +/* error messages */ +static const char *const he_errlist[] = { + "OK", + "unknown error", + "malloc() failed", + "first event not found", + "last event not found", + "empty list", + "no next event", + "no previous event", + "current event is invalid", + "event not found", + "can't read history from file", + "can't write history", + "required parameter(s) not supplied", + "history size negative", + "function not allowed with other history-functions-set the default", + "bad parameters" +}; +/* error codes */ +#define _HE_OK 0 +#define _HE_UNKNOWN 1 +#define _HE_MALLOC_FAILED 2 +#define _HE_FIRST_NOTFOUND 3 +#define _HE_LAST_NOTFOUND 4 +#define _HE_EMPTY_LIST 5 +#define _HE_END_REACHED 6 +#define _HE_START_REACHED 7 +#define _HE_CURR_INVALID 8 +#define _HE_NOT_FOUND 9 +#define _HE_HIST_READ 10 +#define _HE_HIST_WRITE 11 +#define _HE_PARAM_MISSING 12 +#define _HE_SIZE_NEGATIVE 13 +#define _HE_NOT_ALLOWED 14 +#define _HE_BAD_PARAM 15 + +/* history_def_first(): + * Default function to return the first event in the history. + */ +private int +history_def_first(ptr_t p, HistEvent *ev) +{ + history_t *h = (history_t *) p; + + h->cursor = h->list.next; + if (h->cursor != &h->list) + *ev = h->cursor->ev; + else { + he_seterrev(ev, _HE_FIRST_NOTFOUND); + return (-1); + } + + return (0); +} + + +/* history_def_last(): + * Default function to return the last event in the history. + */ +private int +history_def_last(ptr_t p, HistEvent *ev) +{ + history_t *h = (history_t *) p; + + h->cursor = h->list.prev; + if (h->cursor != &h->list) + *ev = h->cursor->ev; + else { + he_seterrev(ev, _HE_LAST_NOTFOUND); + return (-1); + } + + return (0); +} + + +/* history_def_next(): + * Default function to return the next event in the history. + */ +private int +history_def_next(ptr_t p, HistEvent *ev) +{ + history_t *h = (history_t *) p; + + if (h->cursor != &h->list) + h->cursor = h->cursor->next; + else { + he_seterrev(ev, _HE_EMPTY_LIST); + return (-1); + } + + if (h->cursor != &h->list) + *ev = h->cursor->ev; + else { + he_seterrev(ev, _HE_END_REACHED); + return (-1); + } + + return (0); +} + + +/* history_def_prev(): + * Default function to return the previous event in the history. + */ +private int +history_def_prev(ptr_t p, HistEvent *ev) +{ + history_t *h = (history_t *) p; + + if (h->cursor != &h->list) + h->cursor = h->cursor->prev; + else { + he_seterrev(ev, + (h->cur > 0) ? _HE_END_REACHED : _HE_EMPTY_LIST); + return (-1); + } + + if (h->cursor != &h->list) + *ev = h->cursor->ev; + else { + he_seterrev(ev, _HE_START_REACHED); + return (-1); + } + + return (0); +} + + +/* history_def_curr(): + * Default function to return the current event in the history. + */ +private int +history_def_curr(ptr_t p, HistEvent *ev) +{ + history_t *h = (history_t *) p; + + if (h->cursor != &h->list) + *ev = h->cursor->ev; + else { + he_seterrev(ev, + (h->cur > 0) ? _HE_CURR_INVALID : _HE_EMPTY_LIST); + return (-1); + } + + return (0); +} + + +/* history_def_set(): + * Default function to set the current event in the history to the + * given one. + */ +private int +history_def_set(ptr_t p, HistEvent *ev, const int n) +{ + history_t *h = (history_t *) p; + + if (h->cur == 0) { + he_seterrev(ev, _HE_EMPTY_LIST); + return (-1); + } + if (h->cursor == &h->list || h->cursor->ev.num != n) { + for (h->cursor = h->list.next; h->cursor != &h->list; + h->cursor = h->cursor->next) + if (h->cursor->ev.num == n) + break; + } + if (h->cursor == &h->list) { + he_seterrev(ev, _HE_NOT_FOUND); + return (-1); + } + return (0); +} + + +/* history_def_add(): + * Append string to element + */ +private int +history_def_add(ptr_t p, HistEvent *ev, const char *str) +{ + history_t *h = (history_t *) p; + size_t len; + char *s; + HistEventPrivate *evp = (void *)&h->cursor->ev; + + if (h->cursor == &h->list) + return (history_def_enter(p, ev, str)); + len = strlen(evp->str) + strlen(str) + 1; + s = (char *) h_malloc(len); + if (!s) { + he_seterrev(ev, _HE_MALLOC_FAILED); + return (-1); + } + (void) strlcpy(s, h->cursor->ev.str, len); + (void) strlcat(s, str, len); + h_free(evp->str); + evp->str = s; + *ev = h->cursor->ev; + return (0); +} + + +/* history_def_delete(): + * Delete element hp of the h list + */ +/* ARGSUSED */ +private void +history_def_delete(history_t *h, HistEvent *ev, hentry_t *hp) +{ + HistEventPrivate *evp = (void *)&hp->ev; + if (hp == &h->list) + abort(); + hp->prev->next = hp->next; + hp->next->prev = hp->prev; + h_free((ptr_t) evp->str); + h_free(hp); + h->cur--; +} + + +/* history_def_insert(): + * Insert element with string str in the h list + */ +private int +history_def_insert(history_t *h, HistEvent *ev, const char *str) +{ + + h->cursor = (hentry_t *) h_malloc(sizeof(hentry_t)); + if (h->cursor) + h->cursor->ev.str = strdup(str); + if (!h->cursor || !h->cursor->ev.str) { + he_seterrev(ev, _HE_MALLOC_FAILED); + return (-1); + } + h->cursor->ev.num = ++h->eventid; + h->cursor->next = h->list.next; + h->cursor->prev = &h->list; + h->list.next->prev = h->cursor; + h->list.next = h->cursor; + h->cur++; + + *ev = h->cursor->ev; + return (0); +} + + +/* history_def_enter(): + * Default function to enter an item in the history + */ +private int +history_def_enter(ptr_t p, HistEvent *ev, const char *str) +{ + history_t *h = (history_t *) p; + + if (history_def_insert(h, ev, str) == -1) + return (-1); /* error, keep error message */ + + /* + * Always keep at least one entry. + * This way we don't have to check for the empty list. + */ + while (h->cur > h->max && h->cur > 0) + history_def_delete(h, ev, h->list.prev); + + return (0); +} + + +/* history_def_init(): + * Default history initialization function + */ +/* ARGSUSED */ +private void +history_def_init(ptr_t *p, HistEvent *ev, int n) +{ + history_t *h = (history_t *) h_malloc(sizeof(history_t)); + + if (n <= 0) + n = 0; + h->eventid = 0; + h->cur = 0; + h->max = n; + h->list.next = h->list.prev = &h->list; + h->list.ev.str = NULL; + h->list.ev.num = 0; + h->cursor = &h->list; + *p = (ptr_t) h; +} + + +/* history_def_clear(): + * Default history cleanup function + */ +private void +history_def_clear(ptr_t p, HistEvent *ev) +{ + history_t *h = (history_t *) p; + + while (h->list.prev != &h->list) + history_def_delete(h, ev, h->list.prev); + h->eventid = 0; + h->cur = 0; +} + + + + +/************************************************************************/ + +/* history_init(): + * Initialization function. + */ +public History * +history_init(void) +{ + History *h = (History *) h_malloc(sizeof(History)); + HistEvent ev; + + history_def_init(&h->h_ref, &ev, 0); + h->h_ent = -1; + h->h_next = history_def_next; + h->h_first = history_def_first; + h->h_last = history_def_last; + h->h_prev = history_def_prev; + h->h_curr = history_def_curr; + h->h_set = history_def_set; + h->h_clear = history_def_clear; + h->h_enter = history_def_enter; + h->h_add = history_def_add; + + return (h); +} + + +/* history_end(): + * clean up history; + */ +public void +history_end(History *h) +{ + HistEvent ev; + + if (h->h_next == history_def_next) + history_def_clear(h->h_ref, &ev); +} + + + +/* history_setsize(): + * Set history number of events + */ +private int +history_setsize(History *h, HistEvent *ev, int num) +{ + + if (h->h_next != history_def_next) { + he_seterrev(ev, _HE_NOT_ALLOWED); + return (-1); + } + if (num < 0) { + he_seterrev(ev, _HE_BAD_PARAM); + return (-1); + } + history_def_setsize(h->h_ref, num); + return (0); +} + + +/* history_getsize(): + * Get number of events currently in history + */ +private int +history_getsize(History *h, HistEvent *ev) +{ + int retval = 0; + + if (h->h_next != history_def_next) { + he_seterrev(ev, _HE_NOT_ALLOWED); + return (-1); + } + retval = history_def_getsize(h->h_ref); + if (retval < -1) { + he_seterrev(ev, _HE_SIZE_NEGATIVE); + return (-1); + } + ev->num = retval; + return (0); +} + + +/* history_set_fun(): + * Set history functions + */ +private int +history_set_fun(History *h, History *nh) +{ + HistEvent ev; + + if (nh->h_first == NULL || nh->h_next == NULL || nh->h_last == NULL || + nh->h_prev == NULL || nh->h_curr == NULL || nh->h_set == NULL || + nh->h_enter == NULL || nh->h_add == NULL || nh->h_clear == NULL || + nh->h_ref == NULL) { + if (h->h_next != history_def_next) { + history_def_init(&h->h_ref, &ev, 0); + h->h_first = history_def_first; + h->h_next = history_def_next; + h->h_last = history_def_last; + h->h_prev = history_def_prev; + h->h_curr = history_def_curr; + h->h_set = history_def_set; + h->h_clear = history_def_clear; + h->h_enter = history_def_enter; + h->h_add = history_def_add; + } + return (-1); + } + if (h->h_next == history_def_next) + history_def_clear(h->h_ref, &ev); + + h->h_ent = -1; + h->h_first = nh->h_first; + h->h_next = nh->h_next; + h->h_last = nh->h_last; + h->h_prev = nh->h_prev; + h->h_curr = nh->h_curr; + h->h_set = nh->h_set; + h->h_clear = nh->h_clear; + h->h_enter = nh->h_enter; + h->h_add = nh->h_add; + + return (0); +} + + +/* history_load(): + * History load function + */ +private int +history_load(History *h, const char *fname) +{ + FILE *fp; + char *line; + size_t sz, max_size; + char *ptr; + int i = -1; + HistEvent ev; + + if ((fp = fopen(fname, "r")) == NULL) + return (i); + + if ((line = fgetln(fp, &sz)) == NULL) + goto done; + + if (strncmp(line, hist_cookie, sz) != 0) + goto done; + + ptr = h_malloc(max_size = 1024); + for (i = 0; (line = fgetln(fp, &sz)) != NULL; i++) { + char c = line[sz]; + + if (sz != 0 && line[sz - 1] == '\n') + line[--sz] = '\0'; + else + line[sz] = '\0'; + + if (max_size < sz) { + max_size = (sz + 1023) & ~1023; + ptr = h_realloc(ptr, max_size); + } + (void) strunvis(ptr, line); + line[sz] = c; + HENTER(h, &ev, ptr); + } + h_free(ptr); + +done: + (void) fclose(fp); + return (i); +} + + +/* history_save(): + * History save function + */ +private int +history_save(History *h, const char *fname) +{ + FILE *fp; + HistEvent ev; + int i = 0, retval; + size_t len, max_size; + char *ptr; + + if ((fp = fopen(fname, "w")) == NULL) + return (-1); + + (void) fchmod(fileno(fp), S_IRUSR|S_IWUSR); + (void) fputs(hist_cookie, fp); + ptr = h_malloc(max_size = 1024); + for (retval = HLAST(h, &ev); + retval != -1; + retval = HPREV(h, &ev), i++) { + len = strlen(ev.str) * 4; + if (len >= max_size) { + max_size = (len + 1023) & 1023; + ptr = h_realloc(ptr, max_size); + } + (void) strvis(ptr, ev.str, VIS_WHITE); + (void) fprintf(fp, "%s\n", ev.str); + } + h_free(ptr); + (void) fclose(fp); + return (i); +} + + +/* history_prev_event(): + * Find the previous event, with number given + */ +private int +history_prev_event(History *h, HistEvent *ev, int num) +{ + int retval; + + for (retval = HCURR(h, ev); retval != -1; retval = HPREV(h, ev)) + if (ev->num == num) + return (0); + + he_seterrev(ev, _HE_NOT_FOUND); + return (-1); +} + + +/* history_next_event(): + * Find the next event, with number given + */ +private int +history_next_event(History *h, HistEvent *ev, int num) +{ + int retval; + + for (retval = HCURR(h, ev); retval != -1; retval = HNEXT(h, ev)) + if (ev->num == num) + return (0); + + he_seterrev(ev, _HE_NOT_FOUND); + return (-1); +} + + +/* history_prev_string(): + * Find the previous event beginning with string + */ +private int +history_prev_string(History *h, HistEvent *ev, const char *str) +{ + size_t len = strlen(str); + int retval; + + for (retval = HCURR(h, ev); retval != -1; retval = HNEXT(h, ev)) + if (strncmp(str, ev->str, len) == 0) + return (0); + + he_seterrev(ev, _HE_NOT_FOUND); + return (-1); +} + + +/* history_next_string(): + * Find the next event beginning with string + */ +private int +history_next_string(History *h, HistEvent *ev, const char *str) +{ + size_t len = strlen(str); + int retval; + + for (retval = HCURR(h, ev); retval != -1; retval = HPREV(h, ev)) + if (strncmp(str, ev->str, len) == 0) + return (0); + + he_seterrev(ev, _HE_NOT_FOUND); + return (-1); +} + + +/* history(): + * User interface to history functions. + */ +int +history(History *h, HistEvent *ev, int fun, ...) +{ + va_list va; + const char *str; + int retval; + + va_start(va, fun); + + he_seterrev(ev, _HE_OK); + + switch (fun) { + case H_GETSIZE: + retval = history_getsize(h, ev); + break; + + case H_SETSIZE: + retval = history_setsize(h, ev, va_arg(va, int)); + break; + + case H_ADD: + str = va_arg(va, const char *); + retval = HADD(h, ev, str); + break; + + case H_ENTER: + str = va_arg(va, const char *); + if ((retval = HENTER(h, ev, str)) != -1) + h->h_ent = ev->num; + break; + + case H_APPEND: + str = va_arg(va, const char *); + if ((retval = HSET(h, ev, h->h_ent)) != -1) + retval = HADD(h, ev, str); + break; + + case H_FIRST: + retval = HFIRST(h, ev); + break; + + case H_NEXT: + retval = HNEXT(h, ev); + break; + + case H_LAST: + retval = HLAST(h, ev); + break; + + case H_PREV: + retval = HPREV(h, ev); + break; + + case H_CURR: + retval = HCURR(h, ev); + break; + + case H_SET: + retval = HSET(h, ev, va_arg(va, const int)); + break; + + case H_CLEAR: + HCLEAR(h, ev); + retval = 0; + break; + + case H_LOAD: + retval = history_load(h, va_arg(va, const char *)); + if (retval == -1) + he_seterrev(ev, _HE_HIST_READ); + break; + + case H_SAVE: + retval = history_save(h, va_arg(va, const char *)); + if (retval == -1) + he_seterrev(ev, _HE_HIST_WRITE); + break; + + case H_PREV_EVENT: + retval = history_prev_event(h, ev, va_arg(va, int)); + break; + + case H_NEXT_EVENT: + retval = history_next_event(h, ev, va_arg(va, int)); + break; + + case H_PREV_STR: + retval = history_prev_string(h, ev, va_arg(va, const char *)); + break; + + case H_NEXT_STR: + retval = history_next_string(h, ev, va_arg(va, const char *)); + break; + + case H_FUNC: + { + History hf; + + hf.h_ref = va_arg(va, ptr_t); + h->h_ent = -1; + hf.h_first = va_arg(va, history_gfun_t); + hf.h_next = va_arg(va, history_gfun_t); + hf.h_last = va_arg(va, history_gfun_t); + hf.h_prev = va_arg(va, history_gfun_t); + hf.h_curr = va_arg(va, history_gfun_t); + hf.h_set = va_arg(va, history_sfun_t); + hf.h_clear = va_arg(va, history_vfun_t); + hf.h_enter = va_arg(va, history_efun_t); + hf.h_add = va_arg(va, history_efun_t); + + if ((retval = history_set_fun(h, &hf)) == -1) + he_seterrev(ev, _HE_PARAM_MISSING); + break; + } + + case H_END: + history_end(h); + retval = 0; + break; + + default: + retval = -1; + he_seterrev(ev, _HE_UNKNOWN); + break; + } + va_end(va); + return (retval); +} diff --git a/main/editline/install-sh b/main/editline/install-sh new file mode 100755 index 000000000..ebc66913e --- /dev/null +++ b/main/editline/install-sh @@ -0,0 +1,250 @@ +#! /bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5 (mit/util/scripts/install.sh). +# +# Copyright 1991 by the Massachusetts Institute of Technology +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of M.I.T. not be used in advertising or +# publicity pertaining to distribution of the software without specific, +# written prior permission. M.I.T. makes no representations about the +# suitability of this software for any purpose. It is provided "as is" +# without express or implied warranty. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. It can only install one file at a time, a restriction +# shared with many OS's install programs. + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +transformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + true +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + else + instcmd=mkdir + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f $src -o -d $src ] + then + true + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + true + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + true + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' +' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + true + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + true + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + + $doit $instcmd $src $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/main/editline/key.c b/main/editline/key.c new file mode 100644 index 000000000..0dcdf4191 --- /dev/null +++ b/main/editline/key.c @@ -0,0 +1,687 @@ +/* $NetBSD: key.c,v 1.13 2002/03/18 16:00:55 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)key.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: key.c,v 1.13 2002/03/18 16:00:55 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * key.c: This module contains the procedures for maintaining + * the extended-key map. + * + * An extended-key (key) is a sequence of keystrokes introduced + * with an sequence introducer and consisting of an arbitrary + * number of characters. This module maintains a map (the el->el_key.map) + * to convert these extended-key sequences into input strs + * (XK_STR), editor functions (XK_CMD), or unix commands (XK_EXE). + * + * Warning: + * If key is a substr of some other keys, then the longer + * keys are lost!! That is, if the keys "abcd" and "abcef" + * are in el->el_key.map, adding the key "abc" will cause the first two + * definitions to be lost. + * + * Restrictions: + * ------------- + * 1) It is not possible to have one key that is a + * substr of another. + */ +#include +#include + +#include "el.h" + +/* + * The Nodes of the el->el_key.map. The el->el_key.map is a linked list + * of these node elements + */ +struct key_node_t { + char ch; /* single character of key */ + int type; /* node type */ + key_value_t val; /* command code or pointer to str, */ + /* if this is a leaf */ + struct key_node_t *next; /* ptr to next char of this key */ + struct key_node_t *sibling; /* ptr to another key with same prefix*/ +}; + +private int node_trav(EditLine *, key_node_t *, char *, + key_value_t *); +private int node__try(EditLine *, key_node_t *, const char *, + key_value_t *, int); +private key_node_t *node__get(int); +private void node__put(EditLine *, key_node_t *); +private int node__delete(EditLine *, key_node_t **, const char *); +private int node_lookup(EditLine *, const char *, key_node_t *, + int); +private int node_enum(EditLine *, key_node_t *, int); +private int key__decode_char(char *, int, int); + +#define KEY_BUFSIZ EL_BUFSIZ + + +/* key_init(): + * Initialize the key maps + */ +protected int +key_init(EditLine *el) +{ + + el->el_key.buf = (char *) el_malloc(KEY_BUFSIZ); + if (el->el_key.buf == NULL) + return (-1); + el->el_key.map = NULL; + key_reset(el); + return (0); +} + + +/* key_end(): + * Free the key maps + */ +protected void +key_end(EditLine *el) +{ + + el_free((ptr_t) el->el_key.buf); + el->el_key.buf = NULL; + node__put(el, el->el_key.map); + el->el_key.map = NULL; +} + + +/* key_map_cmd(): + * Associate cmd with a key value + */ +protected key_value_t * +key_map_cmd(EditLine *el, int cmd) +{ + + el->el_key.val.cmd = (el_action_t) cmd; + return (&el->el_key.val); +} + + +/* key_map_str(): + * Associate str with a key value + */ +protected key_value_t * +key_map_str(EditLine *el, char *str) +{ + + el->el_key.val.str = str; + return (&el->el_key.val); +} + + +/* key_reset(): + * Takes all nodes on el->el_key.map and puts them on free list. Then + * initializes el->el_key.map with arrow keys + * [Always bind the ansi arrow keys?] + */ +protected void +key_reset(EditLine *el) +{ + + node__put(el, el->el_key.map); + el->el_key.map = NULL; + return; +} + + +/* key_get(): + * Calls the recursive function with entry point el->el_key.map + * Looks up *ch in map and then reads characters until a + * complete match is found or a mismatch occurs. Returns the + * type of the match found (XK_STR, XK_CMD, or XK_EXE). + * Returns NULL in val.str and XK_STR for no match. + * The last character read is returned in *ch. + */ +protected int +key_get(EditLine *el, char *ch, key_value_t *val) +{ + + return (node_trav(el, el->el_key.map, ch, val)); +} + + +/* key_add(): + * Adds key to the el->el_key.map and associates the value in val with it. + * If key is already is in el->el_key.map, the new code is applied to the + * existing key. Ntype specifies if code is a command, an + * out str or a unix command. + */ +protected void +key_add(EditLine *el, const char *key, key_value_t *val, int ntype) +{ + + if (key[0] == '\0') { + (void) fprintf(el->el_errfile, + "key_add: Null extended-key not allowed.\n"); + return; + } + if (ntype == XK_CMD && val->cmd == ED_SEQUENCE_LEAD_IN) { + (void) fprintf(el->el_errfile, + "key_add: sequence-lead-in command not allowed\n"); + return; + } + if (el->el_key.map == NULL) + /* tree is initially empty. Set up new node to match key[0] */ + el->el_key.map = node__get(key[0]); + /* it is properly initialized */ + + /* Now recurse through el->el_key.map */ + (void) node__try(el, el->el_key.map, key, val, ntype); + return; +} + + +/* key_clear(): + * + */ +protected void +key_clear(EditLine *el, el_action_t *map, const char *in) +{ + + if ((map[(unsigned char)*in] == ED_SEQUENCE_LEAD_IN) && + ((map == el->el_map.key && + el->el_map.alt[(unsigned char)*in] != ED_SEQUENCE_LEAD_IN) || + (map == el->el_map.alt && + el->el_map.key[(unsigned char)*in] != ED_SEQUENCE_LEAD_IN))) + (void) key_delete(el, in); +} + + +/* key_delete(): + * Delete the key and all longer keys staring with key, if + * they exists. + */ +protected int +key_delete(EditLine *el, const char *key) +{ + + if (key[0] == '\0') { + (void) fprintf(el->el_errfile, + "key_delete: Null extended-key not allowed.\n"); + return (-1); + } + if (el->el_key.map == NULL) + return (0); + + (void) node__delete(el, &el->el_key.map, key); + return (0); +} + + +/* key_print(): + * Print the binding associated with key key. + * Print entire el->el_key.map if null + */ +protected void +key_print(EditLine *el, const char *key) +{ + + /* do nothing if el->el_key.map is empty and null key specified */ + if (el->el_key.map == NULL && *key == 0) + return; + + el->el_key.buf[0] = '"'; + if (node_lookup(el, key, el->el_key.map, 1) <= -1) + /* key is not bound */ + (void) fprintf(el->el_errfile, "Unbound extended key \"%s\"\n", + key); + return; +} + + +/* node_trav(): + * recursively traverses node in tree until match or mismatch is + * found. May read in more characters. + */ +private int +node_trav(EditLine *el, key_node_t *ptr, char *ch, key_value_t *val) +{ + + if (ptr->ch == *ch) { + /* match found */ + if (ptr->next) { + /* key not complete so get next char */ + if (el_getc(el, ch) != 1) { /* if EOF or error */ + val->cmd = ED_END_OF_FILE; + return (XK_CMD); + /* PWP: Pretend we just read an end-of-file */ + } + return (node_trav(el, ptr->next, ch, val)); + } else { + *val = ptr->val; + if (ptr->type != XK_CMD) + *ch = '\0'; + return (ptr->type); + } + } else { + /* no match found here */ + if (ptr->sibling) { + /* try next sibling */ + return (node_trav(el, ptr->sibling, ch, val)); + } else { + /* no next sibling -- mismatch */ + val->str = NULL; + return (XK_STR); + } + } +} + + +/* node__try(): + * Find a node that matches *str or allocate a new one + */ +private int +node__try(EditLine *el, key_node_t *ptr, const char *str, key_value_t *val, int ntype) +{ + + if (ptr->ch != *str) { + key_node_t *xm; + + for (xm = ptr; xm->sibling != NULL; xm = xm->sibling) + if (xm->sibling->ch == *str) + break; + if (xm->sibling == NULL) + xm->sibling = node__get(*str); /* setup new node */ + ptr = xm->sibling; + } + if (*++str == '\0') { + /* we're there */ + if (ptr->next != NULL) { + node__put(el, ptr->next); + /* lose longer keys with this prefix */ + ptr->next = NULL; + } + switch (ptr->type) { + case XK_CMD: + case XK_NOD: + break; + case XK_STR: + case XK_EXE: + if (ptr->val.str) + el_free((ptr_t) ptr->val.str); + break; + default: + EL_ABORT((el->el_errfile, "Bad XK_ type %d\n", + ptr->type)); + break; + } + + switch (ptr->type = ntype) { + case XK_CMD: + ptr->val = *val; + break; + case XK_STR: + case XK_EXE: + ptr->val.str = strdup(val->str); + break; + default: + EL_ABORT((el->el_errfile, "Bad XK_ type %d\n", ntype)); + break; + } + } else { + /* still more chars to go */ + if (ptr->next == NULL) + ptr->next = node__get(*str); /* setup new node */ + (void) node__try(el, ptr->next, str, val, ntype); + } + return (0); +} + + +/* node__delete(): + * Delete node that matches str + */ +private int +node__delete(EditLine *el, key_node_t **inptr, const char *str) +{ + key_node_t *ptr; + key_node_t *prev_ptr = NULL; + + ptr = *inptr; + + if (ptr->ch != *str) { + key_node_t *xm; + + for (xm = ptr; xm->sibling != NULL; xm = xm->sibling) + if (xm->sibling->ch == *str) + break; + if (xm->sibling == NULL) + return (0); + prev_ptr = xm; + ptr = xm->sibling; + } + if (*++str == '\0') { + /* we're there */ + if (prev_ptr == NULL) + *inptr = ptr->sibling; + else + prev_ptr->sibling = ptr->sibling; + ptr->sibling = NULL; + node__put(el, ptr); + return (1); + } else if (ptr->next != NULL && + node__delete(el, &ptr->next, str) == 1) { + if (ptr->next != NULL) + return (0); + if (prev_ptr == NULL) + *inptr = ptr->sibling; + else + prev_ptr->sibling = ptr->sibling; + ptr->sibling = NULL; + node__put(el, ptr); + return (1); + } else { + return (0); + } +} + + +/* node__put(): + * Puts a tree of nodes onto free list using free(3). + */ +private void +node__put(EditLine *el, key_node_t *ptr) +{ + if (ptr == NULL) + return; + + if (ptr->next != NULL) { + node__put(el, ptr->next); + ptr->next = NULL; + } + node__put(el, ptr->sibling); + + switch (ptr->type) { + case XK_CMD: + case XK_NOD: + break; + case XK_EXE: + case XK_STR: + if (ptr->val.str != NULL) + el_free((ptr_t) ptr->val.str); + break; + default: + EL_ABORT((el->el_errfile, "Bad XK_ type %d\n", ptr->type)); + break; + } + el_free((ptr_t) ptr); +} + + +/* node__get(): + * Returns pointer to an key_node_t for ch. + */ +private key_node_t * +node__get(int ch) +{ + key_node_t *ptr; + + ptr = (key_node_t *) el_malloc((size_t) sizeof(key_node_t)); + if (ptr == NULL) + return NULL; + ptr->ch = ch; + ptr->type = XK_NOD; + ptr->val.str = NULL; + ptr->next = NULL; + ptr->sibling = NULL; + return (ptr); +} + + + +/* node_lookup(): + * look for the str starting at node ptr. + * Print if last node + */ +private int +node_lookup(EditLine *el, const char *str, key_node_t *ptr, int cnt) +{ + int ncnt; + + if (ptr == NULL) + return (-1); /* cannot have null ptr */ + + if (*str == 0) { + /* no more chars in str. node_enum from here. */ + (void) node_enum(el, ptr, cnt); + return (0); + } else { + /* If match put this char into el->el_key.buf. Recurse */ + if (ptr->ch == *str) { + /* match found */ + ncnt = key__decode_char(el->el_key.buf, cnt, + (unsigned char) ptr->ch); + if (ptr->next != NULL) + /* not yet at leaf */ + return (node_lookup(el, str + 1, ptr->next, + ncnt + 1)); + else { + /* next node is null so key should be complete */ + if (str[1] == 0) { + el->el_key.buf[ncnt + 1] = '"'; + el->el_key.buf[ncnt + 2] = '\0'; + key_kprint(el, el->el_key.buf, + &ptr->val, ptr->type); + return (0); + } else + return (-1); + /* mismatch -- str still has chars */ + } + } else { + /* no match found try sibling */ + if (ptr->sibling) + return (node_lookup(el, str, ptr->sibling, + cnt)); + else + return (-1); + } + } +} + + +/* node_enum(): + * Traverse the node printing the characters it is bound in buffer + */ +private int +node_enum(EditLine *el, key_node_t *ptr, int cnt) +{ + int ncnt; + + if (cnt >= KEY_BUFSIZ - 5) { /* buffer too small */ + el->el_key.buf[++cnt] = '"'; + el->el_key.buf[++cnt] = '\0'; + (void) fprintf(el->el_errfile, + "Some extended keys too long for internal print buffer"); + (void) fprintf(el->el_errfile, " \"%s...\"\n", el->el_key.buf); + return (0); + } + if (ptr == NULL) { +#ifdef DEBUG_EDIT + (void) fprintf(el->el_errfile, + "node_enum: BUG!! Null ptr passed\n!"); +#endif + return (-1); + } + /* put this char at end of str */ + ncnt = key__decode_char(el->el_key.buf, cnt, (unsigned char) ptr->ch); + if (ptr->next == NULL) { + /* print this key and function */ + el->el_key.buf[ncnt + 1] = '"'; + el->el_key.buf[ncnt + 2] = '\0'; + key_kprint(el, el->el_key.buf, &ptr->val, ptr->type); + } else + (void) node_enum(el, ptr->next, ncnt + 1); + + /* go to sibling if there is one */ + if (ptr->sibling) + (void) node_enum(el, ptr->sibling, cnt); + return (0); +} + + +/* key_kprint(): + * Print the specified key and its associated + * function specified by val + */ +protected void +key_kprint(EditLine *el, const char *key, key_value_t *val, int ntype) +{ + el_bindings_t *fp; + char unparsbuf[EL_BUFSIZ]; + static const char fmt[] = "%-15s-> %s\n"; + + if (val != NULL) + switch (ntype) { + case XK_STR: + case XK_EXE: + (void) fprintf(el->el_outfile, fmt, key, + key__decode_str(val->str, unparsbuf, + ntype == XK_STR ? "\"\"" : "[]")); + break; + case XK_CMD: + for (fp = el->el_map.help; fp->name; fp++) + if (val->cmd == fp->func) { + (void) fprintf(el->el_outfile, fmt, + key, fp->name); + break; + } +#ifdef DEBUG_KEY + if (fp->name == NULL) + (void) fprintf(el->el_outfile, + "BUG! Command not found.\n"); +#endif + + break; + default: + EL_ABORT((el->el_errfile, "Bad XK_ type %d\n", ntype)); + break; + } + else + (void) fprintf(el->el_outfile, fmt, key, "no input"); +} + + +/* key__decode_char(): + * Put a printable form of char in buf. + */ +private int +key__decode_char(char *buf, int cnt, int ch) +{ + if (ch == 0) { + buf[cnt++] = '^'; + buf[cnt] = '@'; + return (cnt); + } + if (iscntrl(ch)) { + buf[cnt++] = '^'; + if (ch == '\177') + buf[cnt] = '?'; + else + buf[cnt] = ch | 0100; + } else if (ch == '^') { + buf[cnt++] = '\\'; + buf[cnt] = '^'; + } else if (ch == '\\') { + buf[cnt++] = '\\'; + buf[cnt] = '\\'; + } else if (ch == ' ' || (isprint(ch) && !isspace(ch))) { + buf[cnt] = ch; + } else { + buf[cnt++] = '\\'; + buf[cnt++] = (((unsigned int) ch >> 6) & 7) + '0'; + buf[cnt++] = (((unsigned int) ch >> 3) & 7) + '0'; + buf[cnt] = (ch & 7) + '0'; + } + return (cnt); +} + + +/* key__decode_str(): + * Make a printable version of the ey + */ +protected char * +key__decode_str(const char *str, char *buf, const char *sep) +{ + char *b; + const char *p; + + b = buf; + if (sep[0] != '\0') + *b++ = sep[0]; + if (*str == 0) { + *b++ = '^'; + *b++ = '@'; + if (sep[0] != '\0' && sep[1] != '\0') + *b++ = sep[1]; + *b++ = 0; + return (buf); + } + for (p = str; *p != 0; p++) { + if (iscntrl((unsigned char) *p)) { + *b++ = '^'; + if (*p == '\177') + *b++ = '?'; + else + *b++ = *p | 0100; + } else if (*p == '^' || *p == '\\') { + *b++ = '\\'; + *b++ = *p; + } else if (*p == ' ' || (isprint((unsigned char) *p) && + !isspace((unsigned char) *p))) { + *b++ = *p; + } else { + *b++ = '\\'; + *b++ = (((unsigned int) *p >> 6) & 7) + '0'; + *b++ = (((unsigned int) *p >> 3) & 7) + '0'; + *b++ = (*p & 7) + '0'; + } + } + if (sep[0] != '\0' && sep[1] != '\0') + *b++ = sep[1]; + *b++ = 0; + return (buf); /* should check for overflow */ +} diff --git a/main/editline/key.h b/main/editline/key.h new file mode 100644 index 000000000..80d8626b8 --- /dev/null +++ b/main/editline/key.h @@ -0,0 +1,79 @@ +/* $NetBSD: key.h,v 1.6 2002/03/18 16:00:55 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)key.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.key.h: Key macro header + */ +#ifndef _h_el_key +#define _h_el_key + +typedef union key_value_t { + el_action_t cmd; /* If it is a command the # */ + char *str; /* If it is a string... */ +} key_value_t; + +typedef struct key_node_t key_node_t; + +typedef struct el_key_t { + char *buf; /* Key print buffer */ + key_node_t *map; /* Key map */ + key_value_t val; /* Local conversion buffer */ +} el_key_t; + +#define XK_CMD 0 +#define XK_STR 1 +#define XK_NOD 2 +#define XK_EXE 3 + +protected int key_init(EditLine *); +protected void key_end(EditLine *); +protected key_value_t *key_map_cmd(EditLine *, int); +protected key_value_t *key_map_str(EditLine *, char *); +protected void key_reset(EditLine *); +protected int key_get(EditLine *, char *, key_value_t *); +protected void key_add(EditLine *, const char *, key_value_t *, int); +protected void key_clear(EditLine *, el_action_t *, const char *); +protected int key_delete(EditLine *, const char *); +protected void key_print(EditLine *, const char *); +protected void key_kprint(EditLine *, const char *, key_value_t *, + int); +protected char *key__decode_str(const char *, char *, const char *); + +#endif /* _h_el_key */ diff --git a/main/editline/makelist b/main/editline/makelist new file mode 100644 index 000000000..36f434cd0 --- /dev/null +++ b/main/editline/makelist @@ -0,0 +1,254 @@ +#!/bin/sh - +# $NetBSD: makelist,v 1.7 2001/01/09 19:22:31 jdolecek Exp $ +# +# Copyright (c) 1992, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Christos Zoulas of Cornell University. +# +# 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. All advertising materials mentioning features or use of this software +# must display the following acknowledgement: +# This product includes software developed by the University of +# California, Berkeley and its contributors. +# 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. +# +# @(#)makelist 5.3 (Berkeley) 6/4/93 + +# makelist.sh: Automatically generate header files... + +AWK=/usr/bin/awk +USAGE="Usage: $0 -h|-e|-fc|-fh|-bc|-bh|-m " + +if [ "x$1" = "x" ] +then + echo $USAGE 1>&2 + exit 1 +fi + +FLAG="$1" +shift + +FILES="$@" + +case $FLAG in + +# generate foo.h file from foo.c +# +-h) + set - `echo $FILES | sed -e 's/\\./_/g'` + hdr="_h_`basename $1`" + cat $FILES | $AWK ' + BEGIN { + printf("/* Automatically generated file, do not edit */\n"); + printf("#ifndef %s\n#define %s\n", "'$hdr'", "'$hdr'"); + } + /\(\):/ { + pr = substr($2, 1, 2); + if (pr == "vi" || pr == "em" || pr == "ed") { + name = substr($2, 1, length($2) - 3); +# +# XXX: need a space between name and prototype so that -fc and -fh +# parsing is much easier +# + printf("protected el_action_t\t%s (EditLine *, int);\n", name); + } + } + END { + printf("#endif /* %s */\n", "'$hdr'"); + }' + ;; + +# generate help.c from various .c files +# +-bc) + cat $FILES | $AWK ' + BEGIN { + printf("/* Automatically generated file, do not edit */\n"); + printf("#include \"sys.h\"\n#include \"el.h\"\n"); + printf("private const struct el_bindings_t el_func_help[] = {\n"); + low = "abcdefghijklmnopqrstuvwxyz_"; + high = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_"; + for (i = 1; i <= length(low); i++) + tr[substr(low, i, 1)] = substr(high, i, 1); + } + /\(\):/ { + pr = substr($2, 1, 2); + if (pr == "vi" || pr == "em" || pr == "ed") { + name = substr($2, 1, length($2) - 3); + uname = ""; + fname = ""; + for (i = 1; i <= length(name); i++) { + s = substr(name, i, 1); + uname = uname tr[s]; + if (s == "_") + s = "-"; + fname = fname s; + } + + printf(" { %-30.30s %-30.30s\n","\"" fname "\",", uname ","); + ok = 1; + } + } + /^ \*/ { + if (ok) { + printf(" \""); + for (i = 2; i < NF; i++) + printf("%s ", $i); + printf("%s\" },\n", $i); + ok = 0; + } + } + END { + printf(" { NULL, 0, NULL }\n"); + printf("};\n"); + printf("\nprotected const el_bindings_t* help__get()"); + printf("{ return el_func_help; }\n"); + }' + ;; + +# generate help.h from various .c files +# +-bh) + $AWK ' + BEGIN { + printf("/* Automatically generated file, do not edit */\n"); + printf("#ifndef _h_help_c\n#define _h_help_c\n"); + printf("protected const el_bindings_t *help__get(void);\n"); + printf("#endif /* _h_help_c */\n"); + }' /dev/null + ;; + +# generate fcns.h from various .h files +# +-fh) + cat $FILES | $AWK '/el_action_t/ { print $3 }' | \ + sort | tr '[:lower:]' '[:upper:]' | $AWK ' + BEGIN { + printf("/* Automatically generated file, do not edit */\n"); + printf("#ifndef _h_fcns_c\n#define _h_fcns_c\n"); + count = 0; + } + { + printf("#define\t%-30.30s\t%3d\n", $1, count++); + } + END { + printf("#define\t%-30.30s\t%3d\n", "EL_NUM_FCNS", count); + + printf("typedef el_action_t (*el_func_t)(EditLine *, int);"); + printf("\nprotected const el_func_t* func__get(void);\n"); + printf("#endif /* _h_fcns_c */\n"); + }' + ;; + +# generate fcns.c from various .h files +# +-fc) + cat $FILES | $AWK '/el_action_t/ { print $3 }' | sort | $AWK ' + BEGIN { + printf("/* Automatically generated file, do not edit */\n"); + printf("#include \"sys.h\"\n#include \"el.h\"\n"); + printf("private const el_func_t el_func[] = {"); + maxlen = 80; + needn = 1; + len = 0; + } + { + clen = 25 + 2; + len += clen; + if (len >= maxlen) + needn = 1; + if (needn) { + printf("\n "); + needn = 0; + len = 4 + clen; + } + s = $1 ","; + printf("%-26.26s ", s); + } + END { + printf("\n};\n"); + printf("\nprotected const el_func_t* func__get() { return el_func; }\n"); + }' + ;; + +# generate editline.c from various .c files +# +-e) + echo "$FILES" | tr ' ' '\012' | $AWK ' + BEGIN { + printf("/* Automatically generated file, do not edit */\n"); + printf("#define protected static\n"); + printf("#define SCCSID\n"); + } + { + printf("#include \"%s\"\n", $1); + }' + ;; + +# generate man page fragment from various .c files +# +-m) + cat $FILES | $AWK ' + BEGIN { + printf(".\\\" Section automatically generated with makelist\n"); + printf(".Bl -tag -width 4n\n"); + } + /\(\):/ { + pr = substr($2, 1, 2); + if (pr == "vi" || pr == "em" || pr == "ed") { + name = substr($2, 1, length($2) - 3); + fname = ""; + for (i = 1; i <= length(name); i++) { + s = substr(name, i, 1); + if (s == "_") + s = "-"; + fname = fname s; + } + + printf(".It Ic %s\n", fname); + ok = 1; + } + } + /^ \*/ { + if (ok) { + for (i = 2; i < NF; i++) + printf("%s ", $i); + printf("%s.\n", $i); + ok = 0; + } + } + END { + printf(".El\n"); + printf(".\\\" End of section automatically generated with makelist\n"); + }' + ;; + +*) + echo $USAGE 1>&2 + exit 1 + ;; + +esac diff --git a/main/editline/map.c b/main/editline/map.c new file mode 100644 index 000000000..4187cb597 --- /dev/null +++ b/main/editline/map.c @@ -0,0 +1,1418 @@ +/* $NetBSD: map.c,v 1.15 2002/03/18 16:00:55 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)map.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: map.c,v 1.15 2002/03/18 16:00:55 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * map.c: Editor function definitions + */ +#include +#include "el.h" + +#define N_KEYS 256 + +private void map_print_key(EditLine *, el_action_t *, const char *); +private void map_print_some_keys(EditLine *, el_action_t *, int, int); +private void map_print_all_keys(EditLine *); +private void map_init_nls(EditLine *); +private void map_init_meta(EditLine *); + +/* keymap tables ; should be N_KEYS*sizeof(KEYCMD) bytes long */ + + +private const el_action_t el_map_emacs[] = { + /* 0 */ EM_SET_MARK, /* ^@ */ + /* 1 */ ED_MOVE_TO_BEG, /* ^A */ + /* 2 */ ED_PREV_CHAR, /* ^B */ + /* 3 */ ED_TTY_SIGINT, /* ^C */ + /* 4 */ EM_DELETE_OR_LIST, /* ^D */ + /* 5 */ ED_MOVE_TO_END, /* ^E */ + /* 6 */ ED_NEXT_CHAR, /* ^F */ + /* 7 */ ED_UNASSIGNED, /* ^G */ + /* 8 */ ED_DELETE_PREV_CHAR, /* ^H */ + /* 9 */ ED_UNASSIGNED, /* ^I */ + /* 10 */ ED_NEWLINE, /* ^J */ + /* 11 */ ED_KILL_LINE, /* ^K */ + /* 12 */ ED_CLEAR_SCREEN, /* ^L */ + /* 13 */ ED_NEWLINE, /* ^M */ + /* 14 */ ED_NEXT_HISTORY, /* ^N */ + /* 15 */ ED_TTY_FLUSH_OUTPUT, /* ^O */ + /* 16 */ ED_PREV_HISTORY, /* ^P */ + /* 17 */ ED_TTY_START_OUTPUT, /* ^Q */ + /* 18 */ ED_REDISPLAY, /* ^R */ + /* 19 */ ED_TTY_STOP_OUTPUT, /* ^S */ + /* 20 */ ED_TRANSPOSE_CHARS, /* ^T */ + /* 21 */ EM_KILL_LINE, /* ^U */ + /* 22 */ ED_QUOTED_INSERT, /* ^V */ + /* 23 */ EM_KILL_REGION, /* ^W */ + /* 24 */ ED_SEQUENCE_LEAD_IN, /* ^X */ + /* 25 */ EM_YANK, /* ^Y */ + /* 26 */ ED_TTY_SIGTSTP, /* ^Z */ + /* 27 */ EM_META_NEXT, /* ^[ */ + /* 28 */ ED_TTY_SIGQUIT, /* ^\ */ + /* 29 */ ED_TTY_DSUSP, /* ^] */ + /* 30 */ ED_UNASSIGNED, /* ^^ */ + /* 31 */ ED_UNASSIGNED, /* ^_ */ + /* 32 */ ED_INSERT, /* SPACE */ + /* 33 */ ED_INSERT, /* ! */ + /* 34 */ ED_INSERT, /* " */ + /* 35 */ ED_INSERT, /* # */ + /* 36 */ ED_INSERT, /* $ */ + /* 37 */ ED_INSERT, /* % */ + /* 38 */ ED_INSERT, /* & */ + /* 39 */ ED_INSERT, /* ' */ + /* 40 */ ED_INSERT, /* ( */ + /* 41 */ ED_INSERT, /* ) */ + /* 42 */ ED_INSERT, /* * */ + /* 43 */ ED_INSERT, /* + */ + /* 44 */ ED_INSERT, /* , */ + /* 45 */ ED_INSERT, /* - */ + /* 46 */ ED_INSERT, /* . */ + /* 47 */ ED_INSERT, /* / */ + /* 48 */ ED_DIGIT, /* 0 */ + /* 49 */ ED_DIGIT, /* 1 */ + /* 50 */ ED_DIGIT, /* 2 */ + /* 51 */ ED_DIGIT, /* 3 */ + /* 52 */ ED_DIGIT, /* 4 */ + /* 53 */ ED_DIGIT, /* 5 */ + /* 54 */ ED_DIGIT, /* 6 */ + /* 55 */ ED_DIGIT, /* 7 */ + /* 56 */ ED_DIGIT, /* 8 */ + /* 57 */ ED_DIGIT, /* 9 */ + /* 58 */ ED_INSERT, /* : */ + /* 59 */ ED_INSERT, /* ; */ + /* 60 */ ED_INSERT, /* < */ + /* 61 */ ED_INSERT, /* = */ + /* 62 */ ED_INSERT, /* > */ + /* 63 */ ED_INSERT, /* ? */ + /* 64 */ ED_INSERT, /* @ */ + /* 65 */ ED_INSERT, /* A */ + /* 66 */ ED_INSERT, /* B */ + /* 67 */ ED_INSERT, /* C */ + /* 68 */ ED_INSERT, /* D */ + /* 69 */ ED_INSERT, /* E */ + /* 70 */ ED_INSERT, /* F */ + /* 71 */ ED_INSERT, /* G */ + /* 72 */ ED_INSERT, /* H */ + /* 73 */ ED_INSERT, /* I */ + /* 74 */ ED_INSERT, /* J */ + /* 75 */ ED_INSERT, /* K */ + /* 76 */ ED_INSERT, /* L */ + /* 77 */ ED_INSERT, /* M */ + /* 78 */ ED_INSERT, /* N */ + /* 79 */ ED_INSERT, /* O */ + /* 80 */ ED_INSERT, /* P */ + /* 81 */ ED_INSERT, /* Q */ + /* 82 */ ED_INSERT, /* R */ + /* 83 */ ED_INSERT, /* S */ + /* 84 */ ED_INSERT, /* T */ + /* 85 */ ED_INSERT, /* U */ + /* 86 */ ED_INSERT, /* V */ + /* 87 */ ED_INSERT, /* W */ + /* 88 */ ED_INSERT, /* X */ + /* 89 */ ED_INSERT, /* Y */ + /* 90 */ ED_INSERT, /* Z */ + /* 91 */ ED_INSERT, /* [ */ + /* 92 */ ED_INSERT, /* \ */ + /* 93 */ ED_INSERT, /* ] */ + /* 94 */ ED_INSERT, /* ^ */ + /* 95 */ ED_INSERT, /* _ */ + /* 96 */ ED_INSERT, /* ` */ + /* 97 */ ED_INSERT, /* a */ + /* 98 */ ED_INSERT, /* b */ + /* 99 */ ED_INSERT, /* c */ + /* 100 */ ED_INSERT, /* d */ + /* 101 */ ED_INSERT, /* e */ + /* 102 */ ED_INSERT, /* f */ + /* 103 */ ED_INSERT, /* g */ + /* 104 */ ED_INSERT, /* h */ + /* 105 */ ED_INSERT, /* i */ + /* 106 */ ED_INSERT, /* j */ + /* 107 */ ED_INSERT, /* k */ + /* 108 */ ED_INSERT, /* l */ + /* 109 */ ED_INSERT, /* m */ + /* 110 */ ED_INSERT, /* n */ + /* 111 */ ED_INSERT, /* o */ + /* 112 */ ED_INSERT, /* p */ + /* 113 */ ED_INSERT, /* q */ + /* 114 */ ED_INSERT, /* r */ + /* 115 */ ED_INSERT, /* s */ + /* 116 */ ED_INSERT, /* t */ + /* 117 */ ED_INSERT, /* u */ + /* 118 */ ED_INSERT, /* v */ + /* 119 */ ED_INSERT, /* w */ + /* 120 */ ED_INSERT, /* x */ + /* 121 */ ED_INSERT, /* y */ + /* 122 */ ED_INSERT, /* z */ + /* 123 */ ED_INSERT, /* { */ + /* 124 */ ED_INSERT, /* | */ + /* 125 */ ED_INSERT, /* } */ + /* 126 */ ED_INSERT, /* ~ */ + /* 127 */ ED_DELETE_PREV_CHAR, /* ^? */ + /* 128 */ ED_UNASSIGNED, /* M-^@ */ + /* 129 */ ED_UNASSIGNED, /* M-^A */ + /* 130 */ ED_UNASSIGNED, /* M-^B */ + /* 131 */ ED_UNASSIGNED, /* M-^C */ + /* 132 */ ED_UNASSIGNED, /* M-^D */ + /* 133 */ ED_UNASSIGNED, /* M-^E */ + /* 134 */ ED_UNASSIGNED, /* M-^F */ + /* 135 */ ED_UNASSIGNED, /* M-^G */ + /* 136 */ ED_DELETE_PREV_WORD, /* M-^H */ + /* 137 */ ED_UNASSIGNED, /* M-^I */ + /* 138 */ ED_UNASSIGNED, /* M-^J */ + /* 139 */ ED_UNASSIGNED, /* M-^K */ + /* 140 */ ED_CLEAR_SCREEN, /* M-^L */ + /* 141 */ ED_UNASSIGNED, /* M-^M */ + /* 142 */ ED_UNASSIGNED, /* M-^N */ + /* 143 */ ED_UNASSIGNED, /* M-^O */ + /* 144 */ ED_UNASSIGNED, /* M-^P */ + /* 145 */ ED_UNASSIGNED, /* M-^Q */ + /* 146 */ ED_UNASSIGNED, /* M-^R */ + /* 147 */ ED_UNASSIGNED, /* M-^S */ + /* 148 */ ED_UNASSIGNED, /* M-^T */ + /* 149 */ ED_UNASSIGNED, /* M-^U */ + /* 150 */ ED_UNASSIGNED, /* M-^V */ + /* 151 */ ED_UNASSIGNED, /* M-^W */ + /* 152 */ ED_UNASSIGNED, /* M-^X */ + /* 153 */ ED_UNASSIGNED, /* M-^Y */ + /* 154 */ ED_UNASSIGNED, /* M-^Z */ + /* 155 */ ED_UNASSIGNED, /* M-^[ */ + /* 156 */ ED_UNASSIGNED, /* M-^\ */ + /* 157 */ ED_UNASSIGNED, /* M-^] */ + /* 158 */ ED_UNASSIGNED, /* M-^^ */ + /* 159 */ EM_COPY_PREV_WORD, /* M-^_ */ + /* 160 */ ED_UNASSIGNED, /* M-SPACE */ + /* 161 */ ED_UNASSIGNED, /* M-! */ + /* 162 */ ED_UNASSIGNED, /* M-" */ + /* 163 */ ED_UNASSIGNED, /* M-# */ + /* 164 */ ED_UNASSIGNED, /* M-$ */ + /* 165 */ ED_UNASSIGNED, /* M-% */ + /* 166 */ ED_UNASSIGNED, /* M-& */ + /* 167 */ ED_UNASSIGNED, /* M-' */ + /* 168 */ ED_UNASSIGNED, /* M-( */ + /* 169 */ ED_UNASSIGNED, /* M-) */ + /* 170 */ ED_UNASSIGNED, /* M-* */ + /* 171 */ ED_UNASSIGNED, /* M-+ */ + /* 172 */ ED_UNASSIGNED, /* M-, */ + /* 173 */ ED_UNASSIGNED, /* M-- */ + /* 174 */ ED_UNASSIGNED, /* M-. */ + /* 175 */ ED_UNASSIGNED, /* M-/ */ + /* 176 */ ED_ARGUMENT_DIGIT, /* M-0 */ + /* 177 */ ED_ARGUMENT_DIGIT, /* M-1 */ + /* 178 */ ED_ARGUMENT_DIGIT, /* M-2 */ + /* 179 */ ED_ARGUMENT_DIGIT, /* M-3 */ + /* 180 */ ED_ARGUMENT_DIGIT, /* M-4 */ + /* 181 */ ED_ARGUMENT_DIGIT, /* M-5 */ + /* 182 */ ED_ARGUMENT_DIGIT, /* M-6 */ + /* 183 */ ED_ARGUMENT_DIGIT, /* M-7 */ + /* 184 */ ED_ARGUMENT_DIGIT, /* M-8 */ + /* 185 */ ED_ARGUMENT_DIGIT, /* M-9 */ + /* 186 */ ED_UNASSIGNED, /* M-: */ + /* 187 */ ED_UNASSIGNED, /* M-; */ + /* 188 */ ED_UNASSIGNED, /* M-< */ + /* 189 */ ED_UNASSIGNED, /* M-= */ + /* 190 */ ED_UNASSIGNED, /* M-> */ + /* 191 */ ED_UNASSIGNED, /* M-? */ + /* 192 */ ED_UNASSIGNED, /* M-@ */ + /* 193 */ ED_UNASSIGNED, /* M-A */ + /* 194 */ ED_PREV_WORD, /* M-B */ + /* 195 */ EM_CAPITOL_CASE, /* M-C */ + /* 196 */ EM_DELETE_NEXT_WORD, /* M-D */ + /* 197 */ ED_UNASSIGNED, /* M-E */ + /* 198 */ EM_NEXT_WORD, /* M-F */ + /* 199 */ ED_UNASSIGNED, /* M-G */ + /* 200 */ ED_UNASSIGNED, /* M-H */ + /* 201 */ ED_UNASSIGNED, /* M-I */ + /* 202 */ ED_UNASSIGNED, /* M-J */ + /* 203 */ ED_UNASSIGNED, /* M-K */ + /* 204 */ EM_LOWER_CASE, /* M-L */ + /* 205 */ ED_UNASSIGNED, /* M-M */ + /* 206 */ ED_SEARCH_NEXT_HISTORY, /* M-N */ + /* 207 */ ED_SEQUENCE_LEAD_IN, /* M-O */ + /* 208 */ ED_SEARCH_PREV_HISTORY, /* M-P */ + /* 209 */ ED_UNASSIGNED, /* M-Q */ + /* 210 */ ED_UNASSIGNED, /* M-R */ + /* 211 */ ED_UNASSIGNED, /* M-S */ + /* 212 */ ED_UNASSIGNED, /* M-T */ + /* 213 */ EM_UPPER_CASE, /* M-U */ + /* 214 */ ED_UNASSIGNED, /* M-V */ + /* 215 */ EM_COPY_REGION, /* M-W */ + /* 216 */ ED_COMMAND, /* M-X */ + /* 217 */ ED_UNASSIGNED, /* M-Y */ + /* 218 */ ED_UNASSIGNED, /* M-Z */ + /* 219 */ ED_SEQUENCE_LEAD_IN, /* M-[ */ + /* 220 */ ED_UNASSIGNED, /* M-\ */ + /* 221 */ ED_UNASSIGNED, /* M-] */ + /* 222 */ ED_UNASSIGNED, /* M-^ */ + /* 223 */ ED_UNASSIGNED, /* M-_ */ + /* 223 */ ED_UNASSIGNED, /* M-` */ + /* 224 */ ED_UNASSIGNED, /* M-a */ + /* 225 */ ED_PREV_WORD, /* M-b */ + /* 226 */ EM_CAPITOL_CASE, /* M-c */ + /* 227 */ EM_DELETE_NEXT_WORD, /* M-d */ + /* 228 */ ED_UNASSIGNED, /* M-e */ + /* 229 */ EM_NEXT_WORD, /* M-f */ + /* 230 */ ED_UNASSIGNED, /* M-g */ + /* 231 */ ED_UNASSIGNED, /* M-h */ + /* 232 */ ED_UNASSIGNED, /* M-i */ + /* 233 */ ED_UNASSIGNED, /* M-j */ + /* 234 */ ED_UNASSIGNED, /* M-k */ + /* 235 */ EM_LOWER_CASE, /* M-l */ + /* 236 */ ED_UNASSIGNED, /* M-m */ + /* 237 */ ED_SEARCH_NEXT_HISTORY, /* M-n */ + /* 238 */ ED_UNASSIGNED, /* M-o */ + /* 239 */ ED_SEARCH_PREV_HISTORY, /* M-p */ + /* 240 */ ED_UNASSIGNED, /* M-q */ + /* 241 */ ED_UNASSIGNED, /* M-r */ + /* 242 */ ED_UNASSIGNED, /* M-s */ + /* 243 */ ED_UNASSIGNED, /* M-t */ + /* 244 */ EM_UPPER_CASE, /* M-u */ + /* 245 */ ED_UNASSIGNED, /* M-v */ + /* 246 */ EM_COPY_REGION, /* M-w */ + /* 247 */ ED_COMMAND, /* M-x */ + /* 248 */ ED_UNASSIGNED, /* M-y */ + /* 249 */ ED_UNASSIGNED, /* M-z */ + /* 250 */ ED_UNASSIGNED, /* M-{ */ + /* 251 */ ED_UNASSIGNED, /* M-| */ + /* 252 */ ED_UNASSIGNED, /* M-} */ + /* 253 */ ED_UNASSIGNED, /* M-~ */ + /* 254 */ ED_DELETE_PREV_WORD /* M-^? */ + /* 255 */ +}; + + +/* + * keymap table for vi. Each index into above tbl; should be + * N_KEYS entries long. Vi mode uses a sticky-extend to do command mode: + * insert mode characters are in the normal keymap, and command mode + * in the extended keymap. + */ +private const el_action_t el_map_vi_insert[] = { +#ifdef KSHVI + /* 0 */ ED_UNASSIGNED, /* ^@ */ + /* 1 */ ED_INSERT, /* ^A */ + /* 2 */ ED_INSERT, /* ^B */ + /* 3 */ ED_INSERT, /* ^C */ + /* 4 */ VI_LIST_OR_EOF, /* ^D */ + /* 5 */ ED_INSERT, /* ^E */ + /* 6 */ ED_INSERT, /* ^F */ + /* 7 */ ED_INSERT, /* ^G */ + /* 8 */ VI_DELETE_PREV_CHAR, /* ^H */ /* BackSpace key */ + /* 9 */ ED_INSERT, /* ^I */ /* Tab Key */ + /* 10 */ ED_NEWLINE, /* ^J */ + /* 11 */ ED_INSERT, /* ^K */ + /* 12 */ ED_INSERT, /* ^L */ + /* 13 */ ED_NEWLINE, /* ^M */ + /* 14 */ ED_INSERT, /* ^N */ + /* 15 */ ED_INSERT, /* ^O */ + /* 16 */ ED_INSERT, /* ^P */ + /* 17 */ ED_TTY_START_OUTPUT, /* ^Q */ + /* 18 */ ED_INSERT, /* ^R */ + /* 19 */ ED_TTY_STOP_OUTPUT, /* ^S */ + /* 20 */ ED_INSERT, /* ^T */ + /* 21 */ VI_KILL_LINE_PREV, /* ^U */ + /* 22 */ ED_QUOTED_INSERT, /* ^V */ + /* 23 */ ED_DELETE_PREV_WORD, /* ^W */ + /* ED_DELETE_PREV_WORD: Only until strt edit pos */ + /* 24 */ ED_INSERT, /* ^X */ + /* 25 */ ED_INSERT, /* ^Y */ + /* 26 */ ED_INSERT, /* ^Z */ + /* 27 */ VI_COMMAND_MODE, /* ^[ */ /* [ Esc ] key */ + /* 28 */ ED_TTY_SIGQUIT, /* ^\ */ + /* 29 */ ED_INSERT, /* ^] */ + /* 30 */ ED_INSERT, /* ^^ */ + /* 31 */ ED_INSERT, /* ^_ */ +#else /* !KSHVI */ + /* + * NOTE: These mappings do NOT Correspond well + * to the KSH VI editing assignments. + * On the other and they are convenient and + * many people have have gotten used to them. + */ + /* 0 */ ED_UNASSIGNED, /* ^@ */ + /* 1 */ ED_MOVE_TO_BEG, /* ^A */ + /* 2 */ ED_PREV_CHAR, /* ^B */ + /* 3 */ ED_TTY_SIGINT, /* ^C */ + /* 4 */ VI_LIST_OR_EOF, /* ^D */ + /* 5 */ ED_MOVE_TO_END, /* ^E */ + /* 6 */ ED_NEXT_CHAR, /* ^F */ + /* 7 */ ED_UNASSIGNED, /* ^G */ + /* 8 */ ED_DELETE_PREV_CHAR, /* ^H */ /* BackSpace key */ + /* 9 */ ED_UNASSIGNED, /* ^I */ /* Tab Key */ + /* 10 */ ED_NEWLINE, /* ^J */ + /* 11 */ ED_KILL_LINE, /* ^K */ + /* 12 */ ED_CLEAR_SCREEN, /* ^L */ + /* 13 */ ED_NEWLINE, /* ^M */ + /* 14 */ ED_NEXT_HISTORY, /* ^N */ + /* 15 */ ED_TTY_FLUSH_OUTPUT, /* ^O */ + /* 16 */ ED_PREV_HISTORY, /* ^P */ + /* 17 */ ED_TTY_START_OUTPUT, /* ^Q */ + /* 18 */ ED_REDISPLAY, /* ^R */ + /* 19 */ ED_TTY_STOP_OUTPUT, /* ^S */ + /* 20 */ ED_TRANSPOSE_CHARS, /* ^T */ + /* 21 */ VI_KILL_LINE_PREV, /* ^U */ + /* 22 */ ED_QUOTED_INSERT, /* ^V */ + /* 23 */ ED_DELETE_PREV_WORD, /* ^W */ + /* 24 */ ED_UNASSIGNED, /* ^X */ + /* 25 */ ED_TTY_DSUSP, /* ^Y */ + /* 26 */ ED_TTY_SIGTSTP, /* ^Z */ + /* 27 */ VI_COMMAND_MODE, /* ^[ */ + /* 28 */ ED_TTY_SIGQUIT, /* ^\ */ + /* 29 */ ED_UNASSIGNED, /* ^] */ + /* 30 */ ED_UNASSIGNED, /* ^^ */ + /* 31 */ ED_UNASSIGNED, /* ^_ */ +#endif /* KSHVI */ + /* 32 */ ED_INSERT, /* SPACE */ + /* 33 */ ED_INSERT, /* ! */ + /* 34 */ ED_INSERT, /* " */ + /* 35 */ ED_INSERT, /* # */ + /* 36 */ ED_INSERT, /* $ */ + /* 37 */ ED_INSERT, /* % */ + /* 38 */ ED_INSERT, /* & */ + /* 39 */ ED_INSERT, /* ' */ + /* 40 */ ED_INSERT, /* ( */ + /* 41 */ ED_INSERT, /* ) */ + /* 42 */ ED_INSERT, /* * */ + /* 43 */ ED_INSERT, /* + */ + /* 44 */ ED_INSERT, /* , */ + /* 45 */ ED_INSERT, /* - */ + /* 46 */ ED_INSERT, /* . */ + /* 47 */ ED_INSERT, /* / */ + /* 48 */ ED_INSERT, /* 0 */ + /* 49 */ ED_INSERT, /* 1 */ + /* 50 */ ED_INSERT, /* 2 */ + /* 51 */ ED_INSERT, /* 3 */ + /* 52 */ ED_INSERT, /* 4 */ + /* 53 */ ED_INSERT, /* 5 */ + /* 54 */ ED_INSERT, /* 6 */ + /* 55 */ ED_INSERT, /* 7 */ + /* 56 */ ED_INSERT, /* 8 */ + /* 57 */ ED_INSERT, /* 9 */ + /* 58 */ ED_INSERT, /* : */ + /* 59 */ ED_INSERT, /* ; */ + /* 60 */ ED_INSERT, /* < */ + /* 61 */ ED_INSERT, /* = */ + /* 62 */ ED_INSERT, /* > */ + /* 63 */ ED_INSERT, /* ? */ + /* 64 */ ED_INSERT, /* @ */ + /* 65 */ ED_INSERT, /* A */ + /* 66 */ ED_INSERT, /* B */ + /* 67 */ ED_INSERT, /* C */ + /* 68 */ ED_INSERT, /* D */ + /* 69 */ ED_INSERT, /* E */ + /* 70 */ ED_INSERT, /* F */ + /* 71 */ ED_INSERT, /* G */ + /* 72 */ ED_INSERT, /* H */ + /* 73 */ ED_INSERT, /* I */ + /* 74 */ ED_INSERT, /* J */ + /* 75 */ ED_INSERT, /* K */ + /* 76 */ ED_INSERT, /* L */ + /* 77 */ ED_INSERT, /* M */ + /* 78 */ ED_INSERT, /* N */ + /* 79 */ ED_INSERT, /* O */ + /* 80 */ ED_INSERT, /* P */ + /* 81 */ ED_INSERT, /* Q */ + /* 82 */ ED_INSERT, /* R */ + /* 83 */ ED_INSERT, /* S */ + /* 84 */ ED_INSERT, /* T */ + /* 85 */ ED_INSERT, /* U */ + /* 86 */ ED_INSERT, /* V */ + /* 87 */ ED_INSERT, /* W */ + /* 88 */ ED_INSERT, /* X */ + /* 89 */ ED_INSERT, /* Y */ + /* 90 */ ED_INSERT, /* Z */ + /* 91 */ ED_INSERT, /* [ */ + /* 92 */ ED_INSERT, /* \ */ + /* 93 */ ED_INSERT, /* ] */ + /* 94 */ ED_INSERT, /* ^ */ + /* 95 */ ED_INSERT, /* _ */ + /* 96 */ ED_INSERT, /* ` */ + /* 97 */ ED_INSERT, /* a */ + /* 98 */ ED_INSERT, /* b */ + /* 99 */ ED_INSERT, /* c */ + /* 100 */ ED_INSERT, /* d */ + /* 101 */ ED_INSERT, /* e */ + /* 102 */ ED_INSERT, /* f */ + /* 103 */ ED_INSERT, /* g */ + /* 104 */ ED_INSERT, /* h */ + /* 105 */ ED_INSERT, /* i */ + /* 106 */ ED_INSERT, /* j */ + /* 107 */ ED_INSERT, /* k */ + /* 108 */ ED_INSERT, /* l */ + /* 109 */ ED_INSERT, /* m */ + /* 110 */ ED_INSERT, /* n */ + /* 111 */ ED_INSERT, /* o */ + /* 112 */ ED_INSERT, /* p */ + /* 113 */ ED_INSERT, /* q */ + /* 114 */ ED_INSERT, /* r */ + /* 115 */ ED_INSERT, /* s */ + /* 116 */ ED_INSERT, /* t */ + /* 117 */ ED_INSERT, /* u */ + /* 118 */ ED_INSERT, /* v */ + /* 119 */ ED_INSERT, /* w */ + /* 120 */ ED_INSERT, /* x */ + /* 121 */ ED_INSERT, /* y */ + /* 122 */ ED_INSERT, /* z */ + /* 123 */ ED_INSERT, /* { */ + /* 124 */ ED_INSERT, /* | */ + /* 125 */ ED_INSERT, /* } */ + /* 126 */ ED_INSERT, /* ~ */ + /* 127 */ ED_DELETE_PREV_CHAR, /* ^? */ + /* 128 */ ED_UNASSIGNED, /* M-^@ */ + /* 129 */ ED_UNASSIGNED, /* M-^A */ + /* 130 */ ED_UNASSIGNED, /* M-^B */ + /* 131 */ ED_UNASSIGNED, /* M-^C */ + /* 132 */ ED_UNASSIGNED, /* M-^D */ + /* 133 */ ED_UNASSIGNED, /* M-^E */ + /* 134 */ ED_UNASSIGNED, /* M-^F */ + /* 135 */ ED_UNASSIGNED, /* M-^G */ + /* 136 */ ED_UNASSIGNED, /* M-^H */ + /* 137 */ ED_UNASSIGNED, /* M-^I */ + /* 138 */ ED_UNASSIGNED, /* M-^J */ + /* 139 */ ED_UNASSIGNED, /* M-^K */ + /* 140 */ ED_UNASSIGNED, /* M-^L */ + /* 141 */ ED_UNASSIGNED, /* M-^M */ + /* 142 */ ED_UNASSIGNED, /* M-^N */ + /* 143 */ ED_UNASSIGNED, /* M-^O */ + /* 144 */ ED_UNASSIGNED, /* M-^P */ + /* 145 */ ED_UNASSIGNED, /* M-^Q */ + /* 146 */ ED_UNASSIGNED, /* M-^R */ + /* 147 */ ED_UNASSIGNED, /* M-^S */ + /* 148 */ ED_UNASSIGNED, /* M-^T */ + /* 149 */ ED_UNASSIGNED, /* M-^U */ + /* 150 */ ED_UNASSIGNED, /* M-^V */ + /* 151 */ ED_UNASSIGNED, /* M-^W */ + /* 152 */ ED_UNASSIGNED, /* M-^X */ + /* 153 */ ED_UNASSIGNED, /* M-^Y */ + /* 154 */ ED_UNASSIGNED, /* M-^Z */ + /* 155 */ ED_UNASSIGNED, /* M-^[ */ + /* 156 */ ED_UNASSIGNED, /* M-^\ */ + /* 157 */ ED_UNASSIGNED, /* M-^] */ + /* 158 */ ED_UNASSIGNED, /* M-^^ */ + /* 159 */ ED_UNASSIGNED, /* M-^_ */ + /* 160 */ ED_UNASSIGNED, /* M-SPACE */ + /* 161 */ ED_UNASSIGNED, /* M-! */ + /* 162 */ ED_UNASSIGNED, /* M-" */ + /* 163 */ ED_UNASSIGNED, /* M-# */ + /* 164 */ ED_UNASSIGNED, /* M-$ */ + /* 165 */ ED_UNASSIGNED, /* M-% */ + /* 166 */ ED_UNASSIGNED, /* M-& */ + /* 167 */ ED_UNASSIGNED, /* M-' */ + /* 168 */ ED_UNASSIGNED, /* M-( */ + /* 169 */ ED_UNASSIGNED, /* M-) */ + /* 170 */ ED_UNASSIGNED, /* M-* */ + /* 171 */ ED_UNASSIGNED, /* M-+ */ + /* 172 */ ED_UNASSIGNED, /* M-, */ + /* 173 */ ED_UNASSIGNED, /* M-- */ + /* 174 */ ED_UNASSIGNED, /* M-. */ + /* 175 */ ED_UNASSIGNED, /* M-/ */ + /* 176 */ ED_UNASSIGNED, /* M-0 */ + /* 177 */ ED_UNASSIGNED, /* M-1 */ + /* 178 */ ED_UNASSIGNED, /* M-2 */ + /* 179 */ ED_UNASSIGNED, /* M-3 */ + /* 180 */ ED_UNASSIGNED, /* M-4 */ + /* 181 */ ED_UNASSIGNED, /* M-5 */ + /* 182 */ ED_UNASSIGNED, /* M-6 */ + /* 183 */ ED_UNASSIGNED, /* M-7 */ + /* 184 */ ED_UNASSIGNED, /* M-8 */ + /* 185 */ ED_UNASSIGNED, /* M-9 */ + /* 186 */ ED_UNASSIGNED, /* M-: */ + /* 187 */ ED_UNASSIGNED, /* M-; */ + /* 188 */ ED_UNASSIGNED, /* M-< */ + /* 189 */ ED_UNASSIGNED, /* M-= */ + /* 190 */ ED_UNASSIGNED, /* M-> */ + /* 191 */ ED_UNASSIGNED, /* M-? */ + /* 192 */ ED_UNASSIGNED, /* M-@ */ + /* 193 */ ED_UNASSIGNED, /* M-A */ + /* 194 */ ED_UNASSIGNED, /* M-B */ + /* 195 */ ED_UNASSIGNED, /* M-C */ + /* 196 */ ED_UNASSIGNED, /* M-D */ + /* 197 */ ED_UNASSIGNED, /* M-E */ + /* 198 */ ED_UNASSIGNED, /* M-F */ + /* 199 */ ED_UNASSIGNED, /* M-G */ + /* 200 */ ED_UNASSIGNED, /* M-H */ + /* 201 */ ED_UNASSIGNED, /* M-I */ + /* 202 */ ED_UNASSIGNED, /* M-J */ + /* 203 */ ED_UNASSIGNED, /* M-K */ + /* 204 */ ED_UNASSIGNED, /* M-L */ + /* 205 */ ED_UNASSIGNED, /* M-M */ + /* 206 */ ED_UNASSIGNED, /* M-N */ + /* 207 */ ED_UNASSIGNED, /* M-O */ + /* 208 */ ED_UNASSIGNED, /* M-P */ + /* 209 */ ED_UNASSIGNED, /* M-Q */ + /* 210 */ ED_UNASSIGNED, /* M-R */ + /* 211 */ ED_UNASSIGNED, /* M-S */ + /* 212 */ ED_UNASSIGNED, /* M-T */ + /* 213 */ ED_UNASSIGNED, /* M-U */ + /* 214 */ ED_UNASSIGNED, /* M-V */ + /* 215 */ ED_UNASSIGNED, /* M-W */ + /* 216 */ ED_UNASSIGNED, /* M-X */ + /* 217 */ ED_UNASSIGNED, /* M-Y */ + /* 218 */ ED_UNASSIGNED, /* M-Z */ + /* 219 */ ED_UNASSIGNED, /* M-[ */ + /* 220 */ ED_UNASSIGNED, /* M-\ */ + /* 221 */ ED_UNASSIGNED, /* M-] */ + /* 222 */ ED_UNASSIGNED, /* M-^ */ + /* 223 */ ED_UNASSIGNED, /* M-_ */ + /* 224 */ ED_UNASSIGNED, /* M-` */ + /* 225 */ ED_UNASSIGNED, /* M-a */ + /* 226 */ ED_UNASSIGNED, /* M-b */ + /* 227 */ ED_UNASSIGNED, /* M-c */ + /* 228 */ ED_UNASSIGNED, /* M-d */ + /* 229 */ ED_UNASSIGNED, /* M-e */ + /* 230 */ ED_UNASSIGNED, /* M-f */ + /* 231 */ ED_UNASSIGNED, /* M-g */ + /* 232 */ ED_UNASSIGNED, /* M-h */ + /* 233 */ ED_UNASSIGNED, /* M-i */ + /* 234 */ ED_UNASSIGNED, /* M-j */ + /* 235 */ ED_UNASSIGNED, /* M-k */ + /* 236 */ ED_UNASSIGNED, /* M-l */ + /* 237 */ ED_UNASSIGNED, /* M-m */ + /* 238 */ ED_UNASSIGNED, /* M-n */ + /* 239 */ ED_UNASSIGNED, /* M-o */ + /* 240 */ ED_UNASSIGNED, /* M-p */ + /* 241 */ ED_UNASSIGNED, /* M-q */ + /* 242 */ ED_UNASSIGNED, /* M-r */ + /* 243 */ ED_UNASSIGNED, /* M-s */ + /* 244 */ ED_UNASSIGNED, /* M-t */ + /* 245 */ ED_UNASSIGNED, /* M-u */ + /* 246 */ ED_UNASSIGNED, /* M-v */ + /* 247 */ ED_UNASSIGNED, /* M-w */ + /* 248 */ ED_UNASSIGNED, /* M-x */ + /* 249 */ ED_UNASSIGNED, /* M-y */ + /* 250 */ ED_UNASSIGNED, /* M-z */ + /* 251 */ ED_UNASSIGNED, /* M-{ */ + /* 252 */ ED_UNASSIGNED, /* M-| */ + /* 253 */ ED_UNASSIGNED, /* M-} */ + /* 254 */ ED_UNASSIGNED, /* M-~ */ + /* 255 */ ED_UNASSIGNED /* M-^? */ +}; + +private const el_action_t el_map_vi_command[] = { + /* 0 */ ED_UNASSIGNED, /* ^@ */ + /* 1 */ ED_MOVE_TO_BEG, /* ^A */ + /* 2 */ ED_UNASSIGNED, /* ^B */ + /* 3 */ ED_TTY_SIGINT, /* ^C */ + /* 4 */ ED_UNASSIGNED, /* ^D */ + /* 5 */ ED_MOVE_TO_END, /* ^E */ + /* 6 */ ED_UNASSIGNED, /* ^F */ + /* 7 */ ED_UNASSIGNED, /* ^G */ + /* 8 */ ED_PREV_CHAR, /* ^H */ + /* 9 */ ED_UNASSIGNED, /* ^I */ + /* 10 */ ED_NEWLINE, /* ^J */ + /* 11 */ ED_KILL_LINE, /* ^K */ + /* 12 */ ED_CLEAR_SCREEN, /* ^L */ + /* 13 */ ED_NEWLINE, /* ^M */ + /* 14 */ ED_NEXT_HISTORY, /* ^N */ + /* 15 */ ED_TTY_FLUSH_OUTPUT, /* ^O */ + /* 16 */ ED_PREV_HISTORY, /* ^P */ + /* 17 */ ED_TTY_START_OUTPUT, /* ^Q */ + /* 18 */ ED_REDISPLAY, /* ^R */ + /* 19 */ ED_TTY_STOP_OUTPUT, /* ^S */ + /* 20 */ ED_UNASSIGNED, /* ^T */ + /* 21 */ VI_KILL_LINE_PREV, /* ^U */ + /* 22 */ ED_UNASSIGNED, /* ^V */ + /* 23 */ ED_DELETE_PREV_WORD, /* ^W */ + /* 24 */ ED_UNASSIGNED, /* ^X */ + /* 25 */ ED_UNASSIGNED, /* ^Y */ + /* 26 */ ED_UNASSIGNED, /* ^Z */ + /* 27 */ EM_META_NEXT, /* ^[ */ + /* 28 */ ED_TTY_SIGQUIT, /* ^\ */ + /* 29 */ ED_UNASSIGNED, /* ^] */ + /* 30 */ ED_UNASSIGNED, /* ^^ */ + /* 31 */ ED_UNASSIGNED, /* ^_ */ + /* 32 */ ED_NEXT_CHAR, /* SPACE */ + /* 33 */ ED_UNASSIGNED, /* ! */ + /* 34 */ ED_UNASSIGNED, /* " */ + /* 35 */ ED_UNASSIGNED, /* # */ + /* 36 */ ED_MOVE_TO_END, /* $ */ + /* 37 */ ED_UNASSIGNED, /* % */ + /* 38 */ ED_UNASSIGNED, /* & */ + /* 39 */ ED_UNASSIGNED, /* ' */ + /* 40 */ ED_UNASSIGNED, /* ( */ + /* 41 */ ED_UNASSIGNED, /* ) */ + /* 42 */ ED_UNASSIGNED, /* * */ + /* 43 */ ED_NEXT_HISTORY, /* + */ + /* 44 */ VI_REPEAT_PREV_CHAR, /* , */ + /* 45 */ ED_PREV_HISTORY, /* - */ + /* 46 */ ED_UNASSIGNED, /* . */ + /* 47 */ VI_SEARCH_PREV, /* / */ + /* 48 */ VI_ZERO, /* 0 */ + /* 49 */ ED_ARGUMENT_DIGIT, /* 1 */ + /* 50 */ ED_ARGUMENT_DIGIT, /* 2 */ + /* 51 */ ED_ARGUMENT_DIGIT, /* 3 */ + /* 52 */ ED_ARGUMENT_DIGIT, /* 4 */ + /* 53 */ ED_ARGUMENT_DIGIT, /* 5 */ + /* 54 */ ED_ARGUMENT_DIGIT, /* 6 */ + /* 55 */ ED_ARGUMENT_DIGIT, /* 7 */ + /* 56 */ ED_ARGUMENT_DIGIT, /* 8 */ + /* 57 */ ED_ARGUMENT_DIGIT, /* 9 */ + /* 58 */ ED_COMMAND, /* : */ + /* 59 */ VI_REPEAT_NEXT_CHAR, /* ; */ + /* 60 */ ED_UNASSIGNED, /* < */ + /* 61 */ ED_UNASSIGNED, /* = */ + /* 62 */ ED_UNASSIGNED, /* > */ + /* 63 */ VI_SEARCH_NEXT, /* ? */ + /* 64 */ ED_UNASSIGNED, /* @ */ + /* 65 */ VI_ADD_AT_EOL, /* A */ + /* 66 */ VI_PREV_SPACE_WORD, /* B */ + /* 67 */ VI_CHANGE_TO_EOL, /* C */ + /* 68 */ ED_KILL_LINE, /* D */ + /* 69 */ VI_TO_END_WORD, /* E */ + /* 70 */ VI_PREV_CHAR, /* F */ + /* 71 */ ED_UNASSIGNED, /* G */ + /* 72 */ ED_UNASSIGNED, /* H */ + /* 73 */ VI_INSERT_AT_BOL, /* I */ + /* 74 */ ED_SEARCH_NEXT_HISTORY, /* J */ + /* 75 */ ED_SEARCH_PREV_HISTORY, /* K */ + /* 76 */ ED_UNASSIGNED, /* L */ + /* 77 */ ED_UNASSIGNED, /* M */ + /* 78 */ VI_REPEAT_SEARCH_PREV, /* N */ + /* 79 */ ED_SEQUENCE_LEAD_IN, /* O */ + /* 80 */ VI_PASTE_PREV, /* P */ + /* 81 */ ED_UNASSIGNED, /* Q */ + /* 82 */ VI_REPLACE_MODE, /* R */ + /* 83 */ VI_SUBSTITUTE_LINE, /* S */ + /* 84 */ VI_TO_PREV_CHAR, /* T */ + /* 85 */ ED_UNASSIGNED, /* U */ + /* 86 */ ED_UNASSIGNED, /* V */ + /* 87 */ VI_NEXT_SPACE_WORD, /* W */ + /* 88 */ ED_DELETE_PREV_CHAR, /* X */ + /* 89 */ ED_UNASSIGNED, /* Y */ + /* 90 */ ED_UNASSIGNED, /* Z */ + /* 91 */ ED_SEQUENCE_LEAD_IN, /* [ */ + /* 92 */ ED_UNASSIGNED, /* \ */ + /* 93 */ ED_UNASSIGNED, /* ] */ + /* 94 */ ED_MOVE_TO_BEG, /* ^ */ + /* 95 */ ED_UNASSIGNED, /* _ */ + /* 96 */ ED_UNASSIGNED, /* ` */ + /* 97 */ VI_ADD, /* a */ + /* 98 */ VI_PREV_WORD, /* b */ + /* 99 */ VI_CHANGE_META, /* c */ + /* 100 */ VI_DELETE_META, /* d */ + /* 101 */ VI_END_WORD, /* e */ + /* 102 */ VI_NEXT_CHAR, /* f */ + /* 103 */ ED_UNASSIGNED, /* g */ + /* 104 */ ED_PREV_CHAR, /* h */ + /* 105 */ VI_INSERT, /* i */ + /* 106 */ ED_NEXT_HISTORY, /* j */ + /* 107 */ ED_PREV_HISTORY, /* k */ + /* 108 */ ED_NEXT_CHAR, /* l */ + /* 109 */ ED_UNASSIGNED, /* m */ + /* 110 */ VI_REPEAT_SEARCH_NEXT, /* n */ + /* 111 */ ED_UNASSIGNED, /* o */ + /* 112 */ VI_PASTE_NEXT, /* p */ + /* 113 */ ED_UNASSIGNED, /* q */ + /* 114 */ VI_REPLACE_CHAR, /* r */ + /* 115 */ VI_SUBSTITUTE_CHAR, /* s */ + /* 116 */ VI_TO_NEXT_CHAR, /* t */ + /* 117 */ VI_UNDO, /* u */ + /* 118 */ ED_UNASSIGNED, /* v */ + /* 119 */ VI_NEXT_WORD, /* w */ + /* 120 */ ED_DELETE_NEXT_CHAR, /* x */ + /* 121 */ ED_UNASSIGNED, /* y */ + /* 122 */ ED_UNASSIGNED, /* z */ + /* 123 */ ED_UNASSIGNED, /* { */ + /* 124 */ ED_UNASSIGNED, /* | */ + /* 125 */ ED_UNASSIGNED, /* } */ + /* 126 */ VI_CHANGE_CASE, /* ~ */ + /* 127 */ ED_DELETE_PREV_CHAR, /* ^? */ + /* 128 */ ED_UNASSIGNED, /* M-^@ */ + /* 129 */ ED_UNASSIGNED, /* M-^A */ + /* 130 */ ED_UNASSIGNED, /* M-^B */ + /* 131 */ ED_UNASSIGNED, /* M-^C */ + /* 132 */ ED_UNASSIGNED, /* M-^D */ + /* 133 */ ED_UNASSIGNED, /* M-^E */ + /* 134 */ ED_UNASSIGNED, /* M-^F */ + /* 135 */ ED_UNASSIGNED, /* M-^G */ + /* 136 */ ED_UNASSIGNED, /* M-^H */ + /* 137 */ ED_UNASSIGNED, /* M-^I */ + /* 138 */ ED_UNASSIGNED, /* M-^J */ + /* 139 */ ED_UNASSIGNED, /* M-^K */ + /* 140 */ ED_UNASSIGNED, /* M-^L */ + /* 141 */ ED_UNASSIGNED, /* M-^M */ + /* 142 */ ED_UNASSIGNED, /* M-^N */ + /* 143 */ ED_UNASSIGNED, /* M-^O */ + /* 144 */ ED_UNASSIGNED, /* M-^P */ + /* 145 */ ED_UNASSIGNED, /* M-^Q */ + /* 146 */ ED_UNASSIGNED, /* M-^R */ + /* 147 */ ED_UNASSIGNED, /* M-^S */ + /* 148 */ ED_UNASSIGNED, /* M-^T */ + /* 149 */ ED_UNASSIGNED, /* M-^U */ + /* 150 */ ED_UNASSIGNED, /* M-^V */ + /* 151 */ ED_UNASSIGNED, /* M-^W */ + /* 152 */ ED_UNASSIGNED, /* M-^X */ + /* 153 */ ED_UNASSIGNED, /* M-^Y */ + /* 154 */ ED_UNASSIGNED, /* M-^Z */ + /* 155 */ ED_UNASSIGNED, /* M-^[ */ + /* 156 */ ED_UNASSIGNED, /* M-^\ */ + /* 157 */ ED_UNASSIGNED, /* M-^] */ + /* 158 */ ED_UNASSIGNED, /* M-^^ */ + /* 159 */ ED_UNASSIGNED, /* M-^_ */ + /* 160 */ ED_UNASSIGNED, /* M-SPACE */ + /* 161 */ ED_UNASSIGNED, /* M-! */ + /* 162 */ ED_UNASSIGNED, /* M-" */ + /* 163 */ ED_UNASSIGNED, /* M-# */ + /* 164 */ ED_UNASSIGNED, /* M-$ */ + /* 165 */ ED_UNASSIGNED, /* M-% */ + /* 166 */ ED_UNASSIGNED, /* M-& */ + /* 167 */ ED_UNASSIGNED, /* M-' */ + /* 168 */ ED_UNASSIGNED, /* M-( */ + /* 169 */ ED_UNASSIGNED, /* M-) */ + /* 170 */ ED_UNASSIGNED, /* M-* */ + /* 171 */ ED_UNASSIGNED, /* M-+ */ + /* 172 */ ED_UNASSIGNED, /* M-, */ + /* 173 */ ED_UNASSIGNED, /* M-- */ + /* 174 */ ED_UNASSIGNED, /* M-. */ + /* 175 */ ED_UNASSIGNED, /* M-/ */ + /* 176 */ ED_UNASSIGNED, /* M-0 */ + /* 177 */ ED_UNASSIGNED, /* M-1 */ + /* 178 */ ED_UNASSIGNED, /* M-2 */ + /* 179 */ ED_UNASSIGNED, /* M-3 */ + /* 180 */ ED_UNASSIGNED, /* M-4 */ + /* 181 */ ED_UNASSIGNED, /* M-5 */ + /* 182 */ ED_UNASSIGNED, /* M-6 */ + /* 183 */ ED_UNASSIGNED, /* M-7 */ + /* 184 */ ED_UNASSIGNED, /* M-8 */ + /* 185 */ ED_UNASSIGNED, /* M-9 */ + /* 186 */ ED_UNASSIGNED, /* M-: */ + /* 187 */ ED_UNASSIGNED, /* M-; */ + /* 188 */ ED_UNASSIGNED, /* M-< */ + /* 189 */ ED_UNASSIGNED, /* M-= */ + /* 190 */ ED_UNASSIGNED, /* M-> */ + /* 191 */ ED_UNASSIGNED, /* M-? */ + /* 192 */ ED_UNASSIGNED, /* M-@ */ + /* 193 */ ED_UNASSIGNED, /* M-A */ + /* 194 */ ED_UNASSIGNED, /* M-B */ + /* 195 */ ED_UNASSIGNED, /* M-C */ + /* 196 */ ED_UNASSIGNED, /* M-D */ + /* 197 */ ED_UNASSIGNED, /* M-E */ + /* 198 */ ED_UNASSIGNED, /* M-F */ + /* 199 */ ED_UNASSIGNED, /* M-G */ + /* 200 */ ED_UNASSIGNED, /* M-H */ + /* 201 */ ED_UNASSIGNED, /* M-I */ + /* 202 */ ED_UNASSIGNED, /* M-J */ + /* 203 */ ED_UNASSIGNED, /* M-K */ + /* 204 */ ED_UNASSIGNED, /* M-L */ + /* 205 */ ED_UNASSIGNED, /* M-M */ + /* 206 */ ED_UNASSIGNED, /* M-N */ + /* 207 */ ED_SEQUENCE_LEAD_IN, /* M-O */ + /* 208 */ ED_UNASSIGNED, /* M-P */ + /* 209 */ ED_UNASSIGNED, /* M-Q */ + /* 210 */ ED_UNASSIGNED, /* M-R */ + /* 211 */ ED_UNASSIGNED, /* M-S */ + /* 212 */ ED_UNASSIGNED, /* M-T */ + /* 213 */ ED_UNASSIGNED, /* M-U */ + /* 214 */ ED_UNASSIGNED, /* M-V */ + /* 215 */ ED_UNASSIGNED, /* M-W */ + /* 216 */ ED_UNASSIGNED, /* M-X */ + /* 217 */ ED_UNASSIGNED, /* M-Y */ + /* 218 */ ED_UNASSIGNED, /* M-Z */ + /* 219 */ ED_SEQUENCE_LEAD_IN, /* M-[ */ + /* 220 */ ED_UNASSIGNED, /* M-\ */ + /* 221 */ ED_UNASSIGNED, /* M-] */ + /* 222 */ ED_UNASSIGNED, /* M-^ */ + /* 223 */ ED_UNASSIGNED, /* M-_ */ + /* 224 */ ED_UNASSIGNED, /* M-` */ + /* 225 */ ED_UNASSIGNED, /* M-a */ + /* 226 */ ED_UNASSIGNED, /* M-b */ + /* 227 */ ED_UNASSIGNED, /* M-c */ + /* 228 */ ED_UNASSIGNED, /* M-d */ + /* 229 */ ED_UNASSIGNED, /* M-e */ + /* 230 */ ED_UNASSIGNED, /* M-f */ + /* 231 */ ED_UNASSIGNED, /* M-g */ + /* 232 */ ED_UNASSIGNED, /* M-h */ + /* 233 */ ED_UNASSIGNED, /* M-i */ + /* 234 */ ED_UNASSIGNED, /* M-j */ + /* 235 */ ED_UNASSIGNED, /* M-k */ + /* 236 */ ED_UNASSIGNED, /* M-l */ + /* 237 */ ED_UNASSIGNED, /* M-m */ + /* 238 */ ED_UNASSIGNED, /* M-n */ + /* 239 */ ED_UNASSIGNED, /* M-o */ + /* 240 */ ED_UNASSIGNED, /* M-p */ + /* 241 */ ED_UNASSIGNED, /* M-q */ + /* 242 */ ED_UNASSIGNED, /* M-r */ + /* 243 */ ED_UNASSIGNED, /* M-s */ + /* 244 */ ED_UNASSIGNED, /* M-t */ + /* 245 */ ED_UNASSIGNED, /* M-u */ + /* 246 */ ED_UNASSIGNED, /* M-v */ + /* 247 */ ED_UNASSIGNED, /* M-w */ + /* 248 */ ED_UNASSIGNED, /* M-x */ + /* 249 */ ED_UNASSIGNED, /* M-y */ + /* 250 */ ED_UNASSIGNED, /* M-z */ + /* 251 */ ED_UNASSIGNED, /* M-{ */ + /* 252 */ ED_UNASSIGNED, /* M-| */ + /* 253 */ ED_UNASSIGNED, /* M-} */ + /* 254 */ ED_UNASSIGNED, /* M-~ */ + /* 255 */ ED_UNASSIGNED /* M-^? */ +}; + + +/* map_init(): + * Initialize and allocate the maps + */ +protected int +map_init(EditLine *el) +{ + + /* + * Make sure those are correct before starting. + */ +#ifdef MAP_DEBUG + if (sizeof(el_map_emacs) != N_KEYS * sizeof(el_action_t)) + EL_ABORT((el->errfile, "Emacs map incorrect\n")); + if (sizeof(el_map_vi_command) != N_KEYS * sizeof(el_action_t)) + EL_ABORT((el->errfile, "Vi command map incorrect\n")); + if (sizeof(el_map_vi_insert) != N_KEYS * sizeof(el_action_t)) + EL_ABORT((el->errfile, "Vi insert map incorrect\n")); +#endif + + el->el_map.alt = (el_action_t *)el_malloc(sizeof(el_action_t) * N_KEYS); + if (el->el_map.alt == NULL) + return (-1); + el->el_map.key = (el_action_t *)el_malloc(sizeof(el_action_t) * N_KEYS); + if (el->el_map.key == NULL) + return (-1); + el->el_map.emacs = el_map_emacs; + el->el_map.vic = el_map_vi_command; + el->el_map.vii = el_map_vi_insert; + el->el_map.help = (el_bindings_t *) el_malloc(sizeof(el_bindings_t) * + EL_NUM_FCNS); + if (el->el_map.help == NULL) + return (-1); + (void) memcpy(el->el_map.help, help__get(), + sizeof(el_bindings_t) * EL_NUM_FCNS); + el->el_map.func = (el_func_t *)el_malloc(sizeof(el_func_t) * + EL_NUM_FCNS); + if (el->el_map.func == NULL) + return (-1); + memcpy(el->el_map.func, func__get(), sizeof(el_func_t) * EL_NUM_FCNS); + el->el_map.nfunc = EL_NUM_FCNS; + +#ifdef VIDEFAULT + map_init_vi(el); +#else + map_init_emacs(el); +#endif /* VIDEFAULT */ + return (0); +} + + +/* map_end(): + * Free the space taken by the editor maps + */ +protected void +map_end(EditLine *el) +{ + + el_free((ptr_t) el->el_map.alt); + el->el_map.alt = NULL; + el_free((ptr_t) el->el_map.key); + el->el_map.key = NULL; + el->el_map.emacs = NULL; + el->el_map.vic = NULL; + el->el_map.vii = NULL; + el_free((ptr_t) el->el_map.help); + el->el_map.help = NULL; + el_free((ptr_t) el->el_map.func); + el->el_map.func = NULL; +} + + +/* map_init_nls(): + * Find all the printable keys and bind them to self insert + */ +private void +map_init_nls(EditLine *el) +{ + int i; + + el_action_t *map = el->el_map.key; + + for (i = 0200; i <= 0377; i++) + if (isprint(i)) + map[i] = ED_INSERT; +} + + +/* map_init_meta(): + * Bind all the meta keys to the appropriate ESC- sequence + */ +private void +map_init_meta(EditLine *el) +{ + char buf[3]; + int i; + el_action_t *map = el->el_map.key; + el_action_t *alt = el->el_map.alt; + + for (i = 0; i <= 0377 && map[i] != EM_META_NEXT; i++) + continue; + + if (i > 0377) { + for (i = 0; i <= 0377 && alt[i] != EM_META_NEXT; i++) + continue; + if (i > 0377) { + i = 033; + if (el->el_map.type == MAP_VI) + map = alt; + } else + map = alt; + } + buf[0] = (char) i; + buf[2] = 0; + for (i = 0200; i <= 0377; i++) + switch (map[i]) { + case ED_INSERT: + case ED_UNASSIGNED: + case ED_SEQUENCE_LEAD_IN: + break; + default: + buf[1] = i & 0177; + key_add(el, buf, key_map_cmd(el, (int) map[i]), XK_CMD); + break; + } + map[(int) buf[0]] = ED_SEQUENCE_LEAD_IN; +} + + +/* map_init_vi(): + * Initialize the vi bindings + */ +protected void +map_init_vi(EditLine *el) +{ + int i; + el_action_t *key = el->el_map.key; + el_action_t *alt = el->el_map.alt; + const el_action_t *vii = el->el_map.vii; + const el_action_t *vic = el->el_map.vic; + + el->el_map.type = MAP_VI; + el->el_map.current = el->el_map.key; + + key_reset(el); + + for (i = 0; i < N_KEYS; i++) { + key[i] = vii[i]; + alt[i] = vic[i]; + } + + map_init_meta(el); + map_init_nls(el); + + tty_bind_char(el, 1); + term_bind_arrow(el); +} + + +/* map_init_emacs(): + * Initialize the emacs bindings + */ +protected void +map_init_emacs(EditLine *el) +{ + int i; + char buf[3]; + el_action_t *key = el->el_map.key; + el_action_t *alt = el->el_map.alt; + const el_action_t *emacs = el->el_map.emacs; + + el->el_map.type = MAP_EMACS; + el->el_map.current = el->el_map.key; + key_reset(el); + + for (i = 0; i < N_KEYS; i++) { + key[i] = emacs[i]; + alt[i] = ED_UNASSIGNED; + } + + map_init_meta(el); + map_init_nls(el); + + buf[0] = CONTROL('X'); + buf[1] = CONTROL('X'); + buf[2] = 0; + key_add(el, buf, key_map_cmd(el, EM_EXCHANGE_MARK), XK_CMD); + + tty_bind_char(el, 1); + term_bind_arrow(el); +} + + +/* map_set_editor(): + * Set the editor + */ +protected int +map_set_editor(EditLine *el, char *editor) +{ + + if (strcmp(editor, "emacs") == 0) { + map_init_emacs(el); + return (0); + } + if (strcmp(editor, "vi") == 0) { + map_init_vi(el); + return (0); + } + return (-1); +} + + +/* map_get_editor(): + * Retrieve the editor + */ +protected int +map_get_editor(EditLine *el, const char **editor) +{ + + if (editor == NULL) + return (-1); + switch (el->el_map.type) { + case MAP_EMACS: + *editor = "emacs"; + return (0); + case MAP_VI: + *editor = "vi"; + return (0); + } + return (-1); +} + + +/* map_print_key(): + * Print the function description for 1 key + */ +private void +map_print_key(EditLine *el, el_action_t *map, const char *in) +{ + char outbuf[EL_BUFSIZ]; + el_bindings_t *bp; + + if (in[0] == '\0' || in[1] == '\0') { + (void) key__decode_str(in, outbuf, ""); + for (bp = el->el_map.help; bp->name != NULL; bp++) + if (bp->func == map[(unsigned char) *in]) { + (void) fprintf(el->el_outfile, + "%s\t->\t%s\n", outbuf, bp->name); + return; + } + } else + key_print(el, in); +} + + +/* map_print_some_keys(): + * Print keys from first to last + */ +private void +map_print_some_keys(EditLine *el, el_action_t *map, int first, int last) +{ + el_bindings_t *bp; + char firstbuf[2], lastbuf[2]; + char unparsbuf[EL_BUFSIZ], extrabuf[EL_BUFSIZ]; + + firstbuf[0] = first; + firstbuf[1] = 0; + lastbuf[0] = last; + lastbuf[1] = 0; + if (map[first] == ED_UNASSIGNED) { + if (first == last) + (void) fprintf(el->el_outfile, + "%-15s-> is undefined\n", + key__decode_str(firstbuf, unparsbuf, STRQQ)); + return; + } + for (bp = el->el_map.help; bp->name != NULL; bp++) { + if (bp->func == map[first]) { + if (first == last) { + (void) fprintf(el->el_outfile, "%-15s-> %s\n", + key__decode_str(firstbuf, unparsbuf, STRQQ), + bp->name); + } else { + (void) fprintf(el->el_outfile, + "%-4s to %-7s-> %s\n", + key__decode_str(firstbuf, unparsbuf, STRQQ), + key__decode_str(lastbuf, extrabuf, STRQQ), + bp->name); + } + return; + } + } +#ifdef MAP_DEBUG + if (map == el->el_map.key) { + (void) fprintf(el->el_outfile, + "BUG!!! %s isn't bound to anything.\n", + key__decode_str(firstbuf, unparsbuf, STRQQ)); + (void) fprintf(el->el_outfile, "el->el_map.key[%d] == %d\n", + first, el->el_map.key[first]); + } else { + (void) fprintf(el->el_outfile, + "BUG!!! %s isn't bound to anything.\n", + key__decode_str(firstbuf, unparsbuf, STRQQ)); + (void) fprintf(el->el_outfile, "el->el_map.alt[%d] == %d\n", + first, el->el_map.alt[first]); + } +#endif + EL_ABORT((el->el_errfile, "Error printing keys\n")); +} + + +/* map_print_all_keys(): + * Print the function description for all keys. + */ +private void +map_print_all_keys(EditLine *el) +{ + int prev, i; + + (void) fprintf(el->el_outfile, "Standard key bindings\n"); + prev = 0; + for (i = 0; i < N_KEYS; i++) { + if (el->el_map.key[prev] == el->el_map.key[i]) + continue; + map_print_some_keys(el, el->el_map.key, prev, i - 1); + prev = i; + } + map_print_some_keys(el, el->el_map.key, prev, i - 1); + + (void) fprintf(el->el_outfile, "Alternative key bindings\n"); + prev = 0; + for (i = 0; i < N_KEYS; i++) { + if (el->el_map.alt[prev] == el->el_map.alt[i]) + continue; + map_print_some_keys(el, el->el_map.alt, prev, i - 1); + prev = i; + } + map_print_some_keys(el, el->el_map.alt, prev, i - 1); + + (void) fprintf(el->el_outfile, "Multi-character bindings\n"); + key_print(el, ""); + (void) fprintf(el->el_outfile, "Arrow key bindings\n"); + term_print_arrow(el, ""); +} + + +/* map_bind(): + * Add/remove/change bindings + */ +protected int +map_bind(EditLine *el, int argc, const char **argv) +{ + el_action_t *map; + int ntype, rem; + const char *p; + char inbuf[EL_BUFSIZ]; + char outbuf[EL_BUFSIZ]; + const char *in = NULL; + char *out = NULL; + el_bindings_t *bp; + int cmd; + int key; + + if (argv == NULL) + return (-1); + + map = el->el_map.key; + ntype = XK_CMD; + key = rem = 0; + for (argc = 1; (p = argv[argc]) != NULL; argc++) + if (p[0] == '-') + switch (p[1]) { + case 'a': + map = el->el_map.alt; + break; + + case 's': + ntype = XK_STR; + break; +#ifdef notyet + case 'c': + ntype = XK_EXE; + break; +#endif + case 'k': + key = 1; + break; + + case 'r': + rem = 1; + break; + + case 'v': + map_init_vi(el); + return (0); + + case 'e': + map_init_emacs(el); + return (0); + + case 'l': + for (bp = el->el_map.help; bp->name != NULL; + bp++) + (void) fprintf(el->el_outfile, + "%s\n\t%s\n", + bp->name, bp->description); + return (0); + default: + (void) fprintf(el->el_errfile, + "%s: Invalid switch `%c'.\n", + argv[0], p[1]); + } + else + break; + + if (argv[argc] == NULL) { + map_print_all_keys(el); + return (0); + } + if (key) + in = argv[argc++]; + else if ((in = parse__string(inbuf, argv[argc++])) == NULL) { + (void) fprintf(el->el_errfile, + "%s: Invalid \\ or ^ in instring.\n", + argv[0]); + return (-1); + } + if (rem) { + if (key) { + (void) term_clear_arrow(el, in); + return (-1); + } + if (in[1]) + (void) key_delete(el, in); + else if (map[(unsigned char) *in] == ED_SEQUENCE_LEAD_IN) + (void) key_delete(el, in); + else + map[(unsigned char) *in] = ED_UNASSIGNED; + return (0); + } + if (argv[argc] == NULL) { + if (key) + term_print_arrow(el, in); + else + map_print_key(el, map, in); + return (0); + } +#ifdef notyet + if (argv[argc + 1] != NULL) { + bindkey_usage(); + return (-1); + } +#endif + + switch (ntype) { + case XK_STR: + case XK_EXE: + if ((out = parse__string(outbuf, argv[argc])) == NULL) { + (void) fprintf(el->el_errfile, + "%s: Invalid \\ or ^ in outstring.\n", argv[0]); + return (-1); + } + if (key) + term_set_arrow(el, in, key_map_str(el, out), ntype); + else + key_add(el, in, key_map_str(el, out), ntype); + map[(unsigned char) *in] = ED_SEQUENCE_LEAD_IN; + break; + + case XK_CMD: + if ((cmd = parse_cmd(el, argv[argc])) == -1) { + (void) fprintf(el->el_errfile, + "%s: Invalid command `%s'.\n", argv[0], argv[argc]); + return (-1); + } + if (key) + term_set_arrow(el, in, key_map_str(el, out), ntype); + else { + if (in[1]) { + key_add(el, in, key_map_cmd(el, cmd), ntype); + map[(unsigned char) *in] = ED_SEQUENCE_LEAD_IN; + } else { + key_clear(el, map, in); + map[(unsigned char) *in] = cmd; + } + } + break; + + default: + EL_ABORT((el->el_errfile, "Bad XK_ type\n", ntype)); + break; + } + return (0); +} + + +/* map_addfunc(): + * add a user defined function + */ +protected int +map_addfunc(EditLine *el, const char *name, const char *help, el_func_t func) +{ + void *p; + int nf = el->el_map.nfunc + 2; + + if (name == NULL || help == NULL || func == NULL) + return (-1); + + if ((p = el_realloc(el->el_map.func, nf * sizeof(el_func_t))) == NULL) + return (-1); + el->el_map.func = (el_func_t *) p; + if ((p = el_realloc(el->el_map.help, nf * sizeof(el_bindings_t))) + == NULL) + return (-1); + el->el_map.help = (el_bindings_t *) p; + + nf = el->el_map.nfunc; + el->el_map.func[nf] = func; + + el->el_map.help[nf].name = name; + el->el_map.help[nf].func = nf; + el->el_map.help[nf].description = help; + el->el_map.help[++nf].name = NULL; + el->el_map.nfunc++; + + return (0); +} diff --git a/main/editline/map.h b/main/editline/map.h new file mode 100644 index 000000000..3c9948ccf --- /dev/null +++ b/main/editline/map.h @@ -0,0 +1,79 @@ +/* $NetBSD: map.h,v 1.7 2002/03/18 16:00:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)map.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.map.h: Editor maps + */ +#ifndef _h_el_map +#define _h_el_map + +typedef struct el_bindings_t { /* for the "bind" shell command */ + const char *name; /* function name for bind command */ + int func; /* function numeric value */ + const char *description; /* description of function */ +} el_bindings_t; + + +typedef struct el_map_t { + el_action_t *alt; /* The current alternate key map */ + el_action_t *key; /* The current normal key map */ + el_action_t *current; /* The keymap we are using */ + const el_action_t *emacs; /* The default emacs key map */ + const el_action_t *vic; /* The vi command mode key map */ + const el_action_t *vii; /* The vi insert mode key map */ + int type; /* Emacs or vi */ + el_bindings_t *help; /* The help for the editor functions */ + el_func_t *func; /* List of available functions */ + int nfunc; /* The number of functions/help items */ +} el_map_t; + +#define MAP_EMACS 0 +#define MAP_VI 1 + +protected int map_bind(EditLine *, int, const char **); +protected int map_init(EditLine *); +protected void map_end(EditLine *); +protected void map_init_vi(EditLine *); +protected void map_init_emacs(EditLine *); +protected int map_set_editor(EditLine *, char *); +protected int map_get_editor(EditLine *, const char **); +protected int map_addfunc(EditLine *, const char *, const char *, el_func_t); + +#endif /* _h_el_map */ diff --git a/main/editline/np/fgetln.c b/main/editline/np/fgetln.c new file mode 100644 index 000000000..93da9914d --- /dev/null +++ b/main/editline/np/fgetln.c @@ -0,0 +1,88 @@ +/* $NetBSD: fgetln.c,v 1.1.1.1 1999/04/12 07:43:21 crooksa Exp $ */ + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION OR CONTRIBUTORS + * 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. + */ + +#include "config.h" +#include +#include +#include +#include +#include + +char * +fgetln(fp, len) + FILE *fp; + size_t *len; +{ + static char *buf = NULL; + static size_t bufsiz = 0; + char *ptr; + + + if (buf == NULL) { + bufsiz = BUFSIZ; + if ((buf = malloc(bufsiz)) == NULL) + return NULL; + } + + if (fgets(buf, bufsiz, fp) == NULL) + return NULL; + *len = 0; + + while ((ptr = strchr(&buf[*len], '\n')) == NULL) { + size_t nbufsiz = bufsiz + BUFSIZ; + char *nbuf = realloc(buf, nbufsiz); + + if (nbuf == NULL) { + int oerrno = errno; + free(buf); + errno = oerrno; + buf = NULL; + return NULL; + } else + buf = nbuf; + + *len = bufsiz; + if (fgets(&buf[bufsiz], BUFSIZ, fp) == NULL) + return buf; + + bufsiz = nbufsiz; + } + + *len = (ptr - buf) + 1; + return buf; +} diff --git a/main/editline/np/strlcat.c b/main/editline/np/strlcat.c new file mode 100644 index 000000000..6c9f1e92d --- /dev/null +++ b/main/editline/np/strlcat.c @@ -0,0 +1,75 @@ +/* + * Copyright (c) 1998 Todd C. Miller + * 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``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. + */ + +#include "config.h" +#if defined(LIBC_SCCS) && !defined(lint) +static char *rcsid = "$OpenBSD: strlcat.c,v 1.2 1999/06/17 16:28:58 millert Exp $"; +#endif /* LIBC_SCCS and not lint */ +#ifndef lint +static const char rcsid[] = + "$FreeBSD: src/lib/libc/string/strlcat.c,v 1.2.4.2 2001/07/09 23:30:06 obrien Exp $"; +#endif + +#include +#include + +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(initial dst) + strlen(src); if retval >= siz, + * truncation occurred. + */ +size_t strlcat(dst, src, siz) + char *dst; + const char *src; + size_t siz; +{ + register char *d = dst; + register const char *s = src; + register size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) + return(dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return(dlen + (s - src)); /* count does not include NUL */ +} diff --git a/main/editline/np/strlcpy.c b/main/editline/np/strlcpy.c new file mode 100644 index 000000000..1f154bcf2 --- /dev/null +++ b/main/editline/np/strlcpy.c @@ -0,0 +1,75 @@ +/* $OpenBSD: strlcpy.c,v 1.4 1999/05/01 18:56:41 millert Exp $ */ + +/* + * Copyright (c) 1998 Todd C. Miller + * 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``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. + */ + +#include "config.h" +#if defined(LIBC_SCCS) && !defined(lint) +#if 0 +static char *rcsid = "$OpenBSD: strlcpy.c,v 1.4 1999/05/01 18:56:41 millert Exp $"; +#endif +#endif /* LIBC_SCCS and not lint */ +#ifndef lint +static const char rcsid[] = + "$FreeBSD: src/lib/libc/string/strlcpy.c,v 1.2.4.1 2001/07/09 23:30:06 obrien Exp $"; +#endif + +#include +#include + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t strlcpy(dst, src, siz) + char *dst; + const char *src; + size_t siz; +{ + register char *d = dst; + register const char *s = src; + register size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0 && --n != 0) { + do { + if ((*d++ = *s++) == 0) + break; + } while (--n != 0); + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} diff --git a/main/editline/np/unvis.c b/main/editline/np/unvis.c new file mode 100644 index 000000000..f43c4c749 --- /dev/null +++ b/main/editline/np/unvis.c @@ -0,0 +1,322 @@ +/* $NetBSD: unvis.c,v 1.22 2002/03/23 17:38:27 christos Exp $ */ + +/*- + * Copyright (c) 1989, 1993 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include +#if defined(LIBC_SCCS) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)unvis.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: unvis.c,v 1.22 2002/03/23 17:38:27 christos Exp $"); +#endif +#endif /* LIBC_SCCS and not lint */ + +#define __LIBC12_SOURCE__ + +#include + +#include +#include +#include +#include "np/vis.h" + +#ifdef __weak_alias +__weak_alias(strunvis,_strunvis) +__weak_alias(unvis,_unvis) +#endif + +#ifdef __warn_references +__warn_references(unvis, + "warning: reference to compatibility unvis(); include for correct reference") +#endif + +#if !HAVE_VIS_H +/* + * decode driven by state machine + */ +#define S_GROUND 0 /* haven't seen escape char */ +#define S_START 1 /* start decoding special sequence */ +#define S_META 2 /* metachar started (M) */ +#define S_META1 3 /* metachar more, regular char (-) */ +#define S_CTRL 4 /* control char started (^) */ +#define S_OCTAL2 5 /* octal digit 2 */ +#define S_OCTAL3 6 /* octal digit 3 */ +#define S_HEX1 7 /* hex digit */ +#define S_HEX2 8 /* hex digit 2 */ + +#define isoctal(c) (((u_char)(c)) >= '0' && ((u_char)(c)) <= '7') +#define xtod(c) (isdigit(c) ? (c - '0') : ((tolower(c) - 'a') + 10)) + +int +unvis(cp, c, astate, flag) + char *cp; + int c; + int *astate, flag; +{ + return __unvis13(cp, (int)c, astate, flag); +} + +/* + * unvis - decode characters previously encoded by vis + */ +int +__unvis13(cp, c, astate, flag) + char *cp; + int c; + int *astate, flag; +{ + + _DIAGASSERT(cp != NULL); + _DIAGASSERT(astate != NULL); + + if (flag & UNVIS_END) { + if (*astate == S_OCTAL2 || *astate == S_OCTAL3 + || *astate == S_HEX2) { + *astate = S_GROUND; + return (UNVIS_VALID); + } + return (*astate == S_GROUND ? UNVIS_NOCHAR : UNVIS_SYNBAD); + } + + switch (*astate) { + + case S_GROUND: + *cp = 0; + if (c == '\\') { + *astate = S_START; + return (0); + } + if ((flag & VIS_HTTPSTYLE) && c == '%') { + *astate = S_HEX1; + return (0); + } + *cp = c; + return (UNVIS_VALID); + + case S_START: + switch(c) { + case '\\': + *cp = c; + *astate = S_GROUND; + return (UNVIS_VALID); + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + *cp = (c - '0'); + *astate = S_OCTAL2; + return (0); + case 'M': + *cp = (char)0200; + *astate = S_META; + return (0); + case '^': + *astate = S_CTRL; + return (0); + case 'n': + *cp = '\n'; + *astate = S_GROUND; + return (UNVIS_VALID); + case 'r': + *cp = '\r'; + *astate = S_GROUND; + return (UNVIS_VALID); + case 'b': + *cp = '\b'; + *astate = S_GROUND; + return (UNVIS_VALID); + case 'a': + *cp = '\007'; + *astate = S_GROUND; + return (UNVIS_VALID); + case 'v': + *cp = '\v'; + *astate = S_GROUND; + return (UNVIS_VALID); + case 't': + *cp = '\t'; + *astate = S_GROUND; + return (UNVIS_VALID); + case 'f': + *cp = '\f'; + *astate = S_GROUND; + return (UNVIS_VALID); + case 's': + *cp = ' '; + *astate = S_GROUND; + return (UNVIS_VALID); + case 'E': + *cp = '\033'; + *astate = S_GROUND; + return (UNVIS_VALID); + case '\n': + /* + * hidden newline + */ + *astate = S_GROUND; + return (UNVIS_NOCHAR); + case '$': + /* + * hidden marker + */ + *astate = S_GROUND; + return (UNVIS_NOCHAR); + } + *astate = S_GROUND; + return (UNVIS_SYNBAD); + + case S_META: + if (c == '-') + *astate = S_META1; + else if (c == '^') + *astate = S_CTRL; + else { + *astate = S_GROUND; + return (UNVIS_SYNBAD); + } + return (0); + + case S_META1: + *astate = S_GROUND; + *cp |= c; + return (UNVIS_VALID); + + case S_CTRL: + if (c == '?') + *cp |= 0177; + else + *cp |= c & 037; + *astate = S_GROUND; + return (UNVIS_VALID); + + case S_OCTAL2: /* second possible octal digit */ + if (isoctal(c)) { + /* + * yes - and maybe a third + */ + *cp = (*cp << 3) + (c - '0'); + *astate = S_OCTAL3; + return (0); + } + /* + * no - done with current sequence, push back passed char + */ + *astate = S_GROUND; + return (UNVIS_VALIDPUSH); + + case S_OCTAL3: /* third possible octal digit */ + *astate = S_GROUND; + if (isoctal(c)) { + *cp = (*cp << 3) + (c - '0'); + return (UNVIS_VALID); + } + /* + * we were done, push back passed char + */ + return (UNVIS_VALIDPUSH); + case S_HEX1: + if (isxdigit(c)) { + *cp = xtod(c); + *astate = S_HEX2; + return (0); + } + /* + * no - done with current sequence, push back passed char + */ + *astate = S_GROUND; + return (UNVIS_VALIDPUSH); + case S_HEX2: + *astate = S_GROUND; + if (isxdigit(c)) { + *cp = xtod(c) | (*cp << 4); + return (UNVIS_VALID); + } + return (UNVIS_VALIDPUSH); + default: + /* + * decoder in unknown state - (probably uninitialized) + */ + *astate = S_GROUND; + return (UNVIS_SYNBAD); + } +} + +/* + * strunvis - decode src into dst + * + * Number of chars decoded into dst is returned, -1 on error. + * Dst is null terminated. + */ + +int +strunvisx(dst, src, flag) + char *dst; + const char *src; + int flag; +{ + char c; + char *start = dst; + int state = 0; + + _DIAGASSERT(src != NULL); + _DIAGASSERT(dst != NULL); + + while ((c = *src++) != '\0') { + again: + switch (__unvis13(dst, c, &state, flag)) { + case UNVIS_VALID: + dst++; + break; + case UNVIS_VALIDPUSH: + dst++; + goto again; + case 0: + case UNVIS_NOCHAR: + break; + default: + return (-1); + } + } + if (__unvis13(dst, c, &state, UNVIS_END) == UNVIS_VALID) + dst++; + *dst = '\0'; + return (dst - start); +} + +int +strunvis(dst, src) + char *dst; + const char *src; +{ + return strunvisx(dst, src, 0); +} +#endif diff --git a/main/editline/np/vis.c b/main/editline/np/vis.c new file mode 100644 index 000000000..83265f46e --- /dev/null +++ b/main/editline/np/vis.c @@ -0,0 +1,348 @@ +/* $NetBSD: vis.c,v 1.22 2002/03/23 17:38:27 christos Exp $ */ + +/*- + * Copyright (c) 1999 The NetBSD Foundation, Inc. + * Copyright (c) 1989, 1993 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include +#if defined(LIBC_SCCS) && !defined(lint) +__RCSID("$NetBSD: vis.c,v 1.22 2002/03/23 17:38:27 christos Exp $"); +#endif /* LIBC_SCCS and not lint */ + +#include + +#include +#include "np/vis.h" +#include + +#ifdef __weak_alias +__weak_alias(strsvis,_strsvis) +__weak_alias(strsvisx,_strsvisx) +__weak_alias(strvis,_strvis) +__weak_alias(strvisx,_strvisx) +__weak_alias(svis,_svis) +__weak_alias(vis,_vis) +#endif + +#if !HAVE_VIS_H +#include +#include +#include +#include + +#undef BELL +#if defined(__STDC__) +#define BELL '\a' +#else +#define BELL '\007' +#endif + +#ifdef SOLARIS +#include +typedef unsigned int u_int32_t; +#endif + +#define isoctal(c) (((u_char)(c)) >= '0' && ((u_char)(c)) <= '7') +#define iswhite(c) (c == ' ' || c == '\t' || c == '\n') +#define issafe(c) (c == '\b' || c == BELL || c == '\r') +#define xtoa(c) "0123456789abcdef"[c] + +#define MAXEXTRAS 5 + + +#define MAKEEXTRALIST(flag, extra, orig) \ +do { \ + const char *o = orig; \ + char *e; \ + while (*o++) \ + continue; \ + extra = alloca((size_t)((o - orig) + MAXEXTRAS)); \ + for (o = orig, e = extra; (*e++ = *o++) != '\0';) \ + continue; \ + e--; \ + if (flag & VIS_SP) *e++ = ' '; \ + if (flag & VIS_TAB) *e++ = '\t'; \ + if (flag & VIS_NL) *e++ = '\n'; \ + if ((flag & VIS_NOSLASH) == 0) *e++ = '\\'; \ + *e = '\0'; \ +} while (/*CONSTCOND*/0) + + +/* + * This is HVIS, the macro of vis used to HTTP style (RFC 1808) + */ +#define HVIS(dst, c, flag, nextc, extra) \ +do \ + if (!isascii(c) || !isalnum(c) || strchr("$-_.+!*'(),", c) != NULL) { \ + *dst++ = '%'; \ + *dst++ = xtoa(((unsigned int)c >> 4) & 0xf); \ + *dst++ = xtoa((unsigned int)c & 0xf); \ + } else { \ + SVIS(dst, c, flag, nextc, extra); \ + } \ +while (/*CONSTCOND*/0) + +/* + * This is SVIS, the central macro of vis. + * dst: Pointer to the destination buffer + * c: Character to encode + * flag: Flag word + * nextc: The character following 'c' + * extra: Pointer to the list of extra characters to be + * backslash-protected. + */ +#define SVIS(dst, c, flag, nextc, extra) \ +do { \ + int isextra, isc; \ + isextra = strchr(extra, c) != NULL; \ + if (!isextra && isascii(c) && (isgraph(c) || iswhite(c) || \ + ((flag & VIS_SAFE) && issafe(c)))) { \ + *dst++ = c; \ + break; \ + } \ + isc = 0; \ + if (flag & VIS_CSTYLE) { \ + switch (c) { \ + case '\n': \ + isc = 1; *dst++ = '\\'; *dst++ = 'n'; \ + break; \ + case '\r': \ + isc = 1; *dst++ = '\\'; *dst++ = 'r'; \ + break; \ + case '\b': \ + isc = 1; *dst++ = '\\'; *dst++ = 'b'; \ + break; \ + case BELL: \ + isc = 1; *dst++ = '\\'; *dst++ = 'a'; \ + break; \ + case '\v': \ + isc = 1; *dst++ = '\\'; *dst++ = 'v'; \ + break; \ + case '\t': \ + isc = 1; *dst++ = '\\'; *dst++ = 't'; \ + break; \ + case '\f': \ + isc = 1; *dst++ = '\\'; *dst++ = 'f'; \ + break; \ + case ' ': \ + isc = 1; *dst++ = '\\'; *dst++ = 's'; \ + break; \ + case '\0': \ + isc = 1; *dst++ = '\\'; *dst++ = '0'; \ + if (isoctal(nextc)) { \ + *dst++ = '0'; \ + *dst++ = '0'; \ + } \ + } \ + } \ + if (isc) break; \ + if (isextra || ((c & 0177) == ' ') || (flag & VIS_OCTAL)) { \ + *dst++ = '\\'; \ + *dst++ = (u_char)(((u_int32_t)(u_char)c >> 6) & 03) + '0'; \ + *dst++ = (u_char)(((u_int32_t)(u_char)c >> 3) & 07) + '0'; \ + *dst++ = (c & 07) + '0'; \ + } else { \ + if ((flag & VIS_NOSLASH) == 0) *dst++ = '\\'; \ + if (c & 0200) { \ + c &= 0177; *dst++ = 'M'; \ + } \ + if (iscntrl(c)) { \ + *dst++ = '^'; \ + if (c == 0177) \ + *dst++ = '?'; \ + else \ + *dst++ = c + '@'; \ + } else { \ + *dst++ = '-'; *dst++ = c; \ + } \ + } \ +} while (/*CONSTCOND*/0) + + +/* + * svis - visually encode characters, also encoding the characters + * pointed to by `extra' + */ +char * +svis(dst, c, flag, nextc, extra) + char *dst; + int c, flag, nextc; + const char *extra; +{ + char *nextra; + _DIAGASSERT(dst != NULL); + _DIAGASSERT(extra != NULL); + MAKEEXTRALIST(flag, nextra, extra); + if (flag & VIS_HTTPSTYLE) + HVIS(dst, c, flag, nextc, nextra); + else + SVIS(dst, c, flag, nextc, nextra); + *dst = '\0'; + return(dst); +} + + +/* + * strsvis, strsvisx - visually encode characters from src into dst + * + * Extra is a pointer to a \0-terminated list of characters to + * be encoded, too. These functions are useful e. g. to + * encode strings in such a way so that they are not interpreted + * by a shell. + * + * Dst must be 4 times the size of src to account for possible + * expansion. The length of dst, not including the trailing NULL, + * is returned. + * + * Strsvisx encodes exactly len bytes from src into dst. + * This is useful for encoding a block of data. + */ +int +strsvis(dst, src, flag, extra) + char *dst; + const char *src; + int flag; + const char *extra; +{ + char c; + char *start; + char *nextra; + + _DIAGASSERT(dst != NULL); + _DIAGASSERT(src != NULL); + _DIAGASSERT(extra != NULL); + MAKEEXTRALIST(flag, nextra, extra); + if (flag & VIS_HTTPSTYLE) { + for (start = dst; (c = *src++) != '\0'; /* empty */) + HVIS(dst, c, flag, *src, nextra); + } else { + for (start = dst; (c = *src++) != '\0'; /* empty */) + SVIS(dst, c, flag, *src, nextra); + } + *dst = '\0'; + return (dst - start); +} + + +int +strsvisx(dst, src, len, flag, extra) + char *dst; + const char *src; + size_t len; + int flag; + const char *extra; +{ + char c; + char *start; + char *nextra; + + _DIAGASSERT(dst != NULL); + _DIAGASSERT(src != NULL); + _DIAGASSERT(extra != NULL); + MAKEEXTRALIST(flag, nextra, extra); + + if (flag & VIS_HTTPSTYLE) { + for (start = dst; len > 0; len--) { + c = *src++; + HVIS(dst, c, flag, len ? *src : '\0', nextra); + } + } else { + for (start = dst; len > 0; len--) { + c = *src++; + SVIS(dst, c, flag, len ? *src : '\0', nextra); + } + } + *dst = '\0'; + return (dst - start); +} + + +/* + * vis - visually encode characters + */ +char * +vis(dst, c, flag, nextc) + char *dst; + int c, flag, nextc; + +{ + char *extra; + + _DIAGASSERT(dst != NULL); + + MAKEEXTRALIST(flag, extra, ""); + if (flag & VIS_HTTPSTYLE) + HVIS(dst, c, flag, nextc, extra); + else + SVIS(dst, c, flag, nextc, extra); + *dst = '\0'; + return (dst); +} + + +/* + * strvis, strvisx - visually encode characters from src into dst + * + * Dst must be 4 times the size of src to account for possible + * expansion. The length of dst, not including the trailing NULL, + * is returned. + * + * Strvisx encodes exactly len bytes from src into dst. + * This is useful for encoding a block of data. + */ +int +strvis(dst, src, flag) + char *dst; + const char *src; + int flag; +{ + char *extra; + + MAKEEXTRALIST(flag, extra, ""); + return (strsvis(dst, src, flag, extra)); +} + + +int +strvisx(dst, src, len, flag) + char *dst; + const char *src; + size_t len; + int flag; +{ + char *extra; + + MAKEEXTRALIST(flag, extra, ""); + return (strsvisx(dst, src, len, flag, extra)); +} +#endif diff --git a/main/editline/np/vis.h b/main/editline/np/vis.h new file mode 100644 index 000000000..0739c1dfa --- /dev/null +++ b/main/editline/np/vis.h @@ -0,0 +1,96 @@ +/* $NetBSD: vis.h,v 1.12 2002/03/23 17:39:05 christos Exp $ */ + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)vis.h 8.1 (Berkeley) 6/2/93 + */ + +#ifndef _VIS_H_ +#define _VIS_H_ + +/* + * to select alternate encoding format + */ +#define VIS_OCTAL 0x01 /* use octal \ddd format */ +#define VIS_CSTYLE 0x02 /* use \[nrft0..] where appropiate */ + +/* + * to alter set of characters encoded (default is to encode all + * non-graphic except space, tab, and newline). + */ +#define VIS_SP 0x04 /* also encode space */ +#define VIS_TAB 0x08 /* also encode tab */ +#define VIS_NL 0x10 /* also encode newline */ +#define VIS_WHITE (VIS_SP | VIS_TAB | VIS_NL) +#define VIS_SAFE 0x20 /* only encode "unsafe" characters */ + +/* + * other + */ +#define VIS_NOSLASH 0x40 /* inhibit printing '\' */ +#define VIS_HTTPSTYLE 0x80 /* http-style escape % HEX HEX */ + +/* + * unvis return codes + */ +#define UNVIS_VALID 1 /* character valid */ +#define UNVIS_VALIDPUSH 2 /* character valid, push back passed char */ +#define UNVIS_NOCHAR 3 /* valid sequence, no character produced */ +#define UNVIS_SYNBAD -1 /* unrecognized escape sequence */ +#define UNVIS_ERROR -2 /* decoder in unknown state (unrecoverable) */ + +/* + * unvis flags + */ +#define UNVIS_END 1 /* no more characters */ + +#include + +__BEGIN_DECLS +char *vis __P((char *, int, int, int)); +char *svis __P((char *, int, int, int, const char *)); +int strvis __P((char *, const char *, int)); +int strsvis __P((char *, const char *, int, const char *)); +int strvisx __P((char *, const char *, size_t, int)); +int strsvisx __P((char *, const char *, size_t, int, const char *)); +int strunvis __P((char *, const char *)); +int strunvisx __P((char *, const char *, int)); +#ifdef __LIBC12_SOURCE__ +int unvis __P((char *, int, int *, int)); +int __unvis13 __P((char *, int, int *, int)); +#else +int unvis __P((char *, int, int *, int)) __RENAME(__unvis13); +#endif +__END_DECLS + +#endif /* !_VIS_H_ */ diff --git a/main/editline/parse.c b/main/editline/parse.c new file mode 100644 index 000000000..0a34f0b15 --- /dev/null +++ b/main/editline/parse.c @@ -0,0 +1,259 @@ +/* $NetBSD: parse.c,v 1.15 2002/03/18 16:00:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)parse.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: parse.c,v 1.15 2002/03/18 16:00:56 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * parse.c: parse an editline extended command + * + * commands are: + * + * bind + * echotc + * edit + * gettc + * history + * settc + * setty + */ +#include "el.h" +#include "tokenizer.h" +#include + +private const struct { + const char *name; + int (*func)(EditLine *, int, const char **); +} cmds[] = { + { "bind", map_bind }, + { "echotc", term_echotc }, + { "edit", el_editmode }, + { "history", hist_list }, + { "telltc", term_telltc }, + { "settc", term_settc }, + { "setty", tty_stty }, + { NULL, NULL } +}; + + +/* parse_line(): + * Parse a line and dispatch it + */ +protected int +parse_line(EditLine *el, const char *line) +{ + const char **argv; + int argc; + Tokenizer *tok; + + tok = tok_init(NULL); + tok_line(tok, line, &argc, &argv); + argc = el_parse(el, argc, argv); + tok_end(tok); + return (argc); +} + + +/* el_parse(): + * Command dispatcher + */ +public int +el_parse(EditLine *el, int argc, const char *argv[]) +{ + const char *ptr; + int i; + + if (argc < 1) + return (-1); + ptr = strchr(argv[0], ':'); + if (ptr != NULL) { + char *tprog; + size_t l; + + if (ptr == argv[0]) + return (0); + l = ptr - argv[0] - 1; + tprog = (char *) el_malloc(l + 1); + if (tprog == NULL) + return (0); + (void) strncpy(tprog, argv[0], l); + tprog[l] = '\0'; + ptr++; + l = el_match(el->el_prog, tprog); + el_free(tprog); + if (!l) + return (0); + } else + ptr = argv[0]; + + for (i = 0; cmds[i].name != NULL; i++) + if (strcmp(cmds[i].name, ptr) == 0) { + i = (*cmds[i].func) (el, argc, argv); + return (-i); + } + return (-1); +} + + +/* parse__escape(): + * Parse a string of the form ^ \ \ and return + * the appropriate character or -1 if the escape is not valid + */ +protected int +parse__escape(const char **const ptr) +{ + const char *p; + int c; + + p = *ptr; + + if (p[1] == 0) + return (-1); + + if (*p == '\\') { + p++; + switch (*p) { + case 'a': + c = '\007'; /* Bell */ + break; + case 'b': + c = '\010'; /* Backspace */ + break; + case 't': + c = '\011'; /* Horizontal Tab */ + break; + case 'n': + c = '\012'; /* New Line */ + break; + case 'v': + c = '\013'; /* Vertical Tab */ + break; + case 'f': + c = '\014'; /* Form Feed */ + break; + case 'r': + c = '\015'; /* Carriage Return */ + break; + case 'e': + c = '\033'; /* Escape */ + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + { + int cnt, ch; + + for (cnt = 0, c = 0; cnt < 3; cnt++) { + ch = *p++; + if (ch < '0' || ch > '7') { + p--; + break; + } + c = (c << 3) | (ch - '0'); + } + if ((c & 0xffffff00) != 0) + return (-1); + --p; + break; + } + default: + c = *p; + break; + } + } else if (*p == '^' && isalpha((unsigned char) p[1])) { + p++; + c = (*p == '?') ? '\177' : (*p & 0237); + } else + c = *p; + *ptr = ++p; + return (c); +} +/* parse__string(): + * Parse the escapes from in and put the raw string out + */ +protected char * +parse__string(char *out, const char *in) +{ + char *rv = out; + int n; + + for (;;) + switch (*in) { + case '\0': + *out = '\0'; + return (rv); + + case '\\': + case '^': + if ((n = parse__escape(&in)) == -1) + return (NULL); + *out++ = n; + break; + + default: + *out++ = *in++; + break; + } +} + + +/* parse_cmd(): + * Return the command number for the command string given + * or -1 if one is not found + */ +protected int +parse_cmd(EditLine *el, const char *cmd) +{ + el_bindings_t *b; + + for (b = el->el_map.help; b->name != NULL; b++) + if (strcmp(b->name, cmd) == 0) + return (b->func); + return (-1); +} diff --git a/main/editline/parse.h b/main/editline/parse.h new file mode 100644 index 000000000..4aaef2f83 --- /dev/null +++ b/main/editline/parse.h @@ -0,0 +1,52 @@ +/* $NetBSD: parse.h,v 1.4 2000/09/04 22:06:31 lukem Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)parse.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.parse.h: Parser functions + */ +#ifndef _h_el_parse +#define _h_el_parse + +protected int parse_line(EditLine *, const char *); +protected int parse__escape(const char ** const); +protected char *parse__string(char *, const char *); +protected int parse_cmd(EditLine *, const char *); + +#endif /* _h_el_parse */ diff --git a/main/editline/prompt.c b/main/editline/prompt.c new file mode 100644 index 000000000..5c069e17a --- /dev/null +++ b/main/editline/prompt.c @@ -0,0 +1,174 @@ +/* $NetBSD: prompt.c,v 1.9 2002/03/18 16:00:56 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)prompt.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: prompt.c,v 1.9 2002/03/18 16:00:56 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * prompt.c: Prompt printing functions + */ +#include +#include "el.h" + +private char *prompt_default(EditLine *); +private char *prompt_default_r(EditLine *); + +/* prompt_default(): + * Just a default prompt, in case the user did not provide one + */ +private char * +/*ARGSUSED*/ +prompt_default(EditLine *el) +{ + static char a[3] = {'?', ' ', '\0'}; + + return (a); +} + + +/* prompt_default_r(): + * Just a default rprompt, in case the user did not provide one + */ +private char * +/*ARGSUSED*/ +prompt_default_r(EditLine *el) +{ + static char a[1] = {'\0'}; + + return (a); +} + + +/* prompt_print(): + * Print the prompt and update the prompt position. + * We use an array of integers in case we want to pass + * literal escape sequences in the prompt and we want a + * bit to flag them + */ +protected void +prompt_print(EditLine *el, int op) +{ + el_prompt_t *elp; + char *p; + + if (op == EL_PROMPT) + elp = &el->el_prompt; + else + elp = &el->el_rprompt; + p = (elp->p_func) (el); + while (*p) + re_putc(el, *p++, 1); + + elp->p_pos.v = el->el_refresh.r_cursor.v; + elp->p_pos.h = el->el_refresh.r_cursor.h; +} + + +/* prompt_init(): + * Initialize the prompt stuff + */ +protected int +prompt_init(EditLine *el) +{ + + el->el_prompt.p_func = prompt_default; + el->el_prompt.p_pos.v = 0; + el->el_prompt.p_pos.h = 0; + el->el_rprompt.p_func = prompt_default_r; + el->el_rprompt.p_pos.v = 0; + el->el_rprompt.p_pos.h = 0; + return (0); +} + + +/* prompt_end(): + * Clean up the prompt stuff + */ +protected void +/*ARGSUSED*/ +prompt_end(EditLine *el) +{ +} + + +/* prompt_set(): + * Install a prompt printing function + */ +protected int +prompt_set(EditLine *el, el_pfunc_t prf, int op) +{ + el_prompt_t *p; + + if (op == EL_PROMPT) + p = &el->el_prompt; + else + p = &el->el_rprompt; + if (prf == NULL) { + if (op == EL_PROMPT) + p->p_func = prompt_default; + else + p->p_func = prompt_default_r; + } else + p->p_func = prf; + p->p_pos.v = 0; + p->p_pos.h = 0; + return (0); +} + + +/* prompt_get(): + * Retrieve the prompt printing function + */ +protected int +prompt_get(EditLine *el, el_pfunc_t *prf, int op) +{ + + if (prf == NULL) + return (-1); + if (op == EL_PROMPT) + *prf = el->el_prompt.p_func; + else + *prf = el->el_rprompt.p_func; + return (0); +} diff --git a/main/editline/prompt.h b/main/editline/prompt.h new file mode 100644 index 000000000..08810e22a --- /dev/null +++ b/main/editline/prompt.h @@ -0,0 +1,62 @@ +/* $NetBSD: prompt.h,v 1.5 2000/09/04 22:06:31 lukem Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)prompt.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.prompt.h: Prompt printing stuff + */ +#ifndef _h_el_prompt +#define _h_el_prompt + +#include "histedit.h" + +typedef char * (*el_pfunc_t)(EditLine*); + +typedef struct el_prompt_t { + el_pfunc_t p_func; /* Function to return the prompt */ + coord_t p_pos; /* position in the line after prompt */ +} el_prompt_t; + +protected void prompt_print(EditLine *, int); +protected int prompt_set(EditLine *, el_pfunc_t, int); +protected int prompt_get(EditLine *, el_pfunc_t *, int); +protected int prompt_init(EditLine *); +protected void prompt_end(EditLine *); + +#endif /* _h_el_prompt */ diff --git a/main/editline/read.c b/main/editline/read.c new file mode 100644 index 000000000..ccd0a06e5 --- /dev/null +++ b/main/editline/read.c @@ -0,0 +1,555 @@ +/* $NetBSD: read.c,v 1.21 2002/03/18 16:00:57 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)read.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: read.c,v 1.21 2002/03/18 16:00:57 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * read.c: Clean this junk up! This is horrible code. + * Terminal read functions + */ +#include +#include +#include +#include "el.h" + +#define OKCMD -1 + +private int read__fixio(int, int); +private int read_preread(EditLine *); +private int read_char(EditLine *, char *); +private int read_getcmd(EditLine *, el_action_t *, char *); + +/* read_init(): + * Initialize the read stuff + */ +protected int +read_init(EditLine *el) +{ + /* builtin read_char */ + el->el_read.read_char = read_char; + return 0; +} + + +/* el_read_setfn(): + * Set the read char function to the one provided. + * If it is set to EL_BUILTIN_GETCFN, then reset to the builtin one. + */ +protected int +el_read_setfn(EditLine *el, el_rfunc_t rc) +{ + el->el_read.read_char = (rc == EL_BUILTIN_GETCFN) ? read_char : rc; + return 0; +} + + +/* el_read_getfn(): + * return the current read char function, or EL_BUILTIN_GETCFN + * if it is the default one + */ +protected el_rfunc_t +el_read_getfn(EditLine *el) +{ + return (el->el_read.read_char == read_char) ? + EL_BUILTIN_GETCFN : el->el_read.read_char; +} + + +#ifdef DEBUG_EDIT +private void +read_debug(EditLine *el) +{ + + if (el->el_line.cursor > el->el_line.lastchar) + (void) fprintf(el->el_errfile, "cursor > lastchar\r\n"); + if (el->el_line.cursor < el->el_line.buffer) + (void) fprintf(el->el_errfile, "cursor < buffer\r\n"); + if (el->el_line.cursor > el->el_line.limit) + (void) fprintf(el->el_errfile, "cursor > limit\r\n"); + if (el->el_line.lastchar > el->el_line.limit) + (void) fprintf(el->el_errfile, "lastchar > limit\r\n"); + if (el->el_line.limit != &el->el_line.buffer[EL_BUFSIZ - 2]) + (void) fprintf(el->el_errfile, "limit != &buffer[EL_BUFSIZ-2]\r\n"); +} +#endif /* DEBUG_EDIT */ + + +/* read__fixio(): + * Try to recover from a read error + */ +/* ARGSUSED */ +private int +read__fixio(int fd, int e) +{ + + switch (e) { + case -1: /* Make sure that the code is reachable */ + +#ifdef EWOULDBLOCK + case EWOULDBLOCK: +#ifndef TRY_AGAIN +#define TRY_AGAIN +#endif +#endif /* EWOULDBLOCK */ + +#if defined(POSIX) && defined(EAGAIN) +#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN + case EAGAIN: +#ifndef TRY_AGAIN +#define TRY_AGAIN +#endif +#endif /* EWOULDBLOCK && EWOULDBLOCK != EAGAIN */ +#endif /* POSIX && EAGAIN */ + + e = 0; +#ifdef TRY_AGAIN +#if defined(F_SETFL) && defined(O_NDELAY) + if ((e = fcntl(fd, F_GETFL, 0)) == -1) + return (-1); + + if (fcntl(fd, F_SETFL, e & ~O_NDELAY) == -1) + return (-1); + else + e = 1; +#endif /* F_SETFL && O_NDELAY */ + +#ifdef FIONBIO + { + int zero = 0; + + if (ioctl(fd, FIONBIO, (ioctl_t) & zero) == -1) + return (-1); + else + e = 1; + } +#endif /* FIONBIO */ + +#endif /* TRY_AGAIN */ + return (e ? 0 : -1); + + case EINTR: + return (0); + + default: + return (-1); + } +} + + +/* read_preread(): + * Try to read the stuff in the input queue; + */ +private int +read_preread(EditLine *el) +{ + int chrs = 0; + + if (el->el_chared.c_macro.nline) { + el_free((ptr_t) el->el_chared.c_macro.nline); + el->el_chared.c_macro.nline = NULL; + } + if (el->el_tty.t_mode == ED_IO) + return (0); + +#ifdef FIONREAD + (void) ioctl(el->el_infd, FIONREAD, (ioctl_t) & chrs); + if (chrs > 0) { + char buf[EL_BUFSIZ]; + + chrs = read(el->el_infd, buf, + (size_t) MIN(chrs, EL_BUFSIZ - 1)); + if (chrs > 0) { + buf[chrs] = '\0'; + el->el_chared.c_macro.nline = strdup(buf); + el_push(el, el->el_chared.c_macro.nline); + } + } +#endif /* FIONREAD */ + + return (chrs > 0); +} + + +/* el_push(): + * Push a macro + */ +public void +el_push(EditLine *el, char *str) +{ + c_macro_t *ma = &el->el_chared.c_macro; + + if (str != NULL && ma->level + 1 < EL_MAXMACRO) { + ma->level++; + ma->macro[ma->level] = str; + } else { + term_beep(el); + term__flush(); + } +} + + +/* read_getcmd(): + * Return next command from the input stream. + */ +private int +read_getcmd(EditLine *el, el_action_t *cmdnum, char *ch) +{ + el_action_t cmd = ED_UNASSIGNED; + int num; + + while (cmd == ED_UNASSIGNED || cmd == ED_SEQUENCE_LEAD_IN) { + if ((num = el_getc(el, ch)) != 1) /* if EOF or error */ + return (num); + +#ifdef KANJI + if ((*ch & 0200)) { + el->el_state.metanext = 0; + cmd = CcViMap[' ']; + break; + } else +#endif /* KANJI */ + + if (el->el_state.metanext) { + el->el_state.metanext = 0; + *ch |= 0200; + } + cmd = el->el_map.current[(unsigned char) *ch]; + if (cmd == ED_SEQUENCE_LEAD_IN) { + key_value_t val; + switch (key_get(el, ch, &val)) { + case XK_CMD: + cmd = val.cmd; + break; + case XK_STR: + el_push(el, val.str); + break; +#ifdef notyet + case XK_EXE: + /* XXX: In the future to run a user function */ + RunCommand(val.str); + break; +#endif + default: + EL_ABORT((el->el_errfile, "Bad XK_ type \n")); + break; + } + } + if (el->el_map.alt == NULL) + el->el_map.current = el->el_map.key; + } + *cmdnum = cmd; + return (OKCMD); +} + + +/* read_char(): + * Read a character from the tty. + */ +private int +read_char(EditLine *el, char *cp) +{ + int num_read; + int tried = 0; + + while ((num_read = read(el->el_infd, cp, 1)) == -1) + if (!tried && read__fixio(el->el_infd, errno) == 0) + tried = 1; + else { + *cp = '\0'; + return (-1); + } + + return (num_read); +} + + +/* el_getc(): + * Read a character + */ +public int +el_getc(EditLine *el, char *cp) +{ + int num_read; + c_macro_t *ma = &el->el_chared.c_macro; + + term__flush(); + for (;;) { + if (ma->level < 0) { + if (!read_preread(el)) + break; + } + if (ma->level < 0) + break; + + if (*ma->macro[ma->level] == 0) { + ma->level--; + continue; + } + *cp = *ma->macro[ma->level]++ & 0377; + if (*ma->macro[ma->level] == 0) { /* Needed for QuoteMode + * On */ + ma->level--; + } + return (1); + } + +#ifdef DEBUG_READ + (void) fprintf(el->el_errfile, "Turning raw mode on\n"); +#endif /* DEBUG_READ */ + if (tty_rawmode(el) < 0)/* make sure the tty is set up correctly */ + return (0); + +#ifdef DEBUG_READ + (void) fprintf(el->el_errfile, "Reading a character\n"); +#endif /* DEBUG_READ */ + num_read = (*el->el_read.read_char)(el, cp); +#ifdef DEBUG_READ + (void) fprintf(el->el_errfile, "Got it %c\n", *cp); +#endif /* DEBUG_READ */ + return (num_read); +} + + +public const char * +el_gets(EditLine *el, int *nread) +{ + int retval; + el_action_t cmdnum = 0; + int num; /* how many chars we have read at NL */ + char ch; +#ifdef FIONREAD + c_macro_t *ma = &el->el_chared.c_macro; +#endif /* FIONREAD */ + + if (el->el_flags & HANDLE_SIGNALS) + sig_set(el); + + if (el->el_flags & NO_TTY) { + char *cp = el->el_line.buffer; + size_t idx; + + while ((*el->el_read.read_char)(el, cp) == 1) { + /* make sure there is space for next character */ + if (cp + 1 >= el->el_line.limit) { + idx = (cp - el->el_line.buffer); + if (!ch_enlargebufs(el, 2)) + break; + cp = &el->el_line.buffer[idx]; + } + cp++; + if (cp[-1] == '\r' || cp[-1] == '\n') + break; + } + + el->el_line.cursor = el->el_line.lastchar = cp; + *cp = '\0'; + if (nread) + *nread = el->el_line.cursor - el->el_line.buffer; + return (el->el_line.buffer); + } + re_clear_display(el); /* reset the display stuff */ + ch_reset(el); + +#ifdef FIONREAD + if (el->el_tty.t_mode == EX_IO && ma->level < 0) { + long chrs = 0; + + (void) ioctl(el->el_infd, FIONREAD, (ioctl_t) & chrs); + if (chrs == 0) { + if (tty_rawmode(el) < 0) { + if (nread) + *nread = 0; + return (NULL); + } + } + } +#endif /* FIONREAD */ + + re_refresh(el); /* print the prompt */ + + if (el->el_flags & EDIT_DISABLED) { + char *cp = el->el_line.buffer; + size_t idx; + + term__flush(); + + while ((*el->el_read.read_char)(el, cp) == 1) { + /* make sure there is space next character */ + if (cp + 1 >= el->el_line.limit) { + idx = (cp - el->el_line.buffer); + if (!ch_enlargebufs(el, 2)) + break; + cp = &el->el_line.buffer[idx]; + } + cp++; + if (cp[-1] == '\r' || cp[-1] == '\n') + break; + } + + el->el_line.cursor = el->el_line.lastchar = cp; + *cp = '\0'; + if (nread) + *nread = el->el_line.cursor - el->el_line.buffer; + return (el->el_line.buffer); + } + for (num = OKCMD; num == OKCMD;) { /* while still editing this + * line */ +#ifdef DEBUG_EDIT + read_debug(el); +#endif /* DEBUG_EDIT */ + /* if EOF or error */ + if ((num = read_getcmd(el, &cmdnum, &ch)) != OKCMD) { +#ifdef DEBUG_READ + (void) fprintf(el->el_errfile, + "Returning from el_gets %d\n", num); +#endif /* DEBUG_READ */ + break; + } + if ((int) cmdnum >= el->el_map.nfunc) { /* BUG CHECK command */ +#ifdef DEBUG_EDIT + (void) fprintf(el->el_errfile, + "ERROR: illegal command from key 0%o\r\n", ch); +#endif /* DEBUG_EDIT */ + continue; /* try again */ + } + /* now do the real command */ +#ifdef DEBUG_READ + { + el_bindings_t *b; + for (b = el->el_map.help; b->name; b++) + if (b->func == cmdnum) + break; + if (b->name) + (void) fprintf(el->el_errfile, + "Executing %s\n", b->name); + else + (void) fprintf(el->el_errfile, + "Error command = %d\n", cmdnum); + } +#endif /* DEBUG_READ */ + retval = (*el->el_map.func[cmdnum]) (el, ch); + + /* save the last command here */ + el->el_state.lastcmd = cmdnum; + + /* use any return value */ + switch (retval) { + case CC_CURSOR: + el->el_state.argument = 1; + el->el_state.doingarg = 0; + re_refresh_cursor(el); + break; + + case CC_REDISPLAY: + re_clear_lines(el); + re_clear_display(el); + /* FALLTHROUGH */ + + case CC_REFRESH: + el->el_state.argument = 1; + el->el_state.doingarg = 0; + re_refresh(el); + break; + + case CC_REFRESH_BEEP: + el->el_state.argument = 1; + el->el_state.doingarg = 0; + re_refresh(el); + term_beep(el); + break; + + case CC_NORM: /* normal char */ + el->el_state.argument = 1; + el->el_state.doingarg = 0; + break; + + case CC_ARGHACK: /* Suggested by Rich Salz */ + /* */ + break; /* keep going... */ + + case CC_EOF: /* end of file typed */ + num = 0; + break; + + case CC_NEWLINE: /* normal end of line */ + num = el->el_line.lastchar - el->el_line.buffer; + break; + + case CC_FATAL: /* fatal error, reset to known state */ +#ifdef DEBUG_READ + (void) fprintf(el->el_errfile, + "*** editor fatal ERROR ***\r\n\n"); +#endif /* DEBUG_READ */ + /* put (real) cursor in a known place */ + re_clear_display(el); /* reset the display stuff */ + ch_reset(el); /* reset the input pointers */ + re_refresh(el); /* print the prompt again */ + el->el_state.argument = 1; + el->el_state.doingarg = 0; + break; + + case CC_ERROR: + default: /* functions we don't know about */ +#ifdef DEBUG_READ + (void) fprintf(el->el_errfile, + "*** editor ERROR ***\r\n\n"); +#endif /* DEBUG_READ */ + el->el_state.argument = 1; + el->el_state.doingarg = 0; + term_beep(el); + term__flush(); + break; + } + } + + /* make sure the tty is set up correctly */ + (void) tty_cookedmode(el); + term__flush(); /* flush any buffered output */ + if (el->el_flags & HANDLE_SIGNALS) + sig_clr(el); + if (nread) + *nread = num; + return (num ? el->el_line.buffer : NULL); +} diff --git a/main/editline/read.h b/main/editline/read.h new file mode 100644 index 000000000..b01e77db2 --- /dev/null +++ b/main/editline/read.h @@ -0,0 +1,55 @@ +/* $NetBSD: read.h,v 1.1 2001/09/27 19:29:50 christos Exp $ */ + +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Anthony Mallet. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION OR CONTRIBUTORS + * 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. + */ + +/* + * el.read.h: Character reading functions + */ +#ifndef _h_el_read +#define _h_el_read + +typedef int (*el_rfunc_t)(EditLine *, char *); + +typedef struct el_read_t { + el_rfunc_t read_char; /* Function to read a character */ +} el_read_t; + +protected int read_init(EditLine *); +protected int el_read_setfn(EditLine *, el_rfunc_t); +protected el_rfunc_t el_read_getfn(EditLine *); + +#endif /* _h_el_read */ diff --git a/main/editline/readline.c b/main/editline/readline.c new file mode 100644 index 000000000..442118b38 --- /dev/null +++ b/main/editline/readline.c @@ -0,0 +1,1669 @@ +/* $NetBSD: readline.c,v 1.21 2002/03/18 16:20:36 christos Exp $ */ + +/*- + * Copyright (c) 1997 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Jaromir Dolecek. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION OR CONTRIBUTORS + * 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +__RCSID("$NetBSD: readline.c,v 1.21 2002/03/18 16:20:36 christos Exp $"); +#endif /* not lint && not SCCSID */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SOLARIS +#include +#endif + +#include "histedit.h" +#include "readline/readline.h" +#include "el.h" +#include "fcns.h" /* for EL_NUM_FCNS */ + +/* for rl_complete() */ +#define TAB '\r' + +/* see comment at the #ifdef for sense of this */ +#define GDB_411_HACK + +/* readline compatibility stuff - look at readline sources/documentation */ +/* to see what these variables mean */ +const char *rl_library_version = "EditLine wrapper"; +static char empty[] = { '\0' }; +static char expand_chars[] = { ' ', '\t', '\n', '=', '(', '\0' }; +static char break_chars[] = { ' ', '\t', '\n', '"', '\\', '\'', '`', '@', '$', + '>', '<', '=', ';', '|', '&', '{', '(', '\0' }; +char *rl_readline_name = empty; +FILE *rl_instream = NULL; +FILE *rl_outstream = NULL; +int rl_point = 0; +int rl_end = 0; +char *rl_line_buffer = NULL; + +int history_base = 1; /* probably never subject to change */ +int history_length = 0; +int max_input_history = 0; +char history_expansion_char = '!'; +char history_subst_char = '^'; +char *history_no_expand_chars = expand_chars; +Function *history_inhibit_expansion_function = NULL; + +int rl_inhibit_completion = 0; +int rl_attempted_completion_over = 0; +char *rl_basic_word_break_characters = break_chars; +char *rl_completer_word_break_characters = NULL; +char *rl_completer_quote_characters = NULL; +CPFunction *rl_completion_entry_function = NULL; +CPPFunction *rl_attempted_completion_function = NULL; + +/* + * This is set to character indicating type of completion being done by + * rl_complete_internal(); this is available for application completion + * functions. + */ +int rl_completion_type = 0; + +/* + * If more than this number of items results from query for possible + * completions, we ask user if they are sure to really display the list. + */ +int rl_completion_query_items = 100; + +/* + * List of characters which are word break characters, but should be left + * in the parsed text when it is passed to the completion function. + * Shell uses this to help determine what kind of completing to do. + */ +char *rl_special_prefixes = (char *)NULL; + +/* + * This is the character appended to the completed words if at the end of + * the line. Default is ' ' (a space). + */ +int rl_completion_append_character = ' '; + +/* stuff below is used internally by libedit for readline emulation */ + +/* if not zero, non-unique completions always show list of possible matches */ +static int _rl_complete_show_all = 0; + +static History *h = NULL; +static EditLine *e = NULL; +static int el_rl_complete_cmdnum = 0; + +/* internal functions */ +static unsigned char _el_rl_complete(EditLine *, int); +static char *_get_prompt(EditLine *); +static HIST_ENTRY *_move_history(int); +static int _history_search_gen(const char *, int, int); +static int _history_expand_command(const char *, size_t, char **); +static char *_rl_compat_sub(const char *, const char *, + const char *, int); +static int rl_complete_internal(int); +static int _rl_qsort_string_compare(const void *, const void *); + +/* + * needed for prompt switching in readline() + */ +static char *el_rl_prompt = NULL; + + +/* ARGSUSED */ +static char * +_get_prompt(EditLine *el) +{ + return (el_rl_prompt); +} + + +/* + * generic function for moving around history + */ +static HIST_ENTRY * +_move_history(int op) +{ + HistEvent ev; + static HIST_ENTRY rl_he; + + if (history(h, &ev, op) != 0) + return (HIST_ENTRY *) NULL; + + rl_he.line = ev.str; + rl_he.data = ""; + + return (&rl_he); +} + + +/* + * READLINE compatibility stuff + */ + +/* + * initialize rl compat stuff + */ +int +rl_initialize(void) +{ + HistEvent ev; + const LineInfo *li; + int i; + int editmode = 1; + struct termios t; + + if (e != NULL) + el_end(e); + if (h != NULL) + history_end(h); + + if (!rl_instream) + rl_instream = stdin; + if (!rl_outstream) + rl_outstream = stdout; + + /* + * See if we don't really want to run the editor + */ + if (tcgetattr(fileno(rl_instream), &t) != -1 && (t.c_lflag & ECHO) == 0) + editmode = 0; + + e = el_init(rl_readline_name, rl_instream, rl_outstream, stderr); + + if (!editmode) + el_set(e, EL_EDITMODE, 0); + + h = history_init(); + if (!e || !h) + return (-1); + + history(h, &ev, H_SETSIZE, INT_MAX); /* unlimited */ + history_length = 0; + max_input_history = INT_MAX; + el_set(e, EL_HIST, history, h); + + /* for proper prompt printing in readline() */ + el_rl_prompt = strdup(""); + el_set(e, EL_PROMPT, _get_prompt); + el_set(e, EL_SIGNAL, 1); + + /* set default mode to "emacs"-style and read setting afterwards */ + /* so this can be overriden */ + el_set(e, EL_EDITOR, "emacs"); + + /* + * Word completition - this has to go AFTER rebinding keys + * to emacs-style. + */ + el_set(e, EL_ADDFN, "rl_complete", + "ReadLine compatible completition function", + _el_rl_complete); + el_set(e, EL_BIND, "^I", "rl_complete", NULL); + + /* + * Find out where the rl_complete function was added; this is + * used later to detect that lastcmd was also rl_complete. + */ + for(i=EL_NUM_FCNS; i < e->el_map.nfunc; i++) { + if (e->el_map.func[i] == _el_rl_complete) { + el_rl_complete_cmdnum = i; + break; + } + } + + /* read settings from configuration file */ + el_source(e, NULL); + + /* + * Unfortunately, some applications really do use rl_point + * and rl_line_buffer directly. + */ + li = el_line(e); + /* a cheesy way to get rid of const cast. */ + rl_line_buffer = memchr(li->buffer, *li->buffer, 1); + rl_point = rl_end = 0; + + return (0); +} + + +/* + * read one line from input stream and return it, chomping + * trailing newline (if there is any) + */ +char * +readline(const char *prompt) +{ + HistEvent ev; + int count; + const char *ret; + char *buf; + + if (e == NULL || h == NULL) + rl_initialize(); + + /* update prompt accordingly to what has been passed */ + if (!prompt) + prompt = ""; + if (strcmp(el_rl_prompt, prompt) != 0) { + free(el_rl_prompt); + el_rl_prompt = strdup(prompt); + } + /* get one line from input stream */ + ret = el_gets(e, &count); + + if (ret && count > 0) { + int lastidx; + + buf = strdup(ret); + lastidx = count - 1; + if (buf[lastidx] == '\n') + buf[lastidx] = '\0'; + } else + buf = NULL; + + history(h, &ev, H_GETSIZE); + history_length = ev.num; + + return buf; +} + +/* + * history functions + */ + +/* + * is normally called before application starts to use + * history expansion functions + */ +void +using_history(void) +{ + if (h == NULL || e == NULL) + rl_initialize(); +} + + +/* + * substitute ``what'' with ``with'', returning resulting string; if + * globally == 1, substitutes all occurences of what, otherwise only the + * first one + */ +static char * +_rl_compat_sub(const char *str, const char *what, const char *with, + int globally) +{ + char *result; + const char *temp, *new; + int len, with_len, what_len, add; + size_t size, i; + + result = malloc((size = 16)); + temp = str; + with_len = strlen(with); + what_len = strlen(what); + len = 0; + do { + new = strstr(temp, what); + if (new) { + i = new - temp; + add = i + with_len; + if (i + add + 1 >= size) { + size += add + 1; + result = realloc(result, size); + } + (void) strncpy(&result[len], temp, i); + len += i; + (void) strcpy(&result[len], with); /* safe */ + len += with_len; + temp = new + what_len; + } else { + add = strlen(temp); + if (len + add + 1 >= size) { + size += add + 1; + result = realloc(result, size); + } + (void) strcpy(&result[len], temp); /* safe */ + len += add; + temp = NULL; + } + } while (temp && globally); + result[len] = '\0'; + + return (result); +} + + +/* + * the real function doing history expansion - takes as argument command + * to do and data upon which the command should be executed + * does expansion the way I've understood readline documentation + * word designator ``%'' isn't supported (yet ?) + * + * returns 0 if data was not modified, 1 if it was and 2 if the string + * should be only printed and not executed; in case of error, + * returns -1 and *result points to NULL + * it's callers responsibility to free() string returned in *result + */ +static int +_history_expand_command(const char *command, size_t cmdlen, char **result) +{ + char **arr, *tempcmd, *line, *search = NULL, *cmd; + const char *event_data = NULL; + static char *from = NULL, *to = NULL; + int start = -1, end = -1, max, i, idx; + int h_on = 0, t_on = 0, r_on = 0, e_on = 0, p_on = 0, g_on = 0; + int event_num = 0, retval; + size_t cmdsize; + + *result = NULL; + + cmd = alloca(cmdlen + 1); + (void) strncpy(cmd, command, cmdlen); + cmd[cmdlen] = 0; + + idx = 1; + /* find out which event to take */ + if (cmd[idx] == history_expansion_char) { + event_num = history_length; + idx++; + } else { + int off, num; + size_t len; + off = idx; + while (cmd[off] && !strchr(":^$*-%", cmd[off])) + off++; + num = atoi(&cmd[idx]); + if (num != 0) { + event_num = num; + if (num < 0) + event_num += history_length + 1; + } else { + int prefix = 1, curr_num; + HistEvent ev; + + len = off - idx; + if (cmd[idx] == '?') { + idx++, len--; + if (cmd[off - 1] == '?') + len--; + else if (cmd[off] != '\n' && cmd[off] != '\0') + return (-1); + prefix = 0; + } + search = alloca(len + 1); + (void) strncpy(search, &cmd[idx], len); + search[len] = '\0'; + + if (history(h, &ev, H_CURR) != 0) + return (-1); + curr_num = ev.num; + + if (prefix) + retval = history_search_prefix(search, -1); + else + retval = history_search(search, -1); + + if (retval == -1) { + fprintf(rl_outstream, "%s: Event not found\n", + search); + return (-1); + } + if (history(h, &ev, H_CURR) != 0) + return (-1); + event_data = ev.str; + + /* roll back to original position */ + history(h, &ev, H_NEXT_EVENT, curr_num); + } + idx = off; + } + + if (!event_data && event_num >= 0) { + HIST_ENTRY *rl_he; + rl_he = history_get(event_num); + if (!rl_he) + return (0); + event_data = rl_he->line; + } else + return (-1); + + if (cmd[idx] != ':') + return (-1); + cmd += idx + 1; + + /* recognize cmd */ + if (*cmd == '^') + start = end = 1, cmd++; + else if (*cmd == '$') + start = end = -1, cmd++; + else if (*cmd == '*') + start = 1, end = -1, cmd++; + else if (isdigit((unsigned char) *cmd)) { + const char *temp; + int shifted = 0; + + start = atoi(cmd); + temp = cmd; + for (; isdigit((unsigned char) *cmd); cmd++); + if (temp != cmd) + shifted = 1; + if (shifted && *cmd == '-') { + if (!isdigit((unsigned char) *(cmd + 1))) + end = -2; + else { + end = atoi(cmd + 1); + for (; isdigit((unsigned char) *cmd); cmd++); + } + } else if (shifted && *cmd == '*') + end = -1, cmd++; + else if (shifted) + end = start; + } + if (*cmd == ':') + cmd++; + + line = strdup(event_data); + for (; *cmd; cmd++) { + if (*cmd == ':') + continue; + else if (*cmd == 'h') + h_on = 1 | g_on, g_on = 0; + else if (*cmd == 't') + t_on = 1 | g_on, g_on = 0; + else if (*cmd == 'r') + r_on = 1 | g_on, g_on = 0; + else if (*cmd == 'e') + e_on = 1 | g_on, g_on = 0; + else if (*cmd == 'p') + p_on = 1 | g_on, g_on = 0; + else if (*cmd == 'g') + g_on = 2; + else if (*cmd == 's' || *cmd == '&') { + char *what, *with, delim; + int len, from_len; + size_t size; + + if (*cmd == '&' && (from == NULL || to == NULL)) + continue; + else if (*cmd == 's') { + delim = *(++cmd), cmd++; + size = 16; + what = realloc(from, size); + len = 0; + for (; *cmd && *cmd != delim; cmd++) { + if (*cmd == '\\' + && *(cmd + 1) == delim) + cmd++; + if (len >= size) + what = realloc(what, + (size <<= 1)); + what[len++] = *cmd; + } + what[len] = '\0'; + from = what; + if (*what == '\0') { + free(what); + if (search) + from = strdup(search); + else { + from = NULL; + return (-1); + } + } + cmd++; /* shift after delim */ + if (!*cmd) + continue; + + size = 16; + with = realloc(to, size); + len = 0; + from_len = strlen(from); + for (; *cmd && *cmd != delim; cmd++) { + if (len + from_len + 1 >= size) { + size += from_len + 1; + with = realloc(with, size); + } + if (*cmd == '&') { + /* safe */ + (void) strcpy(&with[len], from); + len += from_len; + continue; + } + if (*cmd == '\\' + && (*(cmd + 1) == delim + || *(cmd + 1) == '&')) + cmd++; + with[len++] = *cmd; + } + with[len] = '\0'; + to = with; + + tempcmd = _rl_compat_sub(line, from, to, + (g_on) ? 1 : 0); + free(line); + line = tempcmd; + g_on = 0; + } + } + } + + arr = history_tokenize(line); + free(line); /* no more needed */ + if (arr && *arr == NULL) + free(arr), arr = NULL; + if (!arr) + return (-1); + + /* find out max valid idx to array of array */ + max = 0; + for (i = 0; arr[i]; i++) + max++; + max--; + + /* set boundaries to something relevant */ + if (start < 0) + start = 1; + if (end < 0) + end = max - ((end < -1) ? 1 : 0); + + /* check boundaries ... */ + if (start > max || end > max || start > end) + return (-1); + + for (i = 0; i <= max; i++) { + char *temp; + if (h_on && (i == 1 || h_on > 1) && + (temp = strrchr(arr[i], '/'))) + *(temp + 1) = '\0'; + if (t_on && (i == 1 || t_on > 1) && + (temp = strrchr(arr[i], '/'))) + (void) strcpy(arr[i], temp + 1); + if (r_on && (i == 1 || r_on > 1) && + (temp = strrchr(arr[i], '.'))) + *temp = '\0'; + if (e_on && (i == 1 || e_on > 1) && + (temp = strrchr(arr[i], '.'))) + (void) strcpy(arr[i], temp); + } + + cmdsize = 1, cmdlen = 0; + tempcmd = malloc(cmdsize); + for (i = start; start <= i && i <= end; i++) { + int arr_len; + + arr_len = strlen(arr[i]); + if (cmdlen + arr_len + 1 >= cmdsize) { + cmdsize += arr_len + 1; + tempcmd = realloc(tempcmd, cmdsize); + } + (void) strcpy(&tempcmd[cmdlen], arr[i]); /* safe */ + cmdlen += arr_len; + tempcmd[cmdlen++] = ' '; /* add a space */ + } + while (cmdlen > 0 && isspace((unsigned char) tempcmd[cmdlen - 1])) + cmdlen--; + tempcmd[cmdlen] = '\0'; + + *result = tempcmd; + + for (i = 0; i <= max; i++) + free(arr[i]); + free(arr), arr = (char **) NULL; + return (p_on) ? 2 : 1; +} + + +/* + * csh-style history expansion + */ +int +history_expand(char *str, char **output) +{ + int i, retval = 0, idx; + size_t size; + char *temp, *result; + + if (h == NULL || e == NULL) + rl_initialize(); + + *output = strdup(str); /* do it early */ + + if (str[0] == history_subst_char) { + /* ^foo^foo2^ is equivalent to !!:s^foo^foo2^ */ + temp = alloca(4 + strlen(str) + 1); + temp[0] = temp[1] = history_expansion_char; + temp[2] = ':'; + temp[3] = 's'; + (void) strcpy(temp + 4, str); + str = temp; + } +#define ADD_STRING(what, len) \ + { \ + if (idx + len + 1 > size) \ + result = realloc(result, (size += len + 1)); \ + (void)strncpy(&result[idx], what, len); \ + idx += len; \ + result[idx] = '\0'; \ + } + + result = NULL; + size = idx = 0; + for (i = 0; str[i];) { + int start, j, loop_again; + size_t len; + + loop_again = 1; + start = j = i; +loop: + for (; str[j]; j++) { + if (str[j] == '\\' && + str[j + 1] == history_expansion_char) { + (void) strcpy(&str[j], &str[j + 1]); + continue; + } + if (!loop_again) { + if (str[j] == '?') { + while (str[j] && str[++j] != '?'); + if (str[j] == '?') + j++; + } else if (isspace((unsigned char) str[j])) + break; + } + if (str[j] == history_expansion_char + && !strchr(history_no_expand_chars, str[j + 1]) + && (!history_inhibit_expansion_function || + (*history_inhibit_expansion_function)(str, j) == 0)) + break; + } + + if (str[j] && str[j + 1] != '#' && loop_again) { + i = j; + j++; + if (str[j] == history_expansion_char) + j++; + loop_again = 0; + goto loop; + } + len = i - start; + temp = &str[start]; + ADD_STRING(temp, len); + + if (str[i] == '\0' || str[i] != history_expansion_char + || str[i + 1] == '#') { + len = j - i; + temp = &str[i]; + ADD_STRING(temp, len); + if (start == 0) + retval = 0; + else + retval = 1; + break; + } + retval = _history_expand_command(&str[i], (size_t) (j - i), + &temp); + if (retval != -1) { + len = strlen(temp); + ADD_STRING(temp, len); + } + i = j; + } /* for(i ...) */ + + if (retval == 2) { + add_history(temp); +#ifdef GDB_411_HACK + /* gdb 4.11 has been shipped with readline, where */ + /* history_expand() returned -1 when the line */ + /* should not be executed; in readline 2.1+ */ + /* it should return 2 in such a case */ + retval = -1; +#endif + } + free(*output); + *output = result; + + return (retval); +} + + +/* + * Parse the string into individual tokens, similarily to how shell would do it. + */ +char ** +history_tokenize(const char *str) +{ + int size = 1, result_idx = 0, i, start; + size_t len; + char **result = NULL, *temp, delim = '\0'; + + for (i = 0; str[i]; i++) { + while (isspace((unsigned char) str[i])) + i++; + start = i; + for (; str[i]; i++) { + if (str[i] == '\\') { + if (str[i+1] != '\0') + i++; + } else if (str[i] == delim) + delim = '\0'; + else if (!delim && + (isspace((unsigned char) str[i]) || + strchr("()<>;&|$", str[i]))) + break; + else if (!delim && strchr("'`\"", str[i])) + delim = str[i]; + } + + if (result_idx + 2 >= size) { + size <<= 1; + result = realloc(result, size * sizeof(char *)); + } + len = i - start; + temp = malloc(len + 1); + (void) strncpy(temp, &str[start], len); + temp[len] = '\0'; + result[result_idx++] = temp; + result[result_idx] = NULL; + } + + return (result); +} + + +/* + * limit size of history record to ``max'' events + */ +void +stifle_history(int max) +{ + HistEvent ev; + + if (h == NULL || e == NULL) + rl_initialize(); + + if (history(h, &ev, H_SETSIZE, max) == 0) + max_input_history = max; +} + + +/* + * "unlimit" size of history - set the limit to maximum allowed int value + */ +int +unstifle_history(void) +{ + HistEvent ev; + int omax; + + history(h, &ev, H_SETSIZE, INT_MAX); + omax = max_input_history; + max_input_history = INT_MAX; + return (omax); /* some value _must_ be returned */ +} + + +int +history_is_stifled(void) +{ + + /* cannot return true answer */ + return (max_input_history != INT_MAX); +} + + +/* + * read history from a file given + */ +int +read_history(const char *filename) +{ + HistEvent ev; + + if (h == NULL || e == NULL) + rl_initialize(); + return (history(h, &ev, H_LOAD, filename)); +} + + +/* + * write history to a file given + */ +int +write_history(const char *filename) +{ + HistEvent ev; + + if (h == NULL || e == NULL) + rl_initialize(); + return (history(h, &ev, H_SAVE, filename)); +} + + +/* + * returns history ``num''th event + * + * returned pointer points to static variable + */ +HIST_ENTRY * +history_get(int num) +{ + static HIST_ENTRY she; + HistEvent ev; + int i = 1, curr_num; + + if (h == NULL || e == NULL) + rl_initialize(); + + /* rewind to beginning */ + if (history(h, &ev, H_CURR) != 0) + return (NULL); + curr_num = ev.num; + if (history(h, &ev, H_LAST) != 0) + return (NULL); /* error */ + while (i < num && history(h, &ev, H_PREV) == 0) + i++; + if (i != num) + return (NULL); /* not so many entries */ + + she.line = ev.str; + she.data = NULL; + + /* rewind history to the same event it was before */ + (void) history(h, &ev, H_FIRST); + (void) history(h, &ev, H_NEXT_EVENT, curr_num); + + return (&she); +} + + +/* + * add the line to history table + */ +int +add_history(const char *line) +{ + HistEvent ev; + + if (h == NULL || e == NULL) + rl_initialize(); + + (void) history(h, &ev, H_ENTER, line); + if (history(h, &ev, H_GETSIZE) == 0) + history_length = ev.num; + + return (!(history_length > 0)); /* return 0 if all is okay */ +} + + +/* + * clear the history list - delete all entries + */ +void +clear_history(void) +{ + HistEvent ev; + + history(h, &ev, H_CLEAR); +} + + +/* + * returns offset of the current history event + */ +int +where_history(void) +{ + HistEvent ev; + int curr_num, off; + + if (history(h, &ev, H_CURR) != 0) + return (0); + curr_num = ev.num; + + history(h, &ev, H_FIRST); + off = 1; + while (ev.num != curr_num && history(h, &ev, H_NEXT) == 0) + off++; + + return (off); +} + + +/* + * returns current history event or NULL if there is no such event + */ +HIST_ENTRY * +current_history(void) +{ + + return (_move_history(H_CURR)); +} + + +/* + * returns total number of bytes history events' data are using + */ +int +history_total_bytes(void) +{ + HistEvent ev; + int curr_num, size; + + if (history(h, &ev, H_CURR) != 0) + return (-1); + curr_num = ev.num; + + history(h, &ev, H_FIRST); + size = 0; + do + size += strlen(ev.str); + while (history(h, &ev, H_NEXT) == 0); + + /* get to the same position as before */ + history(h, &ev, H_PREV_EVENT, curr_num); + + return (size); +} + + +/* + * sets the position in the history list to ``pos'' + */ +int +history_set_pos(int pos) +{ + HistEvent ev; + int off, curr_num; + + if (pos > history_length || pos < 0) + return (-1); + + history(h, &ev, H_CURR); + curr_num = ev.num; + history(h, &ev, H_FIRST); + off = 0; + while (off < pos && history(h, &ev, H_NEXT) == 0) + off++; + + if (off != pos) { /* do a rollback in case of error */ + history(h, &ev, H_FIRST); + history(h, &ev, H_NEXT_EVENT, curr_num); + return (-1); + } + return (0); +} + + +/* + * returns previous event in history and shifts pointer accordingly + */ +HIST_ENTRY * +previous_history(void) +{ + + return (_move_history(H_PREV)); +} + + +/* + * returns next event in history and shifts pointer accordingly + */ +HIST_ENTRY * +next_history(void) +{ + + return (_move_history(H_NEXT)); +} + + +/* + * generic history search function + */ +static int +_history_search_gen(const char *str, int direction, int pos) +{ + HistEvent ev; + const char *strp; + int curr_num; + + if (history(h, &ev, H_CURR) != 0) + return (-1); + curr_num = ev.num; + + for (;;) { + strp = strstr(ev.str, str); + if (strp && (pos < 0 || &ev.str[pos] == strp)) + return (int) (strp - ev.str); + if (history(h, &ev, direction < 0 ? H_PREV : H_NEXT) != 0) + break; + } + + history(h, &ev, direction < 0 ? H_NEXT_EVENT : H_PREV_EVENT, curr_num); + + return (-1); +} + + +/* + * searches for first history event containing the str + */ +int +history_search(const char *str, int direction) +{ + + return (_history_search_gen(str, direction, -1)); +} + + +/* + * searches for first history event beginning with str + */ +int +history_search_prefix(const char *str, int direction) +{ + + return (_history_search_gen(str, direction, 0)); +} + + +/* + * search for event in history containing str, starting at offset + * abs(pos); continue backward, if pos<0, forward otherwise + */ +/* ARGSUSED */ +int +history_search_pos(const char *str, int direction, int pos) +{ + HistEvent ev; + int curr_num, off; + + off = (pos > 0) ? pos : -pos; + pos = (pos > 0) ? 1 : -1; + + if (history(h, &ev, H_CURR) != 0) + return (-1); + curr_num = ev.num; + + if (history_set_pos(off) != 0 || history(h, &ev, H_CURR) != 0) + return (-1); + + + for (;;) { + if (strstr(ev.str, str)) + return (off); + if (history(h, &ev, (pos < 0) ? H_PREV : H_NEXT) != 0) + break; + } + + /* set "current" pointer back to previous state */ + history(h, &ev, (pos < 0) ? H_NEXT_EVENT : H_PREV_EVENT, curr_num); + + return (-1); +} + + +/********************************/ +/* completition functions */ + +/* + * does tilde expansion of strings of type ``~user/foo'' + * if ``user'' isn't valid user name or ``txt'' doesn't start + * w/ '~', returns pointer to strdup()ed copy of ``txt'' + * + * it's callers's responsibility to free() returned string + */ +char * +tilde_expand(char *txt) +{ + struct passwd *pass; + char *temp; + size_t len = 0; + + if (txt[0] != '~') + return (strdup(txt)); + + temp = strchr(txt + 1, '/'); + if (temp == NULL) + temp = strdup(txt + 1); + else { + len = temp - txt + 1; /* text until string after slash */ + temp = malloc(len); + (void) strncpy(temp, txt + 1, len - 2); + temp[len - 2] = '\0'; + } + pass = getpwnam(temp); + free(temp); /* value no more needed */ + if (pass == NULL) + return (strdup(txt)); + + /* update pointer txt to point at string immedially following */ + /* first slash */ + txt += len; + + temp = malloc(strlen(pass->pw_dir) + 1 + strlen(txt) + 1); + (void) sprintf(temp, "%s/%s", pass->pw_dir, txt); + + return (temp); +} + + +/* + * return first found file name starting by the ``text'' or NULL if no + * such file can be found + * value of ``state'' is ignored + * + * it's caller's responsibility to free returned string + */ +char * +filename_completion_function(const char *text, int state) +{ + static DIR *dir = NULL; + static char *filename = NULL, *dirname = NULL; + static size_t filename_len = 0; + struct dirent *entry; + char *temp; + size_t len; + + if (state == 0 || dir == NULL) { + if (dir != NULL) { + closedir(dir); + dir = NULL; + } + temp = strrchr(text, '/'); + if (temp) { + temp++; + filename = realloc(filename, strlen(temp) + 1); + (void) strcpy(filename, temp); + len = temp - text; /* including last slash */ + dirname = realloc(dirname, len + 1); + (void) strncpy(dirname, text, len); + dirname[len] = '\0'; + } else { + filename = strdup(text); + dirname = NULL; + } + + /* support for ``~user'' syntax */ + if (dirname && *dirname == '~') { + temp = tilde_expand(dirname); + dirname = realloc(dirname, strlen(temp) + 1); + (void) strcpy(dirname, temp); /* safe */ + free(temp); /* no longer needed */ + } + /* will be used in cycle */ + filename_len = strlen(filename); + if (filename_len == 0) + return (NULL); /* no expansion possible */ + + dir = opendir(dirname ? dirname : "."); + if (!dir) + return (NULL); /* cannot open the directory */ + } + /* find the match */ + while ((entry = readdir(dir)) != NULL) { + /* otherwise, get first entry where first */ + /* filename_len characters are equal */ + if (entry->d_name[0] == filename[0] +#if defined(__SVR4) || defined(__linux__) + && strlen(entry->d_name) >= filename_len +#else + && entry->d_namlen >= filename_len +#endif + && strncmp(entry->d_name, filename, + filename_len) == 0) + break; + } + + if (entry) { /* match found */ + + struct stat stbuf; +#if defined(__SVR4) || defined(__linux__) + len = strlen(entry->d_name) + +#else + len = entry->d_namlen + +#endif + ((dirname) ? strlen(dirname) : 0) + 1 + 1; + temp = malloc(len); + (void) sprintf(temp, "%s%s", + dirname ? dirname : "", entry->d_name); /* safe */ + + /* test, if it's directory */ + if (stat(temp, &stbuf) == 0 && S_ISDIR(stbuf.st_mode)) + strcat(temp, "/"); /* safe */ + } else + temp = NULL; + + return (temp); +} + + +/* + * a completion generator for usernames; returns _first_ username + * which starts with supplied text + * text contains a partial username preceded by random character + * (usually '~'); state is ignored + * it's callers responsibility to free returned value + */ +char * +username_completion_function(const char *text, int state) +{ + struct passwd *pwd; + + if (text[0] == '\0') + return (NULL); + + if (*text == '~') + text++; + + if (state == 0) + setpwent(); + + while ((pwd = getpwent()) && text[0] == pwd->pw_name[0] + && strcmp(text, pwd->pw_name) == 0); + + if (pwd == NULL) { + endpwent(); + return (NULL); + } + return (strdup(pwd->pw_name)); +} + + +/* + * el-compatible wrapper around rl_complete; needed for key binding + */ +/* ARGSUSED */ +static unsigned char +_el_rl_complete(EditLine *el, int ch) +{ + return (unsigned char) rl_complete(0, ch); +} + + +/* + * returns list of completitions for text given + */ +char ** +completion_matches(const char *text, CPFunction *genfunc) +{ + char **match_list = NULL, *retstr, *prevstr; + size_t match_list_len, max_equal, which, i; + int matches; + + if (h == NULL || e == NULL) + rl_initialize(); + + matches = 0; + match_list_len = 1; + while ((retstr = (*genfunc) (text, matches)) != NULL) { + if (matches + 1 >= match_list_len) { + match_list_len <<= 1; + match_list = realloc(match_list, + match_list_len * sizeof(char *)); + } + match_list[++matches] = retstr; + } + + if (!match_list) + return (char **) NULL; /* nothing found */ + + /* find least denominator and insert it to match_list[0] */ + which = 2; + prevstr = match_list[1]; + max_equal = strlen(prevstr); + for (; which <= matches; which++) { + for (i = 0; i < max_equal && + prevstr[i] == match_list[which][i]; i++) + continue; + max_equal = i; + } + + retstr = malloc(max_equal + 1); + (void) strncpy(retstr, match_list[1], max_equal); + retstr[max_equal] = '\0'; + match_list[0] = retstr; + + /* add NULL as last pointer to the array */ + if (matches + 1 >= match_list_len) + match_list = realloc(match_list, + (match_list_len + 1) * sizeof(char *)); + match_list[matches + 1] = (char *) NULL; + + return (match_list); +} + +/* + * Sort function for qsort(). Just wrapper around strcasecmp(). + */ +static int +_rl_qsort_string_compare(i1, i2) + const void *i1, *i2; +{ + /* LINTED const castaway */ + const char *s1 = ((const char **)i1)[0]; + /* LINTED const castaway */ + const char *s2 = ((const char **)i2)[0]; + + return strcasecmp(s1, s2); +} + +/* + * Display list of strings in columnar format on readline's output stream. + * 'matches' is list of strings, 'len' is number of strings in 'matches', + * 'max' is maximum length of string in 'matches'. + */ +void +rl_display_match_list (matches, len, max) + char **matches; + int len, max; +{ + int i, idx, limit, count; + int screenwidth = e->el_term.t_size.h; + + /* + * Find out how many entries can be put on one line, count + * with two spaces between strings. + */ + limit = screenwidth / (max + 2); + if (limit == 0) + limit = 1; + + /* how many lines of output */ + count = len / limit; + if (count * limit < len) + count++; + + /* Sort the items if they are not already sorted. */ + qsort(&matches[1], (size_t)(len - 1), sizeof(char *), + _rl_qsort_string_compare); + + idx = 1; + for(; count > 0; count--) { + for(i=0; i < limit && matches[idx]; i++, idx++) + fprintf(e->el_outfile, "%-*s ", max, matches[idx]); + fprintf(e->el_outfile, "\n"); + } +} + +/* + * Complete the word at or before point, called by rl_complete() + * 'what_to_do' says what to do with the completion. + * `?' means list the possible completions. + * TAB means do standard completion. + * `*' means insert all of the possible completions. + * `!' means to do standard completion, and list all possible completions if + * there is more than one. + * + * Note: '*' support is not implemented + */ +static int +rl_complete_internal(int what_to_do) +{ + CPFunction *complet_func; + const LineInfo *li; + char *temp, **matches; + const char *ctemp; + size_t len; + + rl_completion_type = what_to_do; + + if (h == NULL || e == NULL) + rl_initialize(); + + complet_func = rl_completion_entry_function; + if (!complet_func) + complet_func = filename_completion_function; + + /* We now look backwards for the start of a filename/variable word */ + li = el_line(e); + ctemp = (const char *) li->cursor; + while (ctemp > li->buffer + && !strchr(rl_basic_word_break_characters, ctemp[-1]) + && (!rl_special_prefixes + || !strchr(rl_special_prefixes, ctemp[-1]) ) ) + ctemp--; + + len = li->cursor - ctemp; + temp = alloca(len + 1); + (void) strncpy(temp, ctemp, len); + temp[len] = '\0'; + + /* these can be used by function called in completion_matches() */ + /* or (*rl_attempted_completion_function)() */ + rl_point = li->cursor - li->buffer; + rl_end = li->lastchar - li->buffer; + + if (!rl_attempted_completion_function) + matches = completion_matches(temp, complet_func); + else { + int end = li->cursor - li->buffer; + matches = (*rl_attempted_completion_function) (temp, (int) + (end - len), end); + } + + if (matches) { + int i, retval = CC_REFRESH; + int matches_num, maxlen, match_len, match_display=1; + + /* + * Only replace the completed string with common part of + * possible matches if there is possible completion. + */ + if (matches[0][0] != '\0') { + el_deletestr(e, (int) len); + el_insertstr(e, matches[0]); + } + + if (what_to_do == '?') + goto display_matches; + + if (matches[2] == NULL && strcmp(matches[0], matches[1]) == 0) { + /* + * We found exact match. Add a space after + * it, unless we do filename completition and the + * object is a directory. + */ + size_t alen = strlen(matches[0]); + if ((complet_func != filename_completion_function + || (alen > 0 && (matches[0])[alen - 1] != '/')) + && rl_completion_append_character) { + char buf[2]; + buf[0] = rl_completion_append_character; + buf[1] = '\0'; + el_insertstr(e, buf); + } + } else if (what_to_do == '!') { + display_matches: + /* + * More than one match and requested to list possible + * matches. + */ + + for(i=1, maxlen=0; matches[i]; i++) { + match_len = strlen(matches[i]); + if (match_len > maxlen) + maxlen = match_len; + } + matches_num = i - 1; + + /* newline to get on next line from command line */ + fprintf(e->el_outfile, "\n"); + + /* + * If there are too many items, ask user for display + * confirmation. + */ + if (matches_num > rl_completion_query_items) { + fprintf(e->el_outfile, + "Display all %d possibilities? (y or n) ", + matches_num); + fflush(e->el_outfile); + if (getc(stdin) != 'y') + match_display = 0; + fprintf(e->el_outfile, "\n"); + } + + if (match_display) + rl_display_match_list(matches, matches_num, + maxlen); + retval = CC_REDISPLAY; + } else if (matches[0][0]) { + /* + * There was some common match, but the name was + * not complete enough. Next tab will print possible + * completions. + */ + el_beep(e); + } else { + /* lcd is not a valid object - further specification */ + /* is needed */ + el_beep(e); + retval = CC_NORM; + } + + /* free elements of array and the array itself */ + for (i = 0; matches[i]; i++) + free(matches[i]); + free(matches), matches = NULL; + + return (retval); + } + return (CC_NORM); +} + + +/* + * complete word at current point + */ +int +rl_complete(int ignore, int invoking_key) +{ + if (h == NULL || e == NULL) + rl_initialize(); + + if (rl_inhibit_completion) { + rl_insert(ignore, invoking_key); + return (CC_REFRESH); + } else if (e->el_state.lastcmd == el_rl_complete_cmdnum) + return rl_complete_internal('?'); + else if (_rl_complete_show_all) + return rl_complete_internal('!'); + else + return (rl_complete_internal(TAB)); +} + + +/* + * misc other functions + */ + +/* + * bind key c to readline-type function func + */ +int +rl_bind_key(int c, int func(int, int)) +{ + int retval = -1; + + if (h == NULL || e == NULL) + rl_initialize(); + + if (func == rl_insert) { + /* XXX notice there is no range checking of ``c'' */ + e->el_map.key[c] = ED_INSERT; + retval = 0; + } + return (retval); +} + + +/* + * read one key from input - handles chars pushed back + * to input stream also + */ +int +rl_read_key(void) +{ + char fooarr[2 * sizeof(int)]; + + if (e == NULL || h == NULL) + rl_initialize(); + + return (el_getc(e, fooarr)); +} + + +/* + * reset the terminal + */ +/* ARGSUSED */ +void +rl_reset_terminal(const char *p) +{ + + if (h == NULL || e == NULL) + rl_initialize(); + el_reset(e); +} + + +/* + * insert character ``c'' back into input stream, ``count'' times + */ +int +rl_insert(int count, int c) +{ + char arr[2]; + + if (h == NULL || e == NULL) + rl_initialize(); + + /* XXX - int -> char conversion can lose on multichars */ + arr[0] = c; + arr[1] = '\0'; + + for (; count > 0; count--) + el_push(e, arr); + + return (0); +} diff --git a/main/editline/readline/readline.h b/main/editline/readline/readline.h new file mode 100644 index 000000000..7485dde40 --- /dev/null +++ b/main/editline/readline/readline.h @@ -0,0 +1,118 @@ +/* $NetBSD: readline.h,v 1.1 2001/01/05 21:15:50 jdolecek Exp $ */ + +/*- + * Copyright (c) 1997 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Jaromir Dolecek. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION OR CONTRIBUTORS + * 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. + */ +#ifndef _READLINE_H_ +#define _READLINE_H_ + +#include + +/* list of readline stuff supported by editline library's readline wrapper */ + +/* typedefs */ +typedef int Function(const char *, int); +typedef void VFunction(void); +typedef char *CPFunction(const char *, int); +typedef char **CPPFunction(const char *, int, int); + +typedef struct _hist_entry { + const char *line; + const char *data; +} HIST_ENTRY; + +/* global variables used by readline enabled applications */ +#ifdef __cplusplus +extern "C" { +#endif +extern const char *rl_library_version; +extern char *rl_readline_name; +extern FILE *rl_instream; +extern FILE *rl_outstream; +extern char *rl_line_buffer; +extern int rl_point, rl_end; +extern int history_base, history_length; +extern int max_input_history; +extern char *rl_basic_word_break_characters; +extern char *rl_completer_word_break_characters; +extern char *rl_completer_quote_characters; +extern CPFunction *rl_completion_entry_function; +extern CPPFunction *rl_attempted_completion_function; +extern int rl_completion_type; +extern int rl_completion_query_items; +extern char *rl_special_prefixes; +extern int rl_completion_append_character; + +/* supported functions */ +char *readline(const char *); +int rl_initialize(void); + +void using_history(void); +int add_history(const char *); +void clear_history(void); +void stifle_history(int); +int unstifle_history(void); +int history_is_stifled(void); +int where_history(void); +HIST_ENTRY *current_history(void); +HIST_ENTRY *history_get(int); +int history_total_bytes(void); +int history_set_pos(int); +HIST_ENTRY *previous_history(void); +HIST_ENTRY *next_history(void); +int history_search(const char *, int); +int history_search_prefix(const char *, int); +int history_search_pos(const char *, int, int); +int read_history(const char *); +int write_history(const char *); +int history_expand(char *, char **); +char **history_tokenize(const char *); + +char *tilde_expand(char *); +char *filename_completion_function(const char *, int); +char *username_completion_function(const char *, int); +int rl_complete(int, int); +int rl_read_key(void); +char **completion_matches(const char *, CPFunction *); +void rl_display_match_list(char **, int, int); + +int rl_insert(int, int); +void rl_reset_terminal(const char *); +int rl_bind_key(int, int (*)(int, int)); +#ifdef __cplusplus +} +#endif + +#endif /* _READLINE_H_ */ diff --git a/main/editline/refresh.c b/main/editline/refresh.c new file mode 100644 index 000000000..935117741 --- /dev/null +++ b/main/editline/refresh.c @@ -0,0 +1,1104 @@ +/* $NetBSD: refresh.c,v 1.18 2002/03/18 16:00:58 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)refresh.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: refresh.c,v 1.18 2002/03/18 16:00:58 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * refresh.c: Lower level screen refreshing functions + */ +#include +#include +#include +#include + +#include "el.h" + +private void re_addc(EditLine *, int); +private void re_update_line(EditLine *, char *, char *, int); +private void re_insert (EditLine *, char *, int, int, char *, int); +private void re_delete(EditLine *, char *, int, int, int); +private void re_fastputc(EditLine *, int); +private void re__strncopy(char *, char *, size_t); +private void re__copy_and_pad(char *, const char *, size_t); + +#ifdef DEBUG_REFRESH +private void re_printstr(EditLine *, char *, char *, char *); +#define __F el->el_errfile +#define ELRE_ASSERT(a, b, c) do \ + if (a) { \ + (void) fprintf b; \ + c; \ + } \ + while (0) +#define ELRE_DEBUG(a, b) ELRE_ASSERT(a,b,;) + +/* re_printstr(): + * Print a string on the debugging pty + */ +private void +re_printstr(EditLine *el, char *str, char *f, char *t) +{ + + ELRE_DEBUG(1, (__F, "%s:\"", str)); + while (f < t) + ELRE_DEBUG(1, (__F, "%c", *f++ & 0177)); + ELRE_DEBUG(1, (__F, "\"\r\n")); +} +#else +#define ELRE_ASSERT(a, b, c) +#define ELRE_DEBUG(a, b) +#endif + + +/* re_addc(): + * Draw c, expanding tabs, control chars etc. + */ +private void +re_addc(EditLine *el, int c) +{ + + if (isprint(c)) { + re_putc(el, c, 1); + return; + } + if (c == '\n') { /* expand the newline */ + int oldv = el->el_refresh.r_cursor.v; + re_putc(el, '\0', 0); /* assure end of line */ + if (oldv == el->el_refresh.r_cursor.v) { /* XXX */ + el->el_refresh.r_cursor.h = 0; /* reset cursor pos */ + el->el_refresh.r_cursor.v++; + } + return; + } + if (c == '\t') { /* expand the tab */ + for (;;) { + re_putc(el, ' ', 1); + if ((el->el_refresh.r_cursor.h & 07) == 0) + break; /* go until tab stop */ + } + } else if (iscntrl(c)) { + re_putc(el, '^', 1); + if (c == '\177') + re_putc(el, '?', 1); + else + /* uncontrolify it; works only for iso8859-1 like sets */ + re_putc(el, (c | 0100), 1); + } else { + re_putc(el, '\\', 1); + re_putc(el, (int) ((((unsigned int) c >> 6) & 07) + '0'), 1); + re_putc(el, (int) ((((unsigned int) c >> 3) & 07) + '0'), 1); + re_putc(el, (c & 07) + '0', 1); + } +} + + +/* re_putc(): + * Draw the character given + */ +protected void +re_putc(EditLine *el, int c, int shift) +{ + + ELRE_DEBUG(1, (__F, "printing %3.3o '%c'\r\n", c, c)); + + el->el_vdisplay[el->el_refresh.r_cursor.v][el->el_refresh.r_cursor.h] = c; + if (!shift) + return; + + el->el_refresh.r_cursor.h++; /* advance to next place */ + if (el->el_refresh.r_cursor.h >= el->el_term.t_size.h) { + el->el_vdisplay[el->el_refresh.r_cursor.v][el->el_term.t_size.h] = '\0'; + /* assure end of line */ + el->el_refresh.r_cursor.h = 0; /* reset it. */ + + /* + * If we would overflow (input is longer than terminal size), + * emulate scroll by dropping first line and shuffling the rest. + * We do this via pointer shuffling - it's safe in this case + * and we avoid memcpy(). + */ + if (el->el_refresh.r_cursor.v + 1 >= el->el_term.t_size.v) { + int i, lins = el->el_term.t_size.v; + char *firstline = el->el_vdisplay[0]; + + for(i=1; i < lins; i++) + el->el_vdisplay[i-1] = el->el_vdisplay[i]; + + firstline[0] = '\0'; /* empty the string */ + el->el_vdisplay[i-1] = firstline; + } else + el->el_refresh.r_cursor.v++; + + ELRE_ASSERT(el->el_refresh.r_cursor.v >= el->el_term.t_size.v, + (__F, "\r\nre_putc: overflow! r_cursor.v == %d > %d\r\n", + el->el_refresh.r_cursor.v, el->el_term.t_size.v), + abort()); + } +} + + +/* re_refresh(): + * draws the new virtual screen image from the current input + * line, then goes line-by-line changing the real image to the new + * virtual image. The routine to re-draw a line can be replaced + * easily in hopes of a smarter one being placed there. + */ +protected void +re_refresh(EditLine *el) +{ + int i, rhdiff; + char *cp, *st; + coord_t cur; +#ifdef notyet + size_t termsz; +#endif + + ELRE_DEBUG(1, (__F, "el->el_line.buffer = :%s:\r\n", + el->el_line.buffer)); + + /* reset the Drawing cursor */ + el->el_refresh.r_cursor.h = 0; + el->el_refresh.r_cursor.v = 0; + + /* temporarily draw rprompt to calculate its size */ + prompt_print(el, EL_RPROMPT); + + /* reset the Drawing cursor */ + el->el_refresh.r_cursor.h = 0; + el->el_refresh.r_cursor.v = 0; + + cur.h = -1; /* set flag in case I'm not set */ + cur.v = 0; + + prompt_print(el, EL_PROMPT); + + /* draw the current input buffer */ +#if notyet + termsz = el->el_term.t_size.h * el->el_term.t_size.v; + if (el->el_line.lastchar - el->el_line.buffer > termsz) { + /* + * If line is longer than terminal, process only part + * of line which would influence display. + */ + size_t rem = (el->el_line.lastchar-el->el_line.buffer)%termsz; + + st = el->el_line.lastchar - rem + - (termsz - (((rem / el->el_term.t_size.v) - 1) + * el->el_term.t_size.v)); + } else +#endif + st = el->el_line.buffer; + + for (cp = st; cp < el->el_line.lastchar; cp++) { + if (cp == el->el_line.cursor) { + /* save for later */ + cur.h = el->el_refresh.r_cursor.h; + cur.v = el->el_refresh.r_cursor.v; + } + re_addc(el, (unsigned char) *cp); + } + + if (cur.h == -1) { /* if I haven't been set yet, I'm at the end */ + cur.h = el->el_refresh.r_cursor.h; + cur.v = el->el_refresh.r_cursor.v; + } + rhdiff = el->el_term.t_size.h - el->el_refresh.r_cursor.h - + el->el_rprompt.p_pos.h; + if (el->el_rprompt.p_pos.h && !el->el_rprompt.p_pos.v && + !el->el_refresh.r_cursor.v && rhdiff > 1) { + /* + * have a right-hand side prompt that will fit + * on the end of the first line with at least + * one character gap to the input buffer. + */ + while (--rhdiff > 0) /* pad out with spaces */ + re_putc(el, ' ', 1); + prompt_print(el, EL_RPROMPT); + } else { + el->el_rprompt.p_pos.h = 0; /* flag "not using rprompt" */ + el->el_rprompt.p_pos.v = 0; + } + + re_putc(el, '\0', 0); /* make line ended with NUL, no cursor shift */ + + el->el_refresh.r_newcv = el->el_refresh.r_cursor.v; + + ELRE_DEBUG(1, (__F, + "term.h=%d vcur.h=%d vcur.v=%d vdisplay[0]=\r\n:%80.80s:\r\n", + el->el_term.t_size.h, el->el_refresh.r_cursor.h, + el->el_refresh.r_cursor.v, el->el_vdisplay[0])); + + ELRE_DEBUG(1, (__F, "updating %d lines.\r\n", el->el_refresh.r_newcv)); + for (i = 0; i <= el->el_refresh.r_newcv; i++) { + /* NOTE THAT re_update_line MAY CHANGE el_display[i] */ + re_update_line(el, el->el_display[i], el->el_vdisplay[i], i); + + /* + * Copy the new line to be the current one, and pad out with + * spaces to the full width of the terminal so that if we try + * moving the cursor by writing the character that is at the + * end of the screen line, it won't be a NUL or some old + * leftover stuff. + */ + re__copy_and_pad(el->el_display[i], el->el_vdisplay[i], + (size_t) el->el_term.t_size.h); + } + ELRE_DEBUG(1, (__F, + "\r\nel->el_refresh.r_cursor.v=%d,el->el_refresh.r_oldcv=%d i=%d\r\n", + el->el_refresh.r_cursor.v, el->el_refresh.r_oldcv, i)); + + if (el->el_refresh.r_oldcv > el->el_refresh.r_newcv) + for (; i <= el->el_refresh.r_oldcv; i++) { + term_move_to_line(el, i); + term_move_to_char(el, 0); + term_clear_EOL(el, (int) strlen(el->el_display[i])); +#ifdef DEBUG_REFRESH + term_overwrite(el, "C\b", 2); +#endif /* DEBUG_REFRESH */ + el->el_display[i][0] = '\0'; + } + + el->el_refresh.r_oldcv = el->el_refresh.r_newcv; /* set for next time */ + ELRE_DEBUG(1, (__F, + "\r\ncursor.h = %d, cursor.v = %d, cur.h = %d, cur.v = %d\r\n", + el->el_refresh.r_cursor.h, el->el_refresh.r_cursor.v, + cur.h, cur.v)); + term_move_to_line(el, cur.v); /* go to where the cursor is */ + term_move_to_char(el, cur.h); +} + + +/* re_goto_bottom(): + * used to go to last used screen line + */ +protected void +re_goto_bottom(EditLine *el) +{ + + term_move_to_line(el, el->el_refresh.r_oldcv); + term__putc('\r'); + term__putc('\n'); + re_clear_display(el); + term__flush(); +} + + +/* re_insert(): + * insert num characters of s into d (in front of the character) + * at dat, maximum length of d is dlen + */ +private void +/*ARGSUSED*/ +re_insert(EditLine *el, char *d, int dat, int dlen, char *s, int num) +{ + char *a, *b; + + if (num <= 0) + return; + if (num > dlen - dat) + num = dlen - dat; + + ELRE_DEBUG(1, + (__F, "re_insert() starting: %d at %d max %d, d == \"%s\"\n", + num, dat, dlen, d)); + ELRE_DEBUG(1, (__F, "s == \"%s\"n", s)); + + /* open up the space for num chars */ + if (num > 0) { + b = d + dlen - 1; + a = b - num; + while (a >= &d[dat]) + *b-- = *a--; + d[dlen] = '\0'; /* just in case */ + } + ELRE_DEBUG(1, (__F, + "re_insert() after insert: %d at %d max %d, d == \"%s\"\n", + num, dat, dlen, d)); + ELRE_DEBUG(1, (__F, "s == \"%s\"n", s)); + + /* copy the characters */ + for (a = d + dat; (a < d + dlen) && (num > 0); num--) + *a++ = *s++; + + ELRE_DEBUG(1, + (__F, "re_insert() after copy: %d at %d max %d, %s == \"%s\"\n", + num, dat, dlen, d, s)); + ELRE_DEBUG(1, (__F, "s == \"%s\"n", s)); +} + + +/* re_delete(): + * delete num characters d at dat, maximum length of d is dlen + */ +private void +/*ARGSUSED*/ +re_delete(EditLine *el, char *d, int dat, int dlen, int num) +{ + char *a, *b; + + if (num <= 0) + return; + if (dat + num >= dlen) { + d[dat] = '\0'; + return; + } + ELRE_DEBUG(1, + (__F, "re_delete() starting: %d at %d max %d, d == \"%s\"\n", + num, dat, dlen, d)); + + /* open up the space for num chars */ + if (num > 0) { + b = d + dat; + a = b + num; + while (a < &d[dlen]) + *b++ = *a++; + d[dlen] = '\0'; /* just in case */ + } + ELRE_DEBUG(1, + (__F, "re_delete() after delete: %d at %d max %d, d == \"%s\"\n", + num, dat, dlen, d)); +} + + +/* re__strncopy(): + * Like strncpy without padding. + */ +private void +re__strncopy(char *a, char *b, size_t n) +{ + + while (n-- && *b) + *a++ = *b++; +} + + +/***************************************************************** + re_update_line() is based on finding the middle difference of each line + on the screen; vis: + + /old first difference + /beginning of line | /old last same /old EOL + v v v v +old: eddie> Oh, my little gruntle-buggy is to me, as lurgid as +new: eddie> Oh, my little buggy says to me, as lurgid as + ^ ^ ^ ^ + \beginning of line | \new last same \new end of line + \new first difference + + all are character pointers for the sake of speed. Special cases for + no differences, as well as for end of line additions must be handled. +**************************************************************** */ + +/* Minimum at which doing an insert it "worth it". This should be about + * half the "cost" of going into insert mode, inserting a character, and + * going back out. This should really be calculated from the termcap + * data... For the moment, a good number for ANSI terminals. + */ +#define MIN_END_KEEP 4 + +private void +re_update_line(EditLine *el, char *old, char *new, int i) +{ + char *o, *n, *p, c; + char *ofd, *ols, *oe, *nfd, *nls, *ne; + char *osb, *ose, *nsb, *nse; + int fx, sx; + + /* + * find first diff + */ + for (o = old, n = new; *o && (*o == *n); o++, n++) + continue; + ofd = o; + nfd = n; + + /* + * Find the end of both old and new + */ + while (*o) + o++; + /* + * Remove any trailing blanks off of the end, being careful not to + * back up past the beginning. + */ + while (ofd < o) { + if (o[-1] != ' ') + break; + o--; + } + oe = o; + *oe = '\0'; + + while (*n) + n++; + + /* remove blanks from end of new */ + while (nfd < n) { + if (n[-1] != ' ') + break; + n--; + } + ne = n; + *ne = '\0'; + + /* + * if no diff, continue to next line of redraw + */ + if (*ofd == '\0' && *nfd == '\0') { + ELRE_DEBUG(1, (__F, "no difference.\r\n")); + return; + } + /* + * find last same pointer + */ + while ((o > ofd) && (n > nfd) && (*--o == *--n)) + continue; + ols = ++o; + nls = ++n; + + /* + * find same begining and same end + */ + osb = ols; + nsb = nls; + ose = ols; + nse = nls; + + /* + * case 1: insert: scan from nfd to nls looking for *ofd + */ + if (*ofd) { + for (c = *ofd, n = nfd; n < nls; n++) { + if (c == *n) { + for (o = ofd, p = n; + p < nls && o < ols && *o == *p; + o++, p++) + continue; + /* + * if the new match is longer and it's worth + * keeping, then we take it + */ + if (((nse - nsb) < (p - n)) && + (2 * (p - n) > n - nfd)) { + nsb = n; + nse = p; + osb = ofd; + ose = o; + } + } + } + } + /* + * case 2: delete: scan from ofd to ols looking for *nfd + */ + if (*nfd) { + for (c = *nfd, o = ofd; o < ols; o++) { + if (c == *o) { + for (n = nfd, p = o; + p < ols && n < nls && *p == *n; + p++, n++) + continue; + /* + * if the new match is longer and it's worth + * keeping, then we take it + */ + if (((ose - osb) < (p - o)) && + (2 * (p - o) > o - ofd)) { + nsb = nfd; + nse = n; + osb = o; + ose = p; + } + } + } + } + /* + * Pragmatics I: If old trailing whitespace or not enough characters to + * save to be worth it, then don't save the last same info. + */ + if ((oe - ols) < MIN_END_KEEP) { + ols = oe; + nls = ne; + } + /* + * Pragmatics II: if the terminal isn't smart enough, make the data + * dumber so the smart update doesn't try anything fancy + */ + + /* + * fx is the number of characters we need to insert/delete: in the + * beginning to bring the two same begins together + */ + fx = (nsb - nfd) - (osb - ofd); + /* + * sx is the number of characters we need to insert/delete: in the + * end to bring the two same last parts together + */ + sx = (nls - nse) - (ols - ose); + + if (!EL_CAN_INSERT) { + if (fx > 0) { + osb = ols; + ose = ols; + nsb = nls; + nse = nls; + } + if (sx > 0) { + ols = oe; + nls = ne; + } + if ((ols - ofd) < (nls - nfd)) { + ols = oe; + nls = ne; + } + } + if (!EL_CAN_DELETE) { + if (fx < 0) { + osb = ols; + ose = ols; + nsb = nls; + nse = nls; + } + if (sx < 0) { + ols = oe; + nls = ne; + } + if ((ols - ofd) > (nls - nfd)) { + ols = oe; + nls = ne; + } + } + /* + * Pragmatics III: make sure the middle shifted pointers are correct if + * they don't point to anything (we may have moved ols or nls). + */ + /* if the change isn't worth it, don't bother */ + /* was: if (osb == ose) */ + if ((ose - osb) < MIN_END_KEEP) { + osb = ols; + ose = ols; + nsb = nls; + nse = nls; + } + /* + * Now that we are done with pragmatics we recompute fx, sx + */ + fx = (nsb - nfd) - (osb - ofd); + sx = (nls - nse) - (ols - ose); + + ELRE_DEBUG(1, (__F, "\n")); + ELRE_DEBUG(1, (__F, "ofd %d, osb %d, ose %d, ols %d, oe %d\n", + ofd - old, osb - old, ose - old, ols - old, oe - old)); + ELRE_DEBUG(1, (__F, "nfd %d, nsb %d, nse %d, nls %d, ne %d\n", + nfd - new, nsb - new, nse - new, nls - new, ne - new)); + ELRE_DEBUG(1, (__F, + "xxx-xxx:\"00000000001111111111222222222233333333334\"\r\n")); + ELRE_DEBUG(1, (__F, + "xxx-xxx:\"01234567890123456789012345678901234567890\"\r\n")); +#ifdef DEBUG_REFRESH + re_printstr(el, "old- oe", old, oe); + re_printstr(el, "new- ne", new, ne); + re_printstr(el, "old-ofd", old, ofd); + re_printstr(el, "new-nfd", new, nfd); + re_printstr(el, "ofd-osb", ofd, osb); + re_printstr(el, "nfd-nsb", nfd, nsb); + re_printstr(el, "osb-ose", osb, ose); + re_printstr(el, "nsb-nse", nsb, nse); + re_printstr(el, "ose-ols", ose, ols); + re_printstr(el, "nse-nls", nse, nls); + re_printstr(el, "ols- oe", ols, oe); + re_printstr(el, "nls- ne", nls, ne); +#endif /* DEBUG_REFRESH */ + + /* + * el_cursor.v to this line i MUST be in this routine so that if we + * don't have to change the line, we don't move to it. el_cursor.h to + * first diff char + */ + term_move_to_line(el, i); + + /* + * at this point we have something like this: + * + * /old /ofd /osb /ose /ols /oe + * v.....................v v..................v v........v + * eddie> Oh, my fredded gruntle-buggy is to me, as foo var lurgid as + * eddie> Oh, my fredded quiux buggy is to me, as gruntle-lurgid as + * ^.....................^ ^..................^ ^........^ + * \new \nfd \nsb \nse \nls \ne + * + * fx is the difference in length between the chars between nfd and + * nsb, and the chars between ofd and osb, and is thus the number of + * characters to delete if < 0 (new is shorter than old, as above), + * or insert (new is longer than short). + * + * sx is the same for the second differences. + */ + + /* + * if we have a net insert on the first difference, AND inserting the + * net amount ((nsb-nfd) - (osb-ofd)) won't push the last useful + * character (which is ne if nls != ne, otherwise is nse) off the edge + * of the screen (el->el_term.t_size.h) else we do the deletes first + * so that we keep everything we need to. + */ + + /* + * if the last same is the same like the end, there is no last same + * part, otherwise we want to keep the last same part set p to the + * last useful old character + */ + p = (ols != oe) ? oe : ose; + + /* + * if (There is a diffence in the beginning) && (we need to insert + * characters) && (the number of characters to insert is less than + * the term width) + * We need to do an insert! + * else if (we need to delete characters) + * We need to delete characters! + * else + * No insert or delete + */ + if ((nsb != nfd) && fx > 0 && + ((p - old) + fx <= el->el_term.t_size.h)) { + ELRE_DEBUG(1, + (__F, "first diff insert at %d...\r\n", nfd - new)); + /* + * Move to the first char to insert, where the first diff is. + */ + term_move_to_char(el, nfd - new); + /* + * Check if we have stuff to keep at end + */ + if (nsb != ne) { + ELRE_DEBUG(1, (__F, "with stuff to keep at end\r\n")); + /* + * insert fx chars of new starting at nfd + */ + if (fx > 0) { + ELRE_DEBUG(!EL_CAN_INSERT, (__F, + "ERROR: cannot insert in early first diff\n")); + term_insertwrite(el, nfd, fx); + re_insert(el, old, ofd - old, + el->el_term.t_size.h, nfd, fx); + } + /* + * write (nsb-nfd) - fx chars of new starting at + * (nfd + fx) + */ + term_overwrite(el, nfd + fx, (nsb - nfd) - fx); + re__strncopy(ofd + fx, nfd + fx, + (size_t) ((nsb - nfd) - fx)); + } else { + ELRE_DEBUG(1, (__F, "without anything to save\r\n")); + term_overwrite(el, nfd, (nsb - nfd)); + re__strncopy(ofd, nfd, (size_t) (nsb - nfd)); + /* + * Done + */ + return; + } + } else if (fx < 0) { + ELRE_DEBUG(1, + (__F, "first diff delete at %d...\r\n", ofd - old)); + /* + * move to the first char to delete where the first diff is + */ + term_move_to_char(el, ofd - old); + /* + * Check if we have stuff to save + */ + if (osb != oe) { + ELRE_DEBUG(1, (__F, "with stuff to save at end\r\n")); + /* + * fx is less than zero *always* here but we check + * for code symmetry + */ + if (fx < 0) { + ELRE_DEBUG(!EL_CAN_DELETE, (__F, + "ERROR: cannot delete in first diff\n")); + term_deletechars(el, -fx); + re_delete(el, old, ofd - old, + el->el_term.t_size.h, -fx); + } + /* + * write (nsb-nfd) chars of new starting at nfd + */ + term_overwrite(el, nfd, (nsb - nfd)); + re__strncopy(ofd, nfd, (size_t) (nsb - nfd)); + + } else { + ELRE_DEBUG(1, (__F, + "but with nothing left to save\r\n")); + /* + * write (nsb-nfd) chars of new starting at nfd + */ + term_overwrite(el, nfd, (nsb - nfd)); + ELRE_DEBUG(1, (__F, + "cleareol %d\n", (oe - old) - (ne - new))); + term_clear_EOL(el, (oe - old) - (ne - new)); + /* + * Done + */ + return; + } + } else + fx = 0; + + if (sx < 0 && (ose - old) + fx < el->el_term.t_size.h) { + ELRE_DEBUG(1, (__F, + "second diff delete at %d...\r\n", (ose - old) + fx)); + /* + * Check if we have stuff to delete + */ + /* + * fx is the number of characters inserted (+) or deleted (-) + */ + + term_move_to_char(el, (ose - old) + fx); + /* + * Check if we have stuff to save + */ + if (ols != oe) { + ELRE_DEBUG(1, (__F, "with stuff to save at end\r\n")); + /* + * Again a duplicate test. + */ + if (sx < 0) { + ELRE_DEBUG(!EL_CAN_DELETE, (__F, + "ERROR: cannot delete in second diff\n")); + term_deletechars(el, -sx); + } + /* + * write (nls-nse) chars of new starting at nse + */ + term_overwrite(el, nse, (nls - nse)); + } else { + ELRE_DEBUG(1, (__F, + "but with nothing left to save\r\n")); + term_overwrite(el, nse, (nls - nse)); + ELRE_DEBUG(1, (__F, + "cleareol %d\n", (oe - old) - (ne - new))); + if ((oe - old) - (ne - new) != 0) + term_clear_EOL(el, (oe - old) - (ne - new)); + } + } + /* + * if we have a first insert AND WE HAVEN'T ALREADY DONE IT... + */ + if ((nsb != nfd) && (osb - ofd) <= (nsb - nfd) && (fx == 0)) { + ELRE_DEBUG(1, (__F, "late first diff insert at %d...\r\n", + nfd - new)); + + term_move_to_char(el, nfd - new); + /* + * Check if we have stuff to keep at the end + */ + if (nsb != ne) { + ELRE_DEBUG(1, (__F, "with stuff to keep at end\r\n")); + /* + * We have to recalculate fx here because we set it + * to zero above as a flag saying that we hadn't done + * an early first insert. + */ + fx = (nsb - nfd) - (osb - ofd); + if (fx > 0) { + /* + * insert fx chars of new starting at nfd + */ + ELRE_DEBUG(!EL_CAN_INSERT, (__F, + "ERROR: cannot insert in late first diff\n")); + term_insertwrite(el, nfd, fx); + re_insert(el, old, ofd - old, + el->el_term.t_size.h, nfd, fx); + } + /* + * write (nsb-nfd) - fx chars of new starting at + * (nfd + fx) + */ + term_overwrite(el, nfd + fx, (nsb - nfd) - fx); + re__strncopy(ofd + fx, nfd + fx, + (size_t) ((nsb - nfd) - fx)); + } else { + ELRE_DEBUG(1, (__F, "without anything to save\r\n")); + term_overwrite(el, nfd, (nsb - nfd)); + re__strncopy(ofd, nfd, (size_t) (nsb - nfd)); + } + } + /* + * line is now NEW up to nse + */ + if (sx >= 0) { + ELRE_DEBUG(1, (__F, + "second diff insert at %d...\r\n", nse - new)); + term_move_to_char(el, nse - new); + if (ols != oe) { + ELRE_DEBUG(1, (__F, "with stuff to keep at end\r\n")); + if (sx > 0) { + /* insert sx chars of new starting at nse */ + ELRE_DEBUG(!EL_CAN_INSERT, (__F, + "ERROR: cannot insert in second diff\n")); + term_insertwrite(el, nse, sx); + } + /* + * write (nls-nse) - sx chars of new starting at + * (nse + sx) + */ + term_overwrite(el, nse + sx, (nls - nse) - sx); + } else { + ELRE_DEBUG(1, (__F, "without anything to save\r\n")); + term_overwrite(el, nse, (nls - nse)); + + /* + * No need to do a clear-to-end here because we were + * doing a second insert, so we will have over + * written all of the old string. + */ + } + } + ELRE_DEBUG(1, (__F, "done.\r\n")); +} + + +/* re__copy_and_pad(): + * Copy string and pad with spaces + */ +private void +re__copy_and_pad(char *dst, const char *src, size_t width) +{ + int i; + + for (i = 0; i < width; i++) { + if (*src == '\0') + break; + *dst++ = *src++; + } + + for (; i < width; i++) + *dst++ = ' '; + + *dst = '\0'; +} + + +/* re_refresh_cursor(): + * Move to the new cursor position + */ +protected void +re_refresh_cursor(EditLine *el) +{ + char *cp, c; + int h, v, th; + + /* first we must find where the cursor is... */ + h = el->el_prompt.p_pos.h; + v = el->el_prompt.p_pos.v; + th = el->el_term.t_size.h; /* optimize for speed */ + + /* do input buffer to el->el_line.cursor */ + for (cp = el->el_line.buffer; cp < el->el_line.cursor; cp++) { + c = *cp; + h++; /* all chars at least this long */ + + if (c == '\n') {/* handle newline in data part too */ + h = 0; + v++; + } else { + if (c == '\t') { /* if a tab, to next tab stop */ + while (h & 07) { + h++; + } + } else if (iscntrl((unsigned char) c)) { + /* if control char */ + h++; + if (h > th) { /* if overflow, compensate */ + h = 1; + v++; + } + } else if (!isprint((unsigned char) c)) { + h += 3; + if (h > th) { /* if overflow, compensate */ + h = h - th; + v++; + } + } + } + + if (h >= th) { /* check, extra long tabs picked up here also */ + h = 0; + v++; + } + } + + /* now go there */ + term_move_to_line(el, v); + term_move_to_char(el, h); + term__flush(); +} + + +/* re_fastputc(): + * Add a character fast. + */ +private void +re_fastputc(EditLine *el, int c) +{ + + term__putc(c); + el->el_display[el->el_cursor.v][el->el_cursor.h++] = c; + if (el->el_cursor.h >= el->el_term.t_size.h) { + /* if we must overflow */ + el->el_cursor.h = 0; + + /* + * If we would overflow (input is longer than terminal size), + * emulate scroll by dropping first line and shuffling the rest. + * We do this via pointer shuffling - it's safe in this case + * and we avoid memcpy(). + */ + if (el->el_cursor.v + 1 >= el->el_term.t_size.v) { + int i, lins = el->el_term.t_size.v; + char *firstline = el->el_display[0]; + + for(i=1; i < lins; i++) + el->el_display[i-1] = el->el_display[i]; + + re__copy_and_pad(firstline, "", 0); + el->el_display[i-1] = firstline; + } else { + el->el_cursor.v++; + el->el_refresh.r_oldcv++; + } + if (EL_HAS_AUTO_MARGINS) { + if (EL_HAS_MAGIC_MARGINS) { + term__putc(' '); + term__putc('\b'); + } + } else { + term__putc('\r'); + term__putc('\n'); + } + } +} + + +/* re_fastaddc(): + * we added just one char, handle it fast. + * Assumes that screen cursor == real cursor + */ +protected void +re_fastaddc(EditLine *el) +{ + char c; + int rhdiff; + + c = el->el_line.cursor[-1]; + + if (c == '\t' || el->el_line.cursor != el->el_line.lastchar) { + re_refresh(el); /* too hard to handle */ + return; + } + rhdiff = el->el_term.t_size.h - el->el_cursor.h - + el->el_rprompt.p_pos.h; + if (el->el_rprompt.p_pos.h && rhdiff < 3) { + re_refresh(el); /* clear out rprompt if less than 1 char gap */ + return; + } /* else (only do at end of line, no TAB) */ + if (iscntrl((unsigned char) c)) { /* if control char, do caret */ + char mc = (c == '\177') ? '?' : (c | 0100); + re_fastputc(el, '^'); + re_fastputc(el, mc); + } else if (isprint((unsigned char) c)) { /* normal char */ + re_fastputc(el, c); + } else { + re_fastputc(el, '\\'); + re_fastputc(el, (int) ((((unsigned int) c >> 6) & 7) + '0')); + re_fastputc(el, (int) ((((unsigned int) c >> 3) & 7) + '0')); + re_fastputc(el, (c & 7) + '0'); + } + term__flush(); +} + + +/* re_clear_display(): + * clear the screen buffers so that new new prompt starts fresh. + */ +protected void +re_clear_display(EditLine *el) +{ + int i; + + el->el_cursor.v = 0; + el->el_cursor.h = 0; + for (i = 0; i < el->el_term.t_size.v; i++) + el->el_display[i][0] = '\0'; + el->el_refresh.r_oldcv = 0; +} + + +/* re_clear_lines(): + * Make sure all lines are *really* blank + */ +protected void +re_clear_lines(EditLine *el) +{ + + if (EL_CAN_CEOL) { + int i; + term_move_to_char(el, 0); + for (i = 0; i <= el->el_refresh.r_oldcv; i++) { + /* for each line on the screen */ + term_move_to_line(el, i); + term_clear_EOL(el, el->el_term.t_size.h); + } + term_move_to_line(el, 0); + } else { + term_move_to_line(el, el->el_refresh.r_oldcv); + /* go to last line */ + term__putc('\r'); /* go to BOL */ + term__putc('\n'); /* go to new line */ + } +} diff --git a/main/editline/refresh.h b/main/editline/refresh.h new file mode 100644 index 000000000..33c0887c1 --- /dev/null +++ b/main/editline/refresh.h @@ -0,0 +1,63 @@ +/* $NetBSD: refresh.h,v 1.4 2001/01/10 07:45:42 jdolecek Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)refresh.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.refresh.h: Screen refresh functions + */ +#ifndef _h_el_refresh +#define _h_el_refresh + +#include "histedit.h" + +typedef struct { + coord_t r_cursor; /* Refresh cursor position */ + int r_oldcv; /* Vertical locations */ + int r_newcv; +} el_refresh_t; + +protected void re_putc(EditLine *, int, int); +protected void re_clear_lines(EditLine *); +protected void re_clear_display(EditLine *); +protected void re_refresh(EditLine *); +protected void re_refresh_cursor(EditLine *); +protected void re_fastaddc(EditLine *); +protected void re_goto_bottom(EditLine *); + +#endif /* _h_el_refresh */ diff --git a/main/editline/search.c b/main/editline/search.c new file mode 100644 index 000000000..7c1cb84ef --- /dev/null +++ b/main/editline/search.c @@ -0,0 +1,649 @@ +/* $NetBSD: search.c,v 1.12 2002/03/18 16:00:58 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)search.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: search.c,v 1.12 2002/03/18 16:00:58 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * search.c: History and character search functions + */ +#include +#if defined(REGEX) +#include +#elif defined(REGEXP) +#include +#endif +#include "el.h" + +/* + * Adjust cursor in vi mode to include the character under it + */ +#define EL_CURSOR(el) \ + ((el)->el_line.cursor + (((el)->el_map.type == MAP_VI) && \ + ((el)->el_map.current == (el)->el_map.alt))) + +/* search_init(): + * Initialize the search stuff + */ +protected int +search_init(EditLine *el) +{ + + el->el_search.patbuf = (char *) el_malloc(EL_BUFSIZ); + if (el->el_search.patbuf == NULL) + return (-1); + el->el_search.patlen = 0; + el->el_search.patdir = -1; + el->el_search.chacha = '\0'; + el->el_search.chadir = -1; + return (0); +} + + +/* search_end(): + * Initialize the search stuff + */ +protected void +search_end(EditLine *el) +{ + + el_free((ptr_t) el->el_search.patbuf); + el->el_search.patbuf = NULL; +} + + +#ifdef REGEXP +/* regerror(): + * Handle regular expression errors + */ +public void +/*ARGSUSED*/ +regerror(const char *msg) +{ +} +#endif + + +/* el_match(): + * Return if string matches pattern + */ +protected int +el_match(const char *str, const char *pat) +{ +#if defined (REGEX) + regex_t re; + int rv; +#elif defined (REGEXP) + regexp *rp; + int rv; +#else + extern char *re_comp(const char *); + extern int re_exec(const char *); +#endif + + if (strstr(str, pat) != NULL) + return (1); + +#if defined(REGEX) + if (regcomp(&re, pat, 0) == 0) { + rv = regexec(&re, str, 0, NULL, 0) == 0; + regfree(&re); + } else { + rv = 0; + } + return (rv); +#elif defined(REGEXP) + if ((re = regcomp(pat)) != NULL) { + rv = regexec(re, str); + free((ptr_t) re); + } else { + rv = 0; + } + return (rv); +#else + if (re_comp(pat) != NULL) + return (0); + else + return (re_exec(str) == 1); +#endif +} + + +/* c_hmatch(): + * return True if the pattern matches the prefix + */ +protected int +c_hmatch(EditLine *el, const char *str) +{ +#ifdef SDEBUG + (void) fprintf(el->el_errfile, "match `%s' with `%s'\n", + el->el_search.patbuf, str); +#endif /* SDEBUG */ + + return (el_match(str, el->el_search.patbuf)); +} + + +/* c_setpat(): + * Set the history seatch pattern + */ +protected void +c_setpat(EditLine *el) +{ + if (el->el_state.lastcmd != ED_SEARCH_PREV_HISTORY && + el->el_state.lastcmd != ED_SEARCH_NEXT_HISTORY) { + el->el_search.patlen = EL_CURSOR(el) - el->el_line.buffer; + if (el->el_search.patlen >= EL_BUFSIZ) + el->el_search.patlen = EL_BUFSIZ - 1; + if (el->el_search.patlen != 0) { + (void) strncpy(el->el_search.patbuf, el->el_line.buffer, + el->el_search.patlen); + el->el_search.patbuf[el->el_search.patlen] = '\0'; + } else + el->el_search.patlen = strlen(el->el_search.patbuf); + } +#ifdef SDEBUG + (void) fprintf(el->el_errfile, "\neventno = %d\n", + el->el_history.eventno); + (void) fprintf(el->el_errfile, "patlen = %d\n", el->el_search.patlen); + (void) fprintf(el->el_errfile, "patbuf = \"%s\"\n", + el->el_search.patbuf); + (void) fprintf(el->el_errfile, "cursor %d lastchar %d\n", + EL_CURSOR(el) - el->el_line.buffer, + el->el_line.lastchar - el->el_line.buffer); +#endif +} + + +/* ce_inc_search(): + * Emacs incremental search + */ +protected el_action_t +ce_inc_search(EditLine *el, int dir) +{ + static const char STRfwd[] = {'f', 'w', 'd', '\0'}, + STRbck[] = {'b', 'c', 'k', '\0'}; + static char pchar = ':';/* ':' = normal, '?' = failed */ + static char endcmd[2] = {'\0', '\0'}; + char ch, *ocursor = el->el_line.cursor, oldpchar = pchar; + const char *cp; + + el_action_t ret = CC_NORM; + + int ohisteventno = el->el_history.eventno; + int oldpatlen = el->el_search.patlen; + int newdir = dir; + int done, redo; + + if (el->el_line.lastchar + sizeof(STRfwd) / sizeof(char) + 2 + + el->el_search.patlen >= el->el_line.limit) + return (CC_ERROR); + + for (;;) { + + if (el->el_search.patlen == 0) { /* first round */ + pchar = ':'; +#ifdef ANCHOR + el->el_search.patbuf[el->el_search.patlen++] = '.'; + el->el_search.patbuf[el->el_search.patlen++] = '*'; +#endif + } + done = redo = 0; + *el->el_line.lastchar++ = '\n'; + for (cp = (newdir == ED_SEARCH_PREV_HISTORY) ? STRbck : STRfwd; + *cp; *el->el_line.lastchar++ = *cp++) + continue; + *el->el_line.lastchar++ = pchar; + for (cp = &el->el_search.patbuf[1]; + cp < &el->el_search.patbuf[el->el_search.patlen]; + *el->el_line.lastchar++ = *cp++) + continue; + *el->el_line.lastchar = '\0'; + re_refresh(el); + + if (el_getc(el, &ch) != 1) + return (ed_end_of_file(el, 0)); + + switch (el->el_map.current[(unsigned char) ch]) { + case ED_INSERT: + case ED_DIGIT: + if (el->el_search.patlen > EL_BUFSIZ - 3) + term_beep(el); + else { + el->el_search.patbuf[el->el_search.patlen++] = + ch; + *el->el_line.lastchar++ = ch; + *el->el_line.lastchar = '\0'; + re_refresh(el); + } + break; + + case EM_INC_SEARCH_NEXT: + newdir = ED_SEARCH_NEXT_HISTORY; + redo++; + break; + + case EM_INC_SEARCH_PREV: + newdir = ED_SEARCH_PREV_HISTORY; + redo++; + break; + + case ED_DELETE_PREV_CHAR: + if (el->el_search.patlen > 1) + done++; + else + term_beep(el); + break; + + default: + switch (ch) { + case 0007: /* ^G: Abort */ + ret = CC_ERROR; + done++; + break; + + case 0027: /* ^W: Append word */ + /* No can do if globbing characters in pattern */ + for (cp = &el->el_search.patbuf[1];; cp++) + if (cp >= &el->el_search.patbuf[el->el_search.patlen]) { + el->el_line.cursor += + el->el_search.patlen - 1; + cp = c__next_word(el->el_line.cursor, + el->el_line.lastchar, 1, + ce__isword); + while (el->el_line.cursor < cp && + *el->el_line.cursor != '\n') { + if (el->el_search.patlen > + EL_BUFSIZ - 3) { + term_beep(el); + break; + } + el->el_search.patbuf[el->el_search.patlen++] = + *el->el_line.cursor; + *el->el_line.lastchar++ = + *el->el_line.cursor++; + } + el->el_line.cursor = ocursor; + *el->el_line.lastchar = '\0'; + re_refresh(el); + break; + } else if (isglob(*cp)) { + term_beep(el); + break; + } + break; + + default: /* Terminate and execute cmd */ + endcmd[0] = ch; + el_push(el, endcmd); + /* FALLTHROUGH */ + + case 0033: /* ESC: Terminate */ + ret = CC_REFRESH; + done++; + break; + } + break; + } + + while (el->el_line.lastchar > el->el_line.buffer && + *el->el_line.lastchar != '\n') + *el->el_line.lastchar-- = '\0'; + *el->el_line.lastchar = '\0'; + + if (!done) { + + /* Can't search if unmatched '[' */ + for (cp = &el->el_search.patbuf[el->el_search.patlen-1], + ch = ']'; + cp > el->el_search.patbuf; + cp--) + if (*cp == '[' || *cp == ']') { + ch = *cp; + break; + } + if (el->el_search.patlen > 1 && ch != '[') { + if (redo && newdir == dir) { + if (pchar == '?') { /* wrap around */ + el->el_history.eventno = + newdir == ED_SEARCH_PREV_HISTORY ? 0 : 0x7fffffff; + if (hist_get(el) == CC_ERROR) + /* el->el_history.event + * no was fixed by + * first call */ + (void) hist_get(el); + el->el_line.cursor = newdir == + ED_SEARCH_PREV_HISTORY ? + el->el_line.lastchar : + el->el_line.buffer; + } else + el->el_line.cursor += + newdir == + ED_SEARCH_PREV_HISTORY ? + -1 : 1; + } +#ifdef ANCHOR + el->el_search.patbuf[el->el_search.patlen++] = + '.'; + el->el_search.patbuf[el->el_search.patlen++] = + '*'; +#endif + el->el_search.patbuf[el->el_search.patlen] = + '\0'; + if (el->el_line.cursor < el->el_line.buffer || + el->el_line.cursor > el->el_line.lastchar || + (ret = ce_search_line(el, + &el->el_search.patbuf[1], + newdir)) == CC_ERROR) { + /* avoid c_setpat */ + el->el_state.lastcmd = + (el_action_t) newdir; + ret = newdir == ED_SEARCH_PREV_HISTORY ? + ed_search_prev_history(el, 0) : + ed_search_next_history(el, 0); + if (ret != CC_ERROR) { + el->el_line.cursor = newdir == + ED_SEARCH_PREV_HISTORY ? + el->el_line.lastchar : + el->el_line.buffer; + (void) ce_search_line(el, + &el->el_search.patbuf[1], + newdir); + } + } + el->el_search.patbuf[--el->el_search.patlen] = + '\0'; + if (ret == CC_ERROR) { + term_beep(el); + if (el->el_history.eventno != + ohisteventno) { + el->el_history.eventno = + ohisteventno; + if (hist_get(el) == CC_ERROR) + return (CC_ERROR); + } + el->el_line.cursor = ocursor; + pchar = '?'; + } else { + pchar = ':'; + } + } + ret = ce_inc_search(el, newdir); + + if (ret == CC_ERROR && pchar == '?' && oldpchar == ':') + /* + * break abort of failed search at last + * non-failed + */ + ret = CC_NORM; + + } + if (ret == CC_NORM || (ret == CC_ERROR && oldpatlen == 0)) { + /* restore on normal return or error exit */ + pchar = oldpchar; + el->el_search.patlen = oldpatlen; + if (el->el_history.eventno != ohisteventno) { + el->el_history.eventno = ohisteventno; + if (hist_get(el) == CC_ERROR) + return (CC_ERROR); + } + el->el_line.cursor = ocursor; + if (ret == CC_ERROR) + re_refresh(el); + } + if (done || ret != CC_NORM) + return (ret); + } +} + + +/* cv_search(): + * Vi search. + */ +protected el_action_t +cv_search(EditLine *el, int dir) +{ + char ch; + char tmpbuf[EL_BUFSIZ]; + int tmplen; + + tmplen = 0; +#ifdef ANCHOR + tmpbuf[tmplen++] = '.'; + tmpbuf[tmplen++] = '*'; +#endif + + el->el_line.buffer[0] = '\0'; + el->el_line.lastchar = el->el_line.buffer; + el->el_line.cursor = el->el_line.buffer; + el->el_search.patdir = dir; + + c_insert(el, 2); /* prompt + '\n' */ + *el->el_line.cursor++ = '\n'; + *el->el_line.cursor++ = dir == ED_SEARCH_PREV_HISTORY ? '/' : '?'; + re_refresh(el); + +#ifdef ANCHOR +#define LEN 2 +#else +#define LEN 0 +#endif + + tmplen = c_gets(el, &tmpbuf[LEN]) + LEN; + ch = tmpbuf[tmplen]; + tmpbuf[tmplen] = '\0'; + + if (tmplen == LEN) { + /* + * Use the old pattern, but wild-card it. + */ + if (el->el_search.patlen == 0) { + el->el_line.buffer[0] = '\0'; + el->el_line.lastchar = el->el_line.buffer; + el->el_line.cursor = el->el_line.buffer; + re_refresh(el); + return (CC_ERROR); + } +#ifdef ANCHOR + if (el->el_search.patbuf[0] != '.' && + el->el_search.patbuf[0] != '*') { + (void) strncpy(tmpbuf, el->el_search.patbuf, + sizeof(tmpbuf) - 1); + el->el_search.patbuf[0] = '.'; + el->el_search.patbuf[1] = '*'; + (void) strncpy(&el->el_search.patbuf[2], tmpbuf, + EL_BUFSIZ - 3); + el->el_search.patlen++; + el->el_search.patbuf[el->el_search.patlen++] = '.'; + el->el_search.patbuf[el->el_search.patlen++] = '*'; + el->el_search.patbuf[el->el_search.patlen] = '\0'; + } +#endif + } else { +#ifdef ANCHOR + tmpbuf[tmplen++] = '.'; + tmpbuf[tmplen++] = '*'; +#endif + tmpbuf[tmplen] = '\0'; + (void) strncpy(el->el_search.patbuf, tmpbuf, EL_BUFSIZ - 1); + el->el_search.patlen = tmplen; + } + el->el_state.lastcmd = (el_action_t) dir; /* avoid c_setpat */ + el->el_line.cursor = el->el_line.lastchar = el->el_line.buffer; + if ((dir == ED_SEARCH_PREV_HISTORY ? ed_search_prev_history(el, 0) : + ed_search_next_history(el, 0)) == CC_ERROR) { + re_refresh(el); + return (CC_ERROR); + } else { + if (ch == 0033) { + re_refresh(el); + *el->el_line.lastchar++ = '\n'; + *el->el_line.lastchar = '\0'; + re_goto_bottom(el); + return (CC_NEWLINE); + } else + return (CC_REFRESH); + } +} + + +/* ce_search_line(): + * Look for a pattern inside a line + */ +protected el_action_t +ce_search_line(EditLine *el, char *pattern, int dir) +{ + char *cp; + + if (dir == ED_SEARCH_PREV_HISTORY) { + for (cp = el->el_line.cursor; cp >= el->el_line.buffer; cp--) + if (el_match(cp, pattern)) { + el->el_line.cursor = cp; + return (CC_NORM); + } + return (CC_ERROR); + } else { + for (cp = el->el_line.cursor; *cp != '\0' && + cp < el->el_line.limit; cp++) + if (el_match(cp, pattern)) { + el->el_line.cursor = cp; + return (CC_NORM); + } + return (CC_ERROR); + } +} + + +/* cv_repeat_srch(): + * Vi repeat search + */ +protected el_action_t +cv_repeat_srch(EditLine *el, int c) +{ + +#ifdef SDEBUG + (void) fprintf(el->el_errfile, "dir %d patlen %d patbuf %s\n", + c, el->el_search.patlen, el->el_search.patbuf); +#endif + + el->el_state.lastcmd = (el_action_t) c; /* Hack to stop c_setpat */ + el->el_line.lastchar = el->el_line.buffer; + + switch (c) { + case ED_SEARCH_NEXT_HISTORY: + return (ed_search_next_history(el, 0)); + case ED_SEARCH_PREV_HISTORY: + return (ed_search_prev_history(el, 0)); + default: + return (CC_ERROR); + } +} + + +/* cv_csearch_back(): + * Vi character search reverse + */ +protected el_action_t +cv_csearch_back(EditLine *el, int ch, int count, int tflag) +{ + char *cp; + + cp = el->el_line.cursor; + while (count--) { + if (*cp == ch) + cp--; + while (cp > el->el_line.buffer && *cp != ch) + cp--; + } + + if (cp < el->el_line.buffer || (cp == el->el_line.buffer && *cp != ch)) + return (CC_ERROR); + + if (*cp == ch && tflag) + cp++; + + el->el_line.cursor = cp; + + if (el->el_chared.c_vcmd.action & DELETE) { + el->el_line.cursor++; + cv_delfini(el); + return (CC_REFRESH); + } + re_refresh_cursor(el); + return (CC_NORM); +} + + +/* cv_csearch_fwd(): + * Vi character search forward + */ +protected el_action_t +cv_csearch_fwd(EditLine *el, int ch, int count, int tflag) +{ + char *cp; + + cp = el->el_line.cursor; + while (count--) { + if (*cp == ch) + cp++; + while (cp < el->el_line.lastchar && *cp != ch) + cp++; + } + + if (cp >= el->el_line.lastchar) + return (CC_ERROR); + + if (*cp == ch && tflag) + cp--; + + el->el_line.cursor = cp; + + if (el->el_chared.c_vcmd.action & DELETE) { + el->el_line.cursor++; + cv_delfini(el); + return (CC_REFRESH); + } + re_refresh_cursor(el); + return (CC_NORM); +} diff --git a/main/editline/search.h b/main/editline/search.h new file mode 100644 index 000000000..676bbe2e3 --- /dev/null +++ b/main/editline/search.h @@ -0,0 +1,70 @@ +/* $NetBSD: search.h,v 1.5 2000/09/04 22:06:32 lukem Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)search.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.search.h: Line and history searching utilities + */ +#ifndef _h_el_search +#define _h_el_search + +#include "histedit.h" + +typedef struct el_search_t { + char *patbuf; /* The pattern buffer */ + size_t patlen; /* Length of the pattern buffer */ + int patdir; /* Direction of the last search */ + int chadir; /* Character search direction */ + char chacha; /* Character we are looking for */ +} el_search_t; + + +protected int el_match(const char *, const char *); +protected int search_init(EditLine *); +protected void search_end(EditLine *); +protected int c_hmatch(EditLine *, const char *); +protected void c_setpat(EditLine *); +protected el_action_t ce_inc_search(EditLine *, int); +protected el_action_t cv_search(EditLine *, int); +protected el_action_t ce_search_line(EditLine *, char *, int); +protected el_action_t cv_repeat_srch(EditLine *, int); +protected el_action_t cv_csearch_back(EditLine *, int, int, int); +protected el_action_t cv_csearch_fwd(EditLine *, int, int, int); + +#endif /* _h_el_search */ diff --git a/main/editline/sig.c b/main/editline/sig.c new file mode 100644 index 000000000..0acba1247 --- /dev/null +++ b/main/editline/sig.c @@ -0,0 +1,198 @@ +/* $NetBSD: sig.c,v 1.9 2002/03/18 16:00:58 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)sig.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: sig.c,v 1.9 2002/03/18 16:00:58 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * sig.c: Signal handling stuff. + * our policy is to trap all signals, set a good state + * and pass the ball to our caller. + */ +#include "el.h" +#include + +private EditLine *sel = NULL; + +private const int sighdl[] = { +#define _DO(a) (a), + ALLSIGS +#undef _DO + - 1 +}; + +private void sig_handler(int); + +/* sig_handler(): + * This is the handler called for all signals + * XXX: we cannot pass any data so we just store the old editline + * state in a private variable + */ +private void +sig_handler(int signo) +{ + int i; + sigset_t nset, oset; + + (void) sigemptyset(&nset); + (void) sigaddset(&nset, signo); + (void) sigprocmask(SIG_BLOCK, &nset, &oset); + + switch (signo) { + case SIGCONT: + tty_rawmode(sel); + if (ed_redisplay(sel, 0) == CC_REFRESH) + re_refresh(sel); + term__flush(); + break; + + case SIGWINCH: + el_resize(sel); + break; + + default: + tty_cookedmode(sel); + break; + } + + for (i = 0; sighdl[i] != -1; i++) + if (signo == sighdl[i]) + break; + + (void) signal(signo, sel->el_signal[i]); + (void) sigprocmask(SIG_SETMASK, &oset, NULL); + (void) kill(0, signo); +} + + +/* sig_init(): + * Initialize all signal stuff + */ +protected int +sig_init(EditLine *el) +{ + int i; + sigset_t nset, oset; + + (void) sigemptyset(&nset); +#define _DO(a) (void) sigaddset(&nset, a); + ALLSIGS +#undef _DO + (void) sigprocmask(SIG_BLOCK, &nset, &oset); + +#define SIGSIZE (sizeof(sighdl) / sizeof(sighdl[0]) * sizeof(sig_t)) + + el->el_signal = (sig_t *) el_malloc(SIGSIZE); + if (el->el_signal == NULL) + return (-1); + for (i = 0; sighdl[i] != -1; i++) + el->el_signal[i] = SIG_ERR; + + (void) sigprocmask(SIG_SETMASK, &oset, NULL); + + return (0); +} + + +/* sig_end(): + * Clear all signal stuff + */ +protected void +sig_end(EditLine *el) +{ + + el_free((ptr_t) el->el_signal); + el->el_signal = NULL; +} + + +/* sig_set(): + * set all the signal handlers + */ +protected void +sig_set(EditLine *el) +{ + int i; + sigset_t nset, oset; + + (void) sigemptyset(&nset); +#define _DO(a) (void) sigaddset(&nset, a); + ALLSIGS +#undef _DO + (void) sigprocmask(SIG_BLOCK, &nset, &oset); + + for (i = 0; sighdl[i] != -1; i++) { + sig_t s; + /* This could happen if we get interrupted */ + if ((s = signal(sighdl[i], sig_handler)) != sig_handler) + el->el_signal[i] = s; + } + sel = el; + (void) sigprocmask(SIG_SETMASK, &oset, NULL); +} + + +/* sig_clr(): + * clear all the signal handlers + */ +protected void +sig_clr(EditLine *el) +{ + int i; + sigset_t nset, oset; + + (void) sigemptyset(&nset); +#define _DO(a) (void) sigaddset(&nset, a); + ALLSIGS +#undef _DO + (void) sigprocmask(SIG_BLOCK, &nset, &oset); + + for (i = 0; sighdl[i] != -1; i++) + if (el->el_signal[i] != SIG_ERR) + (void) signal(sighdl[i], el->el_signal[i]); + + sel = NULL; /* we are going to die if the handler is + * called */ + (void) sigprocmask(SIG_SETMASK, &oset, NULL); +} diff --git a/main/editline/sig.h b/main/editline/sig.h new file mode 100644 index 000000000..e7231b65c --- /dev/null +++ b/main/editline/sig.h @@ -0,0 +1,72 @@ +/* $NetBSD: sig.h,v 1.3 2000/09/04 22:06:32 lukem Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)sig.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.sig.h: Signal handling functions + */ +#ifndef _h_el_sig +#define _h_el_sig + +#include + +#include "histedit.h" + +/* + * Define here all the signals we are going to handle + * The _DO macro is used to iterate in the source code + */ +#define ALLSIGS \ + _DO(SIGINT) \ + _DO(SIGTSTP) \ + _DO(SIGSTOP) \ + _DO(SIGQUIT) \ + _DO(SIGHUP) \ + _DO(SIGTERM) \ + _DO(SIGCONT) \ + _DO(SIGWINCH) + +typedef sig_t *el_signal_t; + +protected void sig_end(EditLine*); +protected int sig_init(EditLine*); +protected void sig_set(EditLine*); +protected void sig_clr(EditLine*); + +#endif /* _h_el_sig */ diff --git a/main/editline/sys.h b/main/editline/sys.h new file mode 100644 index 000000000..a306d1df7 --- /dev/null +++ b/main/editline/sys.h @@ -0,0 +1,133 @@ +/* $NetBSD: sys.h,v 1.5 2002/03/18 16:00:59 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)sys.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * sys.h: Put all the stupid compiler and system dependencies here... + */ +#ifndef _h_sys +#define _h_sys + +#ifndef public +# define public /* Externally visible functions/variables */ +#endif + +#ifndef private +# define private static /* Always hidden internals */ +#endif + +#ifndef protected +# define protected /* Redefined from elsewhere to "static" */ + /* When we want to hide everything */ +#endif + +#ifndef _PTR_T +# define _PTR_T +typedef void *ptr_t; +#endif + +#ifndef _IOCTL_T +# define _IOCTL_T +typedef void *ioctl_t; +#endif + +#include + +#ifndef HAVE_STRLCAT +#define strlcat libedit_strlcat +size_t strlcat(char *dst, const char *src, size_t size); +#endif + +#ifndef HAVE_STRLCPY +#define strlcpy libedit_strlcpy +size_t strlcpy(char *dst, const char *src, size_t size); +#endif + +#ifndef HAVE_FGETLN +#define fgetln libedit_fgetln +char *fgetln(FILE *fp, size_t *len); +#endif + +#define REGEX /* Use POSIX.2 regular expression functions */ +#undef REGEXP /* Use UNIX V8 regular expression functions */ + +#ifdef SUNOS +# undef REGEX +# undef REGEXP +# include +typedef void (*sig_t)(int); +# ifdef __GNUC__ +/* + * Broken hdrs. + */ +#ifndef SOLARIS +extern int tgetent(const char *bp, char *name); +extern int tgetflag(const char *id); +extern int tgetnum(const char *id); +extern char *tgetstr(const char *id, char **area); +#endif +extern char *tgoto(const char *cap, int col, int row); +extern int tputs(const char *str, int affcnt, int (*putc)(int)); +extern char *getenv(const char *); +extern int fprintf(FILE *, const char *, ...); +extern int sigsetmask(int); +extern int sigblock(int); +extern int fputc(int, FILE *); +extern int fgetc(FILE *); +extern int fflush(FILE *); +extern int tolower(int); +extern int toupper(int); +extern int errno, sys_nerr; +extern char *sys_errlist[]; +extern void perror(const char *); +# include +# define strerror(e) sys_errlist[e] +# endif +# ifdef SABER +extern ptr_t memcpy(ptr_t, const ptr_t, size_t); +extern ptr_t memset(ptr_t, int, size_t); +# endif +extern char *fgetline(FILE *, int *); +#endif + +#ifdef HAVE_SYS_CDEFS_H +#include +#endif + +#endif /* _h_sys */ diff --git a/main/editline/term.c b/main/editline/term.c new file mode 100644 index 000000000..fb627cabb --- /dev/null +++ b/main/editline/term.c @@ -0,0 +1,1587 @@ +/* $NetBSD: term.c,v 1.35 2002/03/18 16:00:59 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)term.c 8.2 (Berkeley) 4/30/95"; +#else +__RCSID("$NetBSD: term.c,v 1.35 2002/03/18 16:00:59 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * term.c: Editor/termcap-curses interface + * We have to declare a static variable here, since the + * termcap putchar routine does not take an argument! + */ +#include +#include +#include +#include +#include +#ifdef HAVE_TERMCAP_H +#include +#endif +#ifdef HAVE_CURSES_H +#include +#endif +#ifdef HAVE_NCURSES_H +#include +#endif +#if defined(HAVE_TERM_H) +#include "term.h" +/* Can not use /usr/include/term.h because of a lot of incompatibilities, so just define some prototypes */ +extern int tgetent(char *, const char *); +extern int tgetflag(const char *); +extern int tgetnum(const char *); +extern char *tgetstr(const char *, char **); +extern int tputs (const char *, int, int (*)(int)); +extern char *tgoto (const char *, int, int); +#endif /* defined(HAVE_TERM_H) */ +#include +#include + +#include "el.h" + +/* + * IMPORTANT NOTE: these routines are allowed to look at the current screen + * and the current possition assuming that it is correct. If this is not + * true, then the update will be WRONG! This is (should be) a valid + * assumption... + */ + +#define TC_BUFSIZE 2048 + +#define GoodStr(a) (el->el_term.t_str[a] != NULL && \ + el->el_term.t_str[a][0] != '\0') +#define Str(a) el->el_term.t_str[a] +#define Val(a) el->el_term.t_val[a] + +#ifdef notdef +private const struct { + const char *b_name; + int b_rate; +} baud_rate[] = { +#ifdef B0 + { "0", B0 }, +#endif +#ifdef B50 + { "50", B50 }, +#endif +#ifdef B75 + { "75", B75 }, +#endif +#ifdef B110 + { "110", B110 }, +#endif +#ifdef B134 + { "134", B134 }, +#endif +#ifdef B150 + { "150", B150 }, +#endif +#ifdef B200 + { "200", B200 }, +#endif +#ifdef B300 + { "300", B300 }, +#endif +#ifdef B600 + { "600", B600 }, +#endif +#ifdef B900 + { "900", B900 }, +#endif +#ifdef B1200 + { "1200", B1200 }, +#endif +#ifdef B1800 + { "1800", B1800 }, +#endif +#ifdef B2400 + { "2400", B2400 }, +#endif +#ifdef B3600 + { "3600", B3600 }, +#endif +#ifdef B4800 + { "4800", B4800 }, +#endif +#ifdef B7200 + { "7200", B7200 }, +#endif +#ifdef B9600 + { "9600", B9600 }, +#endif +#ifdef EXTA + { "19200", EXTA }, +#endif +#ifdef B19200 + { "19200", B19200 }, +#endif +#ifdef EXTB + { "38400", EXTB }, +#endif +#ifdef B38400 + { "38400", B38400 }, +#endif + { NULL, 0 } +}; +#endif + +private const struct termcapstr { + const char *name; + const char *long_name; +} tstr[] = { +#define T_al 0 + { "al", "add new blank line" }, +#define T_bl 1 + { "bl", "audible bell" }, +#define T_cd 2 + { "cd", "clear to bottom" }, +#define T_ce 3 + { "ce", "clear to end of line" }, +#define T_ch 4 + { "ch", "cursor to horiz pos" }, +#define T_cl 5 + { "cl", "clear screen" }, +#define T_dc 6 + { "dc", "delete a character" }, +#define T_dl 7 + { "dl", "delete a line" }, +#define T_dm 8 + { "dm", "start delete mode" }, +#define T_ed 9 + { "ed", "end delete mode" }, +#define T_ei 10 + { "ei", "end insert mode" }, +#define T_fs 11 + { "fs", "cursor from status line" }, +#define T_ho 12 + { "ho", "home cursor" }, +#define T_ic 13 + { "ic", "insert character" }, +#define T_im 14 + { "im", "start insert mode" }, +#define T_ip 15 + { "ip", "insert padding" }, +#define T_kd 16 + { "kd", "sends cursor down" }, +#define T_kl 17 + { "kl", "sends cursor left" }, +#define T_kr 18 + { "kr", "sends cursor right" }, +#define T_ku 19 + { "ku", "sends cursor up" }, +#define T_md 20 + { "md", "begin bold" }, +#define T_me 21 + { "me", "end attributes" }, +#define T_nd 22 + { "nd", "non destructive space" }, +#define T_se 23 + { "se", "end standout" }, +#define T_so 24 + { "so", "begin standout" }, +#define T_ts 25 + { "ts", "cursor to status line" }, +#define T_up 26 + { "up", "cursor up one" }, +#define T_us 27 + { "us", "begin underline" }, +#define T_ue 28 + { "ue", "end underline" }, +#define T_vb 29 + { "vb", "visible bell" }, +#define T_DC 30 + { "DC", "delete multiple chars" }, +#define T_DO 31 + { "DO", "cursor down multiple" }, +#define T_IC 32 + { "IC", "insert multiple chars" }, +#define T_LE 33 + { "LE", "cursor left multiple" }, +#define T_RI 34 + { "RI", "cursor right multiple" }, +#define T_UP 35 + { "UP", "cursor up multiple" }, +#define T_kh 36 + { "kh", "send cursor home" }, +#define T_at7 37 + { "@7", "send cursor end" }, +#define T_str 38 + { NULL, NULL } +}; + +private const struct termcapval { + const char *name; + const char *long_name; +} tval[] = { +#define T_am 0 + { "am", "has automatic margins" }, +#define T_pt 1 + { "pt", "has physical tabs" }, +#define T_li 2 + { "li", "Number of lines" }, +#define T_co 3 + { "co", "Number of columns" }, +#define T_km 4 + { "km", "Has meta key" }, +#define T_xt 5 + { "xt", "Tab chars destructive" }, +#define T_xn 6 + { "xn", "newline ignored at right margin" }, +#define T_MT 7 + { "MT", "Has meta key" }, /* XXX? */ +#define T_val 8 + { NULL, NULL, } +}; +/* do two or more of the attributes use me */ + +private void term_setflags(EditLine *); +private int term_rebuffer_display(EditLine *); +private void term_free_display(EditLine *); +private int term_alloc_display(EditLine *); +private void term_alloc(EditLine *, const struct termcapstr *, const char *); +private void term_init_arrow(EditLine *); +private void term_reset_arrow(EditLine *); + + +private FILE *term_outfile = NULL; /* XXX: How do we fix that? */ + + +/* term_setflags(): + * Set the terminal capability flags + */ +private void +term_setflags(EditLine *el) +{ + EL_FLAGS = 0; + if (el->el_tty.t_tabs) + EL_FLAGS |= (Val(T_pt) && !Val(T_xt)) ? TERM_CAN_TAB : 0; + + EL_FLAGS |= (Val(T_km) || Val(T_MT)) ? TERM_HAS_META : 0; + EL_FLAGS |= GoodStr(T_ce) ? TERM_CAN_CEOL : 0; + EL_FLAGS |= (GoodStr(T_dc) || GoodStr(T_DC)) ? TERM_CAN_DELETE : 0; + EL_FLAGS |= (GoodStr(T_im) || GoodStr(T_ic) || GoodStr(T_IC)) ? + TERM_CAN_INSERT : 0; + EL_FLAGS |= (GoodStr(T_up) || GoodStr(T_UP)) ? TERM_CAN_UP : 0; + EL_FLAGS |= Val(T_am) ? TERM_HAS_AUTO_MARGINS : 0; + EL_FLAGS |= Val(T_xn) ? TERM_HAS_MAGIC_MARGINS : 0; + + if (GoodStr(T_me) && GoodStr(T_ue)) + EL_FLAGS |= (strcmp(Str(T_me), Str(T_ue)) == 0) ? + TERM_CAN_ME : 0; + else + EL_FLAGS &= ~TERM_CAN_ME; + if (GoodStr(T_me) && GoodStr(T_se)) + EL_FLAGS |= (strcmp(Str(T_me), Str(T_se)) == 0) ? + TERM_CAN_ME : 0; + + +#ifdef DEBUG_SCREEN + if (!EL_CAN_UP) { + (void) fprintf(el->el_errfile, + "WARNING: Your terminal cannot move up.\n"); + (void) fprintf(el->el_errfile, + "Editing may be odd for long lines.\n"); + } + if (!EL_CAN_CEOL) + (void) fprintf(el->el_errfile, "no clear EOL capability.\n"); + if (!EL_CAN_DELETE) + (void) fprintf(el->el_errfile, "no delete char capability.\n"); + if (!EL_CAN_INSERT) + (void) fprintf(el->el_errfile, "no insert char capability.\n"); +#endif /* DEBUG_SCREEN */ +} + + +/* term_init(): + * Initialize the terminal stuff + */ +protected int +term_init(EditLine *el) +{ + + el->el_term.t_buf = (char *) el_malloc(TC_BUFSIZE); + if (el->el_term.t_buf == NULL) + return (-1); + el->el_term.t_cap = (char *) el_malloc(TC_BUFSIZE); + if (el->el_term.t_cap == NULL) + return (-1); + el->el_term.t_fkey = (fkey_t *) el_malloc(A_K_NKEYS * sizeof(fkey_t)); + if (el->el_term.t_fkey == NULL) + return (-1); + el->el_term.t_loc = 0; + el->el_term.t_str = (char **) el_malloc(T_str * sizeof(char *)); + if (el->el_term.t_str == NULL) + return (-1); + (void) memset(el->el_term.t_str, 0, T_str * sizeof(char *)); + el->el_term.t_val = (int *) el_malloc(T_val * sizeof(int)); + if (el->el_term.t_val == NULL) + return (-1); + (void) memset(el->el_term.t_val, 0, T_val * sizeof(int)); + term_outfile = el->el_outfile; + (void) term_set(el, NULL); + term_init_arrow(el); + return (0); +} +/* term_end(): + * Clean up the terminal stuff + */ +protected void +term_end(EditLine *el) +{ + + el_free((ptr_t) el->el_term.t_buf); + el->el_term.t_buf = NULL; + el_free((ptr_t) el->el_term.t_cap); + el->el_term.t_cap = NULL; + el_free((ptr_t) el->el_term.t_fkey); + el->el_term.t_fkey = NULL; + el->el_term.t_loc = 0; + el_free((ptr_t) el->el_term.t_str); + el->el_term.t_str = NULL; + el_free((ptr_t) el->el_term.t_val); + el->el_term.t_val = NULL; + term_free_display(el); +} + + +/* term_alloc(): + * Maintain a string pool for termcap strings + */ +private void +term_alloc(EditLine *el, const struct termcapstr *t, const char *cap) +{ + char termbuf[TC_BUFSIZE]; + int tlen, clen; + char **tlist = el->el_term.t_str; + char **tmp, **str = &tlist[t - tstr]; + + if (cap == NULL || *cap == '\0') { + *str = NULL; + return; + } else + clen = strlen(cap); + + tlen = *str == NULL ? 0 : strlen(*str); + + /* + * New string is shorter; no need to allocate space + */ + if (clen <= tlen) { + (void) strcpy(*str, cap); /* XXX strcpy is safe */ + return; + } + /* + * New string is longer; see if we have enough space to append + */ + if (el->el_term.t_loc + 3 < TC_BUFSIZE) { + /* XXX strcpy is safe */ + (void) strcpy(*str = &el->el_term.t_buf[el->el_term.t_loc], + cap); + el->el_term.t_loc += clen + 1; /* one for \0 */ + return; + } + /* + * Compact our buffer; no need to check compaction, cause we know it + * fits... + */ + tlen = 0; + for (tmp = tlist; tmp < &tlist[T_str]; tmp++) + if (*tmp != NULL && *tmp != '\0' && *tmp != *str) { + char *ptr; + + for (ptr = *tmp; *ptr != '\0'; termbuf[tlen++] = *ptr++) + continue; + termbuf[tlen++] = '\0'; + } + memcpy(el->el_term.t_buf, termbuf, TC_BUFSIZE); + el->el_term.t_loc = tlen; + if (el->el_term.t_loc + 3 >= TC_BUFSIZE) { + (void) fprintf(el->el_errfile, + "Out of termcap string space.\n"); + return; + } + /* XXX strcpy is safe */ + (void) strcpy(*str = &el->el_term.t_buf[el->el_term.t_loc], cap); + el->el_term.t_loc += clen + 1; /* one for \0 */ + return; +} + + +/* term_rebuffer_display(): + * Rebuffer the display after the screen changed size + */ +private int +term_rebuffer_display(EditLine *el) +{ + coord_t *c = &el->el_term.t_size; + + term_free_display(el); + + c->h = Val(T_co); + c->v = Val(T_li); + + if (term_alloc_display(el) == -1) + return (-1); + return (0); +} + + +/* term_alloc_display(): + * Allocate a new display. + */ +private int +term_alloc_display(EditLine *el) +{ + int i; + char **b; + coord_t *c = &el->el_term.t_size; + + b = (char **) el_malloc((size_t) (sizeof(char *) * (c->v + 1))); + if (b == NULL) + return (-1); + for (i = 0; i < c->v; i++) { + b[i] = (char *) el_malloc((size_t) (sizeof(char) * (c->h + 1))); + if (b[i] == NULL) + return (-1); + } + b[c->v] = NULL; + el->el_display = b; + + b = (char **) el_malloc((size_t) (sizeof(char *) * (c->v + 1))); + if (b == NULL) + return (-1); + for (i = 0; i < c->v; i++) { + b[i] = (char *) el_malloc((size_t) (sizeof(char) * (c->h + 1))); + if (b[i] == NULL) + return (-1); + } + b[c->v] = NULL; + el->el_vdisplay = b; + return (0); +} + + +/* term_free_display(): + * Free the display buffers + */ +private void +term_free_display(EditLine *el) +{ + char **b; + char **bufp; + + b = el->el_display; + el->el_display = NULL; + if (b != NULL) { + for (bufp = b; *bufp != NULL; bufp++) + el_free((ptr_t) * bufp); + el_free((ptr_t) b); + } + b = el->el_vdisplay; + el->el_vdisplay = NULL; + if (b != NULL) { + for (bufp = b; *bufp != NULL; bufp++) + el_free((ptr_t) * bufp); + el_free((ptr_t) b); + } +} + + +/* term_move_to_line(): + * move to line (first line == 0) + * as efficiently as possible + */ +protected void +term_move_to_line(EditLine *el, int where) +{ + int del; + + if (where == el->el_cursor.v) + return; + + if (where > el->el_term.t_size.v) { +#ifdef DEBUG_SCREEN + (void) fprintf(el->el_errfile, + "term_move_to_line: where is ridiculous: %d\r\n", where); +#endif /* DEBUG_SCREEN */ + return; + } + if ((del = where - el->el_cursor.v) > 0) { + while (del > 0) { + if (EL_HAS_AUTO_MARGINS && + el->el_display[el->el_cursor.v][0] != '\0') { + /* move without newline */ + term_move_to_char(el, el->el_term.t_size.h - 1); + term_overwrite(el, + &el->el_display[el->el_cursor.v][el->el_cursor.h], + 1); + /* updates Cursor */ + del--; + } else { + if ((del > 1) && GoodStr(T_DO)) { + (void) tputs(tgoto(Str(T_DO), del, del), + del, term__putc); + del = 0; + } else { + for (; del > 0; del--) + term__putc('\n'); + /* because the \n will become \r\n */ + el->el_cursor.h = 0; + } + } + } + } else { /* del < 0 */ + if (GoodStr(T_UP) && (-del > 1 || !GoodStr(T_up))) + (void) tputs(tgoto(Str(T_UP), -del, -del), -del, + term__putc); + else { + if (GoodStr(T_up)) + for (; del < 0; del++) + (void) tputs(Str(T_up), 1, term__putc); + } + } + el->el_cursor.v = where;/* now where is here */ +} + + +/* term_move_to_char(): + * Move to the character position specified + */ +protected void +term_move_to_char(EditLine *el, int where) +{ + int del, i; + +mc_again: + if (where == el->el_cursor.h) + return; + + if (where > el->el_term.t_size.h) { +#ifdef DEBUG_SCREEN + (void) fprintf(el->el_errfile, + "term_move_to_char: where is riduculous: %d\r\n", where); +#endif /* DEBUG_SCREEN */ + return; + } + if (!where) { /* if where is first column */ + term__putc('\r'); /* do a CR */ + el->el_cursor.h = 0; + return; + } + del = where - el->el_cursor.h; + + if ((del < -4 || del > 4) && GoodStr(T_ch)) + /* go there directly */ + (void) tputs(tgoto(Str(T_ch), where, where), where, term__putc); + else { + if (del > 0) { /* moving forward */ + if ((del > 4) && GoodStr(T_RI)) + (void) tputs(tgoto(Str(T_RI), del, del), + del, term__putc); + else { + /* if I can do tabs, use them */ + if (EL_CAN_TAB) { + if ((el->el_cursor.h & 0370) != + (where & 0370)) { + /* if not within tab stop */ + for (i = + (el->el_cursor.h & 0370); + i < (where & 0370); + i += 8) + term__putc('\t'); + /* then tab over */ + el->el_cursor.h = where & 0370; + } + } + /* + * it's usually cheaper to just write the + * chars, so we do. + */ + /* + * NOTE THAT term_overwrite() WILL CHANGE + * el->el_cursor.h!!! + */ + term_overwrite(el, + &el->el_display[el->el_cursor.v][el->el_cursor.h], + where - el->el_cursor.h); + + } + } else { /* del < 0 := moving backward */ + if ((-del > 4) && GoodStr(T_LE)) + (void) tputs(tgoto(Str(T_LE), -del, -del), + -del, term__putc); + else { /* can't go directly there */ + /* + * if the "cost" is greater than the "cost" + * from col 0 + */ + if (EL_CAN_TAB ? + (-del > (((unsigned int) where >> 3) + + (where & 07))) + : (-del > where)) { + term__putc('\r'); /* do a CR */ + el->el_cursor.h = 0; + goto mc_again; /* and try again */ + } + for (i = 0; i < -del; i++) + term__putc('\b'); + } + } + } + el->el_cursor.h = where; /* now where is here */ +} + + +/* term_overwrite(): + * Overstrike num characters + */ +protected void +term_overwrite(EditLine *el, const char *cp, int n) +{ + if (n <= 0) + return; /* catch bugs */ + + if (n > el->el_term.t_size.h) { +#ifdef DEBUG_SCREEN + (void) fprintf(el->el_errfile, + "term_overwrite: n is riduculous: %d\r\n", n); +#endif /* DEBUG_SCREEN */ + return; + } + do { + term__putc(*cp++); + el->el_cursor.h++; + } while (--n); + + if (el->el_cursor.h >= el->el_term.t_size.h) { /* wrap? */ + if (EL_HAS_AUTO_MARGINS) { /* yes */ + el->el_cursor.h = 0; + el->el_cursor.v++; + if (EL_HAS_MAGIC_MARGINS) { + /* force the wrap to avoid the "magic" + * situation */ + char c; + if ((c = el->el_display[el->el_cursor.v][el->el_cursor.h]) + != '\0') + term_overwrite(el, &c, 1); + else + term__putc(' '); + el->el_cursor.h = 1; + } + } else /* no wrap, but cursor stays on screen */ + el->el_cursor.h = el->el_term.t_size.h; + } +} + + +/* term_deletechars(): + * Delete num characters + */ +protected void +term_deletechars(EditLine *el, int num) +{ + if (num <= 0) + return; + + if (!EL_CAN_DELETE) { +#ifdef DEBUG_EDIT + (void) fprintf(el->el_errfile, " ERROR: cannot delete \n"); +#endif /* DEBUG_EDIT */ + return; + } + if (num > el->el_term.t_size.h) { +#ifdef DEBUG_SCREEN + (void) fprintf(el->el_errfile, + "term_deletechars: num is riduculous: %d\r\n", num); +#endif /* DEBUG_SCREEN */ + return; + } + if (GoodStr(T_DC)) /* if I have multiple delete */ + if ((num > 1) || !GoodStr(T_dc)) { /* if dc would be more + * expen. */ + (void) tputs(tgoto(Str(T_DC), num, num), + num, term__putc); + return; + } + if (GoodStr(T_dm)) /* if I have delete mode */ + (void) tputs(Str(T_dm), 1, term__putc); + + if (GoodStr(T_dc)) /* else do one at a time */ + while (num--) + (void) tputs(Str(T_dc), 1, term__putc); + + if (GoodStr(T_ed)) /* if I have delete mode */ + (void) tputs(Str(T_ed), 1, term__putc); +} + + +/* term_insertwrite(): + * Puts terminal in insert character mode or inserts num + * characters in the line + */ +protected void +term_insertwrite(EditLine *el, char *cp, int num) +{ + if (num <= 0) + return; + if (!EL_CAN_INSERT) { +#ifdef DEBUG_EDIT + (void) fprintf(el->el_errfile, " ERROR: cannot insert \n"); +#endif /* DEBUG_EDIT */ + return; + } + if (num > el->el_term.t_size.h) { +#ifdef DEBUG_SCREEN + (void) fprintf(el->el_errfile, + "StartInsert: num is riduculous: %d\r\n", num); +#endif /* DEBUG_SCREEN */ + return; + } + if (GoodStr(T_IC)) /* if I have multiple insert */ + if ((num > 1) || !GoodStr(T_ic)) { + /* if ic would be more expensive */ + (void) tputs(tgoto(Str(T_IC), num, num), + num, term__putc); + term_overwrite(el, cp, num); + /* this updates el_cursor.h */ + return; + } + if (GoodStr(T_im) && GoodStr(T_ei)) { /* if I have insert mode */ + (void) tputs(Str(T_im), 1, term__putc); + + el->el_cursor.h += num; + do + term__putc(*cp++); + while (--num); + + if (GoodStr(T_ip)) /* have to make num chars insert */ + (void) tputs(Str(T_ip), 1, term__putc); + + (void) tputs(Str(T_ei), 1, term__putc); + return; + } + do { + if (GoodStr(T_ic)) /* have to make num chars insert */ + (void) tputs(Str(T_ic), 1, term__putc); + /* insert a char */ + + term__putc(*cp++); + + el->el_cursor.h++; + + if (GoodStr(T_ip)) /* have to make num chars insert */ + (void) tputs(Str(T_ip), 1, term__putc); + /* pad the inserted char */ + + } while (--num); +} + + +/* term_clear_EOL(): + * clear to end of line. There are num characters to clear + */ +protected void +term_clear_EOL(EditLine *el, int num) +{ + int i; + + if (EL_CAN_CEOL && GoodStr(T_ce)) + (void) tputs(Str(T_ce), 1, term__putc); + else { + for (i = 0; i < num; i++) + term__putc(' '); + el->el_cursor.h += num; /* have written num spaces */ + } +} + + +/* term_clear_screen(): + * Clear the screen + */ +protected void +term_clear_screen(EditLine *el) +{ /* clear the whole screen and home */ + + if (GoodStr(T_cl)) + /* send the clear screen code */ + (void) tputs(Str(T_cl), Val(T_li), term__putc); + else if (GoodStr(T_ho) && GoodStr(T_cd)) { + (void) tputs(Str(T_ho), Val(T_li), term__putc); /* home */ + /* clear to bottom of screen */ + (void) tputs(Str(T_cd), Val(T_li), term__putc); + } else { + term__putc('\r'); + term__putc('\n'); + } +} + + +/* term_beep(): + * Beep the way the terminal wants us + */ +protected void +term_beep(EditLine *el) +{ + if (GoodStr(T_bl)) + /* what termcap says we should use */ + (void) tputs(Str(T_bl), 1, term__putc); + else + term__putc('\007'); /* an ASCII bell; ^G */ +} + + +#ifdef notdef +/* term_clear_to_bottom(): + * Clear to the bottom of the screen + */ +protected void +term_clear_to_bottom(EditLine *el) +{ + if (GoodStr(T_cd)) + (void) tputs(Str(T_cd), Val(T_li), term__putc); + else if (GoodStr(T_ce)) + (void) tputs(Str(T_ce), Val(T_li), term__putc); +} +#endif + + +/* term_set(): + * Read in the terminal capabilities from the requested terminal + */ +protected int +term_set(EditLine *el, const char *term) +{ + int i; + char buf[TC_BUFSIZE]; + char *area; + const struct termcapstr *t; + sigset_t oset, nset; + int lins, cols; + + (void) sigemptyset(&nset); + (void) sigaddset(&nset, SIGWINCH); + (void) sigprocmask(SIG_BLOCK, &nset, &oset); + + area = buf; + + + if (term == NULL) + term = getenv("TERM"); + + if (!term || !term[0]) + term = "dumb"; + + if (strcmp(term, "emacs") == 0) + el->el_flags |= EDIT_DISABLED; + + memset(el->el_term.t_cap, 0, TC_BUFSIZE); + + i = tgetent(el->el_term.t_cap, term); + + if (i <= 0) { + if (i == -1) + (void) fprintf(el->el_errfile, + "Cannot read termcap database;\n"); + else if (i == 0) + (void) fprintf(el->el_errfile, + "No entry for terminal type \"%s\";\n", term); + (void) fprintf(el->el_errfile, + "using dumb terminal settings.\n"); + Val(T_co) = 80; /* do a dumb terminal */ + Val(T_pt) = Val(T_km) = Val(T_li) = 0; + Val(T_xt) = Val(T_MT); + for (t = tstr; t->name != NULL; t++) + term_alloc(el, t, NULL); + } else { + /* auto/magic margins */ + Val(T_am) = tgetflag("am"); + Val(T_xn) = tgetflag("xn"); + /* Can we tab */ + Val(T_pt) = tgetflag("pt"); + Val(T_xt) = tgetflag("xt"); + /* do we have a meta? */ + Val(T_km) = tgetflag("km"); + Val(T_MT) = tgetflag("MT"); + /* Get the size */ + Val(T_co) = tgetnum("co"); + Val(T_li) = tgetnum("li"); + for (t = tstr; t->name != NULL; t++) + term_alloc(el, t, tgetstr((char *)t->name, &area)); + } + + if (Val(T_co) < 2) + Val(T_co) = 80; /* just in case */ + if (Val(T_li) < 1) + Val(T_li) = 24; + + el->el_term.t_size.v = Val(T_co); + el->el_term.t_size.h = Val(T_li); + + term_setflags(el); + + /* get the correct window size */ + (void) term_get_size(el, &lins, &cols); + if (term_change_size(el, lins, cols) == -1) + return (-1); + (void) sigprocmask(SIG_SETMASK, &oset, NULL); + term_bind_arrow(el); + return (i <= 0 ? -1 : 0); +} + + +/* term_get_size(): + * Return the new window size in lines and cols, and + * true if the size was changed. + */ +protected int +term_get_size(EditLine *el, int *lins, int *cols) +{ + + *cols = Val(T_co); + *lins = Val(T_li); + +#ifdef TIOCGWINSZ + { + struct winsize ws; + if (ioctl(el->el_infd, TIOCGWINSZ, (ioctl_t) & ws) != -1) { + if (ws.ws_col) + *cols = ws.ws_col; + if (ws.ws_row) + *lins = ws.ws_row; + } + } +#endif +#ifdef TIOCGSIZE + { + struct ttysize ts; + if (ioctl(el->el_infd, TIOCGSIZE, (ioctl_t) & ts) != -1) { + if (ts.ts_cols) + *cols = ts.ts_cols; + if (ts.ts_lines) + *lins = ts.ts_lines; + } + } +#endif + return (Val(T_co) != *cols || Val(T_li) != *lins); +} + + +/* term_change_size(): + * Change the size of the terminal + */ +protected int +term_change_size(EditLine *el, int lins, int cols) +{ + /* + * Just in case + */ + Val(T_co) = (cols < 2) ? 80 : cols; + Val(T_li) = (lins < 1) ? 24 : lins; + + /* re-make display buffers */ + if (term_rebuffer_display(el) == -1) + return (-1); + re_clear_display(el); + return (0); +} + + +/* term_init_arrow(): + * Initialize the arrow key bindings from termcap + */ +private void +term_init_arrow(EditLine *el) +{ + fkey_t *arrow = el->el_term.t_fkey; + + arrow[A_K_DN].name = "down"; + arrow[A_K_DN].key = T_kd; + arrow[A_K_DN].fun.cmd = ED_NEXT_HISTORY; + arrow[A_K_DN].type = XK_CMD; + + arrow[A_K_UP].name = "up"; + arrow[A_K_UP].key = T_ku; + arrow[A_K_UP].fun.cmd = ED_PREV_HISTORY; + arrow[A_K_UP].type = XK_CMD; + + arrow[A_K_LT].name = "left"; + arrow[A_K_LT].key = T_kl; + arrow[A_K_LT].fun.cmd = ED_PREV_CHAR; + arrow[A_K_LT].type = XK_CMD; + + arrow[A_K_RT].name = "right"; + arrow[A_K_RT].key = T_kr; + arrow[A_K_RT].fun.cmd = ED_NEXT_CHAR; + arrow[A_K_RT].type = XK_CMD; + + arrow[A_K_HO].name = "home"; + arrow[A_K_HO].key = T_kh; + arrow[A_K_HO].fun.cmd = ED_MOVE_TO_BEG; + arrow[A_K_HO].type = XK_CMD; + + arrow[A_K_EN].name = "end"; + arrow[A_K_EN].key = T_at7; + arrow[A_K_EN].fun.cmd = ED_MOVE_TO_END; + arrow[A_K_EN].type = XK_CMD; +} + + +/* term_reset_arrow(): + * Reset arrow key bindings + */ +private void +term_reset_arrow(EditLine *el) +{ + fkey_t *arrow = el->el_term.t_fkey; + static const char strA[] = {033, '[', 'A', '\0'}; + static const char strB[] = {033, '[', 'B', '\0'}; + static const char strC[] = {033, '[', 'C', '\0'}; + static const char strD[] = {033, '[', 'D', '\0'}; + static const char strH[] = {033, '[', 'H', '\0'}; + static const char strF[] = {033, '[', 'F', '\0'}; + static const char stOA[] = {033, 'O', 'A', '\0'}; + static const char stOB[] = {033, 'O', 'B', '\0'}; + static const char stOC[] = {033, 'O', 'C', '\0'}; + static const char stOD[] = {033, 'O', 'D', '\0'}; + static const char stOH[] = {033, 'O', 'H', '\0'}; + static const char stOF[] = {033, 'O', 'F', '\0'}; + + key_add(el, strA, &arrow[A_K_UP].fun, arrow[A_K_UP].type); + key_add(el, strB, &arrow[A_K_DN].fun, arrow[A_K_DN].type); + key_add(el, strC, &arrow[A_K_RT].fun, arrow[A_K_RT].type); + key_add(el, strD, &arrow[A_K_LT].fun, arrow[A_K_LT].type); + key_add(el, strH, &arrow[A_K_HO].fun, arrow[A_K_HO].type); + key_add(el, strF, &arrow[A_K_EN].fun, arrow[A_K_EN].type); + key_add(el, stOA, &arrow[A_K_UP].fun, arrow[A_K_UP].type); + key_add(el, stOB, &arrow[A_K_DN].fun, arrow[A_K_DN].type); + key_add(el, stOC, &arrow[A_K_RT].fun, arrow[A_K_RT].type); + key_add(el, stOD, &arrow[A_K_LT].fun, arrow[A_K_LT].type); + key_add(el, stOH, &arrow[A_K_HO].fun, arrow[A_K_HO].type); + key_add(el, stOF, &arrow[A_K_EN].fun, arrow[A_K_EN].type); + + if (el->el_map.type == MAP_VI) { + key_add(el, &strA[1], &arrow[A_K_UP].fun, arrow[A_K_UP].type); + key_add(el, &strB[1], &arrow[A_K_DN].fun, arrow[A_K_DN].type); + key_add(el, &strC[1], &arrow[A_K_RT].fun, arrow[A_K_RT].type); + key_add(el, &strD[1], &arrow[A_K_LT].fun, arrow[A_K_LT].type); + key_add(el, &strH[1], &arrow[A_K_HO].fun, arrow[A_K_HO].type); + key_add(el, &strF[1], &arrow[A_K_EN].fun, arrow[A_K_EN].type); + key_add(el, &stOA[1], &arrow[A_K_UP].fun, arrow[A_K_UP].type); + key_add(el, &stOB[1], &arrow[A_K_DN].fun, arrow[A_K_DN].type); + key_add(el, &stOC[1], &arrow[A_K_RT].fun, arrow[A_K_RT].type); + key_add(el, &stOD[1], &arrow[A_K_LT].fun, arrow[A_K_LT].type); + key_add(el, &stOH[1], &arrow[A_K_HO].fun, arrow[A_K_HO].type); + key_add(el, &stOF[1], &arrow[A_K_EN].fun, arrow[A_K_EN].type); + } +} + + +/* term_set_arrow(): + * Set an arrow key binding + */ +protected int +term_set_arrow(EditLine *el, const char *name, key_value_t *fun, int type) +{ + fkey_t *arrow = el->el_term.t_fkey; + int i; + + for (i = 0; i < A_K_NKEYS; i++) + if (strcmp(name, arrow[i].name) == 0) { + arrow[i].fun = *fun; + arrow[i].type = type; + return (0); + } + return (-1); +} + + +/* term_clear_arrow(): + * Clear an arrow key binding + */ +protected int +term_clear_arrow(EditLine *el, const char *name) +{ + fkey_t *arrow = el->el_term.t_fkey; + int i; + + for (i = 0; i < A_K_NKEYS; i++) + if (strcmp(name, arrow[i].name) == 0) { + arrow[i].type = XK_NOD; + return (0); + } + return (-1); +} + + +/* term_print_arrow(): + * Print the arrow key bindings + */ +protected void +term_print_arrow(EditLine *el, const char *name) +{ + int i; + fkey_t *arrow = el->el_term.t_fkey; + + for (i = 0; i < A_K_NKEYS; i++) + if (*name == '\0' || strcmp(name, arrow[i].name) == 0) + if (arrow[i].type != XK_NOD) + key_kprint(el, arrow[i].name, &arrow[i].fun, + arrow[i].type); +} + + +/* term_bind_arrow(): + * Bind the arrow keys + */ +protected void +term_bind_arrow(EditLine *el) +{ + el_action_t *map; + const el_action_t *dmap; + int i, j; + char *p; + fkey_t *arrow = el->el_term.t_fkey; + + /* Check if the components needed are initialized */ + if (el->el_term.t_buf == NULL || el->el_map.key == NULL) + return; + + map = el->el_map.type == MAP_VI ? el->el_map.alt : el->el_map.key; + dmap = el->el_map.type == MAP_VI ? el->el_map.vic : el->el_map.emacs; + + term_reset_arrow(el); + + for (i = 0; i < A_K_NKEYS; i++) { + p = el->el_term.t_str[arrow[i].key]; + if (p && *p) { + j = (unsigned char) *p; + /* + * Assign the arrow keys only if: + * + * 1. They are multi-character arrow keys and the user + * has not re-assigned the leading character, or + * has re-assigned the leading character to be + * ED_SEQUENCE_LEAD_IN + * 2. They are single arrow keys pointing to an + * unassigned key. + */ + if (arrow[i].type == XK_NOD) + key_clear(el, map, p); + else { + if (p[1] && (dmap[j] == map[j] || + map[j] == ED_SEQUENCE_LEAD_IN)) { + key_add(el, p, &arrow[i].fun, + arrow[i].type); + map[j] = ED_SEQUENCE_LEAD_IN; + } else if (map[j] == ED_UNASSIGNED) { + key_clear(el, map, p); + if (arrow[i].type == XK_CMD) + map[j] = arrow[i].fun.cmd; + else + key_add(el, p, &arrow[i].fun, + arrow[i].type); + } + } + } + } +} + + +/* term__putc(): + * Add a character + */ +protected int +term__putc(int c) +{ + + return (fputc(c, term_outfile)); +} + + +/* term__flush(): + * Flush output + */ +protected void +term__flush(void) +{ + + (void) fflush(term_outfile); +} + + +/* term_telltc(): + * Print the current termcap characteristics + */ +protected int +/*ARGSUSED*/ +term_telltc(EditLine *el, int argc, const char **argv) +{ + const struct termcapstr *t; + char **ts; + char upbuf[EL_BUFSIZ]; + + (void) fprintf(el->el_outfile, "\n\tYour terminal has the\n"); + (void) fprintf(el->el_outfile, "\tfollowing characteristics:\n\n"); + (void) fprintf(el->el_outfile, "\tIt has %d columns and %d lines\n", + Val(T_co), Val(T_li)); + (void) fprintf(el->el_outfile, + "\tIt has %s meta key\n", EL_HAS_META ? "a" : "no"); + (void) fprintf(el->el_outfile, + "\tIt can%suse tabs\n", EL_CAN_TAB ? " " : "not "); + (void) fprintf(el->el_outfile, "\tIt %s automatic margins\n", + EL_HAS_AUTO_MARGINS ? "has" : "does not have"); + if (EL_HAS_AUTO_MARGINS) + (void) fprintf(el->el_outfile, "\tIt %s magic margins\n", + EL_HAS_MAGIC_MARGINS ? "has" : "does not have"); + + for (t = tstr, ts = el->el_term.t_str; t->name != NULL; t++, ts++) + (void) fprintf(el->el_outfile, "\t%25s (%s) == %s\n", + t->long_name, + t->name, *ts && **ts ? + key__decode_str(*ts, upbuf, "") : "(empty)"); + (void) fputc('\n', el->el_outfile); + return (0); +} + + +/* term_settc(): + * Change the current terminal characteristics + */ +protected int +/*ARGSUSED*/ +term_settc(EditLine *el, int argc, const char **argv) +{ + const struct termcapstr *ts; + const struct termcapval *tv; + const char *what, *how; + + if (argv == NULL || argv[1] == NULL || argv[2] == NULL) + return (-1); + + what = argv[1]; + how = argv[2]; + + /* + * Do the strings first + */ + for (ts = tstr; ts->name != NULL; ts++) + if (strcmp(ts->name, what) == 0) + break; + + if (ts->name != NULL) { + term_alloc(el, ts, how); + term_setflags(el); + return (0); + } + /* + * Do the numeric ones second + */ + for (tv = tval; tv->name != NULL; tv++) + if (strcmp(tv->name, what) == 0) + break; + + if (tv->name != NULL) { + if (tv == &tval[T_pt] || tv == &tval[T_km] || + tv == &tval[T_am] || tv == &tval[T_xn]) { + if (strcmp(how, "yes") == 0) + el->el_term.t_val[tv - tval] = 1; + else if (strcmp(how, "no") == 0) + el->el_term.t_val[tv - tval] = 0; + else { + (void) fprintf(el->el_errfile, + "settc: Bad value `%s'.\n", how); + return (-1); + } + term_setflags(el); + if (term_change_size(el, Val(T_li), Val(T_co)) == -1) + return (-1); + return (0); + } else { + long i; + char *ep; + + i = strtol(how, &ep, 10); + if (*ep != '\0') { + (void) fprintf(el->el_errfile, + "settc: Bad value `%s'.\n", how); + return (-1); + } + el->el_term.t_val[tv - tval] = (int) i; + el->el_term.t_size.v = Val(T_co); + el->el_term.t_size.h = Val(T_li); + if (tv == &tval[T_co] || tv == &tval[T_li]) + if (term_change_size(el, Val(T_li), Val(T_co)) + == -1) + return (-1); + return (0); + } + } + return (-1); +} + + +/* term_echotc(): + * Print the termcap string out with variable substitution + */ +protected int +/*ARGSUSED*/ +term_echotc(EditLine *el, int argc, const char **argv) +{ + char *cap, *scap, *ep; + int arg_need, arg_cols, arg_rows; + int verbose = 0, silent = 0; + char *area; + static const char fmts[] = "%s\n", fmtd[] = "%d\n"; + const struct termcapstr *t; + char buf[TC_BUFSIZE]; + long i; + + area = buf; + + if (argv == NULL || argv[1] == NULL) + return (-1); + argv++; + + if (argv[0][0] == '-') { + switch (argv[0][1]) { + case 'v': + verbose = 1; + break; + case 's': + silent = 1; + break; + default: + /* stderror(ERR_NAME | ERR_TCUSAGE); */ + break; + } + argv++; + } + if (!*argv || *argv[0] == '\0') + return (0); + if (strcmp(*argv, "tabs") == 0) { + (void) fprintf(el->el_outfile, fmts, EL_CAN_TAB ? "yes" : "no"); + return (0); + } else if (strcmp(*argv, "meta") == 0) { + (void) fprintf(el->el_outfile, fmts, Val(T_km) ? "yes" : "no"); + return (0); + } else if (strcmp(*argv, "xn") == 0) { + (void) fprintf(el->el_outfile, fmts, EL_HAS_MAGIC_MARGINS ? + "yes" : "no"); + return (0); + } else if (strcmp(*argv, "am") == 0) { + (void) fprintf(el->el_outfile, fmts, EL_HAS_AUTO_MARGINS ? + "yes" : "no"); + return (0); + } else if (strcmp(*argv, "baud") == 0) { +#ifdef notdef + int i; + + for (i = 0; baud_rate[i].b_name != NULL; i++) + if (el->el_tty.t_speed == baud_rate[i].b_rate) { + (void) fprintf(el->el_outfile, fmts, + baud_rate[i].b_name); + return (0); + } + (void) fprintf(el->el_outfile, fmtd, 0); +#else + (void) fprintf(el->el_outfile, fmtd, (int) el->el_tty.t_speed); +#endif + return (0); + } else if (strcmp(*argv, "rows") == 0 || strcmp(*argv, "lines") == 0) { + (void) fprintf(el->el_outfile, fmtd, Val(T_li)); + return (0); + } else if (strcmp(*argv, "cols") == 0) { + (void) fprintf(el->el_outfile, fmtd, Val(T_co)); + return (0); + } + /* + * Try to use our local definition first + */ + scap = NULL; + for (t = tstr; t->name != NULL; t++) + if (strcmp(t->name, *argv) == 0) { + scap = el->el_term.t_str[t - tstr]; + break; + } + if (t->name == NULL) + scap = tgetstr((char *)argv, &area); + if (!scap || scap[0] == '\0') { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Termcap parameter `%s' not found.\n", + *argv); + return (-1); + } + /* + * Count home many values we need for this capability. + */ + for (cap = scap, arg_need = 0; *cap; cap++) + if (*cap == '%') + switch (*++cap) { + case 'd': + case '2': + case '3': + case '.': + case '+': + arg_need++; + break; + case '%': + case '>': + case 'i': + case 'r': + case 'n': + case 'B': + case 'D': + break; + default: + /* + * hpux has lot's of them... + */ + if (verbose) + (void) fprintf(el->el_errfile, + "echotc: Warning: unknown termcap %% `%c'.\n", + *cap); + /* This is bad, but I won't complain */ + break; + } + + switch (arg_need) { + case 0: + argv++; + if (*argv && *argv[0]) { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Warning: Extra argument `%s'.\n", + *argv); + return (-1); + } + (void) tputs(scap, 1, term__putc); + break; + case 1: + argv++; + if (!*argv || *argv[0] == '\0') { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Warning: Missing argument.\n"); + return (-1); + } + arg_cols = 0; + i = strtol(*argv, &ep, 10); + if (*ep != '\0' || i < 0) { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Bad value `%s' for rows.\n", + *argv); + return (-1); + } + arg_rows = (int) i; + argv++; + if (*argv && *argv[0]) { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Warning: Extra argument `%s'.\n", + *argv); + return (-1); + } + (void) tputs(tgoto(scap, arg_cols, arg_rows), 1, term__putc); + break; + default: + /* This is wrong, but I will ignore it... */ + if (verbose) + (void) fprintf(el->el_errfile, + "echotc: Warning: Too many required arguments (%d).\n", + arg_need); + /* FALLTHROUGH */ + case 2: + argv++; + if (!*argv || *argv[0] == '\0') { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Warning: Missing argument.\n"); + return (-1); + } + i = strtol(*argv, &ep, 10); + if (*ep != '\0' || i < 0) { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Bad value `%s' for cols.\n", + *argv); + return (-1); + } + arg_cols = (int) i; + argv++; + if (!*argv || *argv[0] == '\0') { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Warning: Missing argument.\n"); + return (-1); + } + i = strtol(*argv, &ep, 10); + if (*ep != '\0' || i < 0) { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Bad value `%s' for rows.\n", + *argv); + return (-1); + } + arg_rows = (int) i; + if (*ep != '\0') { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Bad value `%s'.\n", *argv); + return (-1); + } + argv++; + if (*argv && *argv[0]) { + if (!silent) + (void) fprintf(el->el_errfile, + "echotc: Warning: Extra argument `%s'.\n", + *argv); + return (-1); + } + (void) tputs(tgoto(scap, arg_cols, arg_rows), arg_rows, + term__putc); + break; + } + return (0); +} diff --git a/main/editline/term.h b/main/editline/term.h new file mode 100644 index 000000000..47e08e84b --- /dev/null +++ b/main/editline/term.h @@ -0,0 +1,124 @@ +/* $NetBSD: term.h,v 1.13 2002/03/18 16:01:00 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)term.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.term.h: Termcap header + */ +#ifndef _h_el_term +#define _h_el_term + +#include "histedit.h" + +typedef struct { /* Symbolic function key bindings */ + const char *name; /* name of the key */ + int key; /* Index in termcap table */ + key_value_t fun; /* Function bound to it */ + int type; /* Type of function */ +} fkey_t; + +typedef struct { + coord_t t_size; /* # lines and cols */ + int t_flags; +#define TERM_CAN_INSERT 0x001 /* Has insert cap */ +#define TERM_CAN_DELETE 0x002 /* Has delete cap */ +#define TERM_CAN_CEOL 0x004 /* Has CEOL cap */ +#define TERM_CAN_TAB 0x008 /* Can use tabs */ +#define TERM_CAN_ME 0x010 /* Can turn all attrs. */ +#define TERM_CAN_UP 0x020 /* Can move up */ +#define TERM_HAS_META 0x040 /* Has a meta key */ +#define TERM_HAS_AUTO_MARGINS 0x080 /* Has auto margins */ +#define TERM_HAS_MAGIC_MARGINS 0x100 /* Has magic margins */ + char *t_buf; /* Termcap buffer */ + int t_loc; /* location used */ + char **t_str; /* termcap strings */ + int *t_val; /* termcap values */ + char *t_cap; /* Termcap buffer */ + fkey_t *t_fkey; /* Array of keys */ +} el_term_t; + +/* + * fKey indexes + */ +#define A_K_DN 0 +#define A_K_UP 1 +#define A_K_LT 2 +#define A_K_RT 3 +#define A_K_HO 4 +#define A_K_EN 5 +#define A_K_NKEYS 6 + +protected void term_move_to_line(EditLine *, int); +protected void term_move_to_char(EditLine *, int); +protected void term_clear_EOL(EditLine *, int); +protected void term_overwrite(EditLine *, const char *, int); +protected void term_insertwrite(EditLine *, char *, int); +protected void term_deletechars(EditLine *, int); +protected void term_clear_screen(EditLine *); +protected void term_beep(EditLine *); +protected int term_change_size(EditLine *, int, int); +protected int term_get_size(EditLine *, int *, int *); +protected int term_init(EditLine *); +protected void term_bind_arrow(EditLine *); +protected void term_print_arrow(EditLine *, const char *); +protected int term_clear_arrow(EditLine *, const char *); +protected int term_set_arrow(EditLine *, const char *, key_value_t *, int); +protected void term_end(EditLine *); +protected int term_set(EditLine *, const char *); +protected int term_settc(EditLine *, int, const char **); +protected int term_telltc(EditLine *, int, const char **); +protected int term_echotc(EditLine *, int, const char **); +protected int term__putc(int); +protected void term__flush(void); + +/* + * Easy access macros + */ +#define EL_FLAGS (el)->el_term.t_flags + +#define EL_CAN_INSERT (EL_FLAGS & TERM_CAN_INSERT) +#define EL_CAN_DELETE (EL_FLAGS & TERM_CAN_DELETE) +#define EL_CAN_CEOL (EL_FLAGS & TERM_CAN_CEOL) +#define EL_CAN_TAB (EL_FLAGS & TERM_CAN_TAB) +#define EL_CAN_ME (EL_FLAGS & TERM_CAN_ME) +#define EL_HAS_META (EL_FLAGS & TERM_HAS_META) +#define EL_HAS_AUTO_MARGINS (EL_FLAGS & TERM_HAS_AUTO_MARGINS) +#define EL_HAS_MAGIC_MARGINS (EL_FLAGS & TERM_HAS_MAGIC_MARGINS) + +#endif /* _h_el_term */ diff --git a/main/editline/tokenizer.c b/main/editline/tokenizer.c new file mode 100644 index 000000000..f0de39bc9 --- /dev/null +++ b/main/editline/tokenizer.c @@ -0,0 +1,397 @@ +/* $NetBSD: tokenizer.c,v 1.10 2002/03/18 16:01:00 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)tokenizer.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: tokenizer.c,v 1.10 2002/03/18 16:01:00 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * tokenize.c: Bourne shell like tokenizer + */ +#include +#include +#include "tokenizer.h" + +typedef enum { + Q_none, Q_single, Q_double, Q_one, Q_doubleone +} quote_t; + +#define IFS "\t \n" + +#define TOK_KEEP 1 +#define TOK_EAT 2 + +#define WINCR 20 +#define AINCR 10 + +#define tok_malloc(a) malloc(a) +#define tok_free(a) free(a) +#define tok_realloc(a, b) realloc(a, b) + + +struct tokenizer { + char *ifs; /* In field separator */ + int argc, amax; /* Current and maximum number of args */ + char **argv; /* Argument list */ + char *wptr, *wmax; /* Space and limit on the word buffer */ + char *wstart; /* Beginning of next word */ + char *wspace; /* Space of word buffer */ + quote_t quote; /* Quoting state */ + int flags; /* flags; */ +}; + + +private void tok_finish(Tokenizer *); + + +/* tok_finish(): + * Finish a word in the tokenizer. + */ +private void +tok_finish(Tokenizer *tok) +{ + + *tok->wptr = '\0'; + if ((tok->flags & TOK_KEEP) || tok->wptr != tok->wstart) { + tok->argv[tok->argc++] = tok->wstart; + tok->argv[tok->argc] = NULL; + tok->wstart = ++tok->wptr; + } + tok->flags &= ~TOK_KEEP; +} + + +/* tok_init(): + * Initialize the tokenizer + */ +public Tokenizer * +tok_init(const char *ifs) +{ + Tokenizer *tok = (Tokenizer *) tok_malloc(sizeof(Tokenizer)); + + tok->ifs = strdup(ifs ? ifs : IFS); + tok->argc = 0; + tok->amax = AINCR; + tok->argv = (char **) tok_malloc(sizeof(char *) * tok->amax); + if (tok->argv == NULL) + return (NULL); + tok->argv[0] = NULL; + tok->wspace = (char *) tok_malloc(WINCR); + if (tok->wspace == NULL) + return (NULL); + tok->wmax = tok->wspace + WINCR; + tok->wstart = tok->wspace; + tok->wptr = tok->wspace; + tok->flags = 0; + tok->quote = Q_none; + + return (tok); +} + + +/* tok_reset(): + * Reset the tokenizer + */ +public void +tok_reset(Tokenizer *tok) +{ + + tok->argc = 0; + tok->wstart = tok->wspace; + tok->wptr = tok->wspace; + tok->flags = 0; + tok->quote = Q_none; +} + + +/* tok_end(): + * Clean up + */ +public void +tok_end(Tokenizer *tok) +{ + + tok_free((ptr_t) tok->ifs); + tok_free((ptr_t) tok->wspace); + tok_free((ptr_t) tok->argv); + tok_free((ptr_t) tok); +} + + + +/* tok_line(): + * Bourne shell like tokenizing + * Return: + * -1: Internal error + * 3: Quoted return + * 2: Unmatched double quote + * 1: Unmatched single quote + * 0: Ok + */ +public int +tok_line(Tokenizer *tok, const char *line, int *argc, const char ***argv) +{ + const char *ptr; + + for (;;) { + switch (*(ptr = line++)) { + case '\'': + tok->flags |= TOK_KEEP; + tok->flags &= ~TOK_EAT; + switch (tok->quote) { + case Q_none: + tok->quote = Q_single; /* Enter single quote + * mode */ + break; + + case Q_single: /* Exit single quote mode */ + tok->quote = Q_none; + break; + + case Q_one: /* Quote this ' */ + tok->quote = Q_none; + *tok->wptr++ = *ptr; + break; + + case Q_double: /* Stay in double quote mode */ + *tok->wptr++ = *ptr; + break; + + case Q_doubleone: /* Quote this ' */ + tok->quote = Q_double; + *tok->wptr++ = *ptr; + break; + + default: + return (-1); + } + break; + + case '"': + tok->flags &= ~TOK_EAT; + tok->flags |= TOK_KEEP; + switch (tok->quote) { + case Q_none: /* Enter double quote mode */ + tok->quote = Q_double; + break; + + case Q_double: /* Exit double quote mode */ + tok->quote = Q_none; + break; + + case Q_one: /* Quote this " */ + tok->quote = Q_none; + *tok->wptr++ = *ptr; + break; + + case Q_single: /* Stay in single quote mode */ + *tok->wptr++ = *ptr; + break; + + case Q_doubleone: /* Quote this " */ + tok->quote = Q_double; + *tok->wptr++ = *ptr; + break; + + default: + return (-1); + } + break; + + case '\\': + tok->flags |= TOK_KEEP; + tok->flags &= ~TOK_EAT; + switch (tok->quote) { + case Q_none: /* Quote next character */ + tok->quote = Q_one; + break; + + case Q_double: /* Quote next character */ + tok->quote = Q_doubleone; + break; + + case Q_one: /* Quote this, restore state */ + *tok->wptr++ = *ptr; + tok->quote = Q_none; + break; + + case Q_single: /* Stay in single quote mode */ + *tok->wptr++ = *ptr; + break; + + case Q_doubleone: /* Quote this \ */ + tok->quote = Q_double; + *tok->wptr++ = *ptr; + break; + + default: + return (-1); + } + break; + + case '\n': + tok->flags &= ~TOK_EAT; + switch (tok->quote) { + case Q_none: + tok_finish(tok); + *argv = (const char **)tok->argv; + *argc = tok->argc; + return (0); + + case Q_single: + case Q_double: + *tok->wptr++ = *ptr; /* Add the return */ + break; + + case Q_doubleone: /* Back to double, eat the '\n' */ + tok->flags |= TOK_EAT; + tok->quote = Q_double; + break; + + case Q_one: /* No quote, more eat the '\n' */ + tok->flags |= TOK_EAT; + tok->quote = Q_none; + break; + + default: + return (0); + } + break; + + case '\0': + switch (tok->quote) { + case Q_none: + /* Finish word and return */ + if (tok->flags & TOK_EAT) { + tok->flags &= ~TOK_EAT; + return (3); + } + tok_finish(tok); + *argv = (const char **)tok->argv; + *argc = tok->argc; + return (0); + + case Q_single: + return (1); + + case Q_double: + return (2); + + case Q_doubleone: + tok->quote = Q_double; + *tok->wptr++ = *ptr; + break; + + case Q_one: + tok->quote = Q_none; + *tok->wptr++ = *ptr; + break; + + default: + return (-1); + } + break; + + default: + tok->flags &= ~TOK_EAT; + switch (tok->quote) { + case Q_none: + if (strchr(tok->ifs, *ptr) != NULL) + tok_finish(tok); + else + *tok->wptr++ = *ptr; + break; + + case Q_single: + case Q_double: + *tok->wptr++ = *ptr; + break; + + + case Q_doubleone: + *tok->wptr++ = '\\'; + tok->quote = Q_double; + *tok->wptr++ = *ptr; + break; + + case Q_one: + tok->quote = Q_none; + *tok->wptr++ = *ptr; + break; + + default: + return (-1); + + } + break; + } + + if (tok->wptr >= tok->wmax - 4) { + size_t size = tok->wmax - tok->wspace + WINCR; + char *s = (char *) tok_realloc(tok->wspace, size); + if (s == NULL) + return (-1); + + if (s != tok->wspace) { + int i; + for (i = 0; i < tok->argc; i++) { + tok->argv[i] = + (tok->argv[i] - tok->wspace) + s; + } + tok->wptr = (tok->wptr - tok->wspace) + s; + tok->wstart = (tok->wstart - tok->wspace) + s; + tok->wspace = s; + } + tok->wmax = s + size; + } + if (tok->argc >= tok->amax - 4) { + char **p; + tok->amax += AINCR; + p = (char **) tok_realloc(tok->argv, + tok->amax * sizeof(char *)); + if (p == NULL) + return (-1); + tok->argv = p; + } + } +} diff --git a/main/editline/tokenizer.h b/main/editline/tokenizer.h new file mode 100644 index 000000000..7cc7a3346 --- /dev/null +++ b/main/editline/tokenizer.h @@ -0,0 +1,54 @@ +/* $NetBSD: tokenizer.h,v 1.5 2002/03/18 16:01:00 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)tokenizer.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * tokenizer.h: Header file for tokenizer routines + */ +#ifndef _h_tokenizer +#define _h_tokenizer + +typedef struct tokenizer Tokenizer; + +Tokenizer *tok_init(const char *); +void tok_reset(Tokenizer *); +void tok_end(Tokenizer *); +int tok_line(Tokenizer *, const char *, int *, const char ***); + +#endif /* _h_tokenizer */ diff --git a/main/editline/tty.c b/main/editline/tty.c new file mode 100644 index 000000000..256cf780b --- /dev/null +++ b/main/editline/tty.c @@ -0,0 +1,1182 @@ +/* $NetBSD: tty.c,v 1.16 2002/03/18 16:01:01 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)tty.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: tty.c,v 1.16 2002/03/18 16:01:01 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * tty.c: tty interface stuff + */ +#include "tty.h" +#include "el.h" + +typedef struct ttymodes_t { + const char *m_name; + u_int m_value; + int m_type; +} ttymodes_t; + +typedef struct ttymap_t { + int nch, och; /* Internal and termio rep of chars */ + el_action_t bind[3]; /* emacs, vi, and vi-cmd */ +} ttymap_t; + + +private const ttyperm_t ttyperm = { + { + {"iflag:", ICRNL, (INLCR | IGNCR)}, + {"oflag:", (OPOST | ONLCR), ONLRET}, + {"cflag:", 0, 0}, + {"lflag:", (ISIG | ICANON | ECHO | ECHOE | ECHOCTL | IEXTEN), + (NOFLSH | ECHONL | EXTPROC | FLUSHO)}, + {"chars:", 0, 0}, + }, + { + {"iflag:", (INLCR | ICRNL), IGNCR}, + {"oflag:", (OPOST | ONLCR), ONLRET}, + {"cflag:", 0, 0}, + {"lflag:", ISIG, + (NOFLSH | ICANON | ECHO | ECHOK | ECHONL | EXTPROC | IEXTEN | FLUSHO)}, + {"chars:", (C_SH(C_MIN) | C_SH(C_TIME) | C_SH(C_SWTCH) | C_SH(C_DSWTCH) | + C_SH(C_SUSP) | C_SH(C_DSUSP) | C_SH(C_EOL) | C_SH(C_DISCARD) | + C_SH(C_PGOFF) | C_SH(C_PAGE) | C_SH(C_STATUS)), 0} + }, + { + {"iflag:", 0, IXON | IXOFF | INLCR | ICRNL}, + {"oflag:", 0, 0}, + {"cflag:", 0, 0}, + {"lflag:", 0, ISIG | IEXTEN}, + {"chars:", 0, 0}, + } +}; + +private const ttychar_t ttychar = { + { + CINTR, CQUIT, CERASE, CKILL, + CEOF, CEOL, CEOL2, CSWTCH, + CDSWTCH, CERASE2, CSTART, CSTOP, + CWERASE, CSUSP, CDSUSP, CREPRINT, + CDISCARD, CLNEXT, CSTATUS, CPAGE, + CPGOFF, CKILL2, CBRK, CMIN, + CTIME + }, + { + CINTR, CQUIT, CERASE, CKILL, + _POSIX_VDISABLE, _POSIX_VDISABLE, _POSIX_VDISABLE, _POSIX_VDISABLE, + _POSIX_VDISABLE, CERASE2, CSTART, CSTOP, + _POSIX_VDISABLE, CSUSP, _POSIX_VDISABLE, _POSIX_VDISABLE, + CDISCARD, _POSIX_VDISABLE, _POSIX_VDISABLE, _POSIX_VDISABLE, + _POSIX_VDISABLE, _POSIX_VDISABLE, _POSIX_VDISABLE, 1, + 0 + }, + { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0 + } +}; + +private const ttymap_t tty_map[] = { +#ifdef VERASE + {C_ERASE, VERASE, + {ED_DELETE_PREV_CHAR, VI_DELETE_PREV_CHAR, ED_PREV_CHAR}}, +#endif /* VERASE */ +#ifdef VERASE2 + {C_ERASE2, VERASE2, + {ED_DELETE_PREV_CHAR, VI_DELETE_PREV_CHAR, ED_PREV_CHAR}}, +#endif /* VERASE2 */ +#ifdef VKILL + {C_KILL, VKILL, + {EM_KILL_LINE, VI_KILL_LINE_PREV, ED_UNASSIGNED}}, +#endif /* VKILL */ +#ifdef VKILL2 + {C_KILL2, VKILL2, + {EM_KILL_LINE, VI_KILL_LINE_PREV, ED_UNASSIGNED}}, +#endif /* VKILL2 */ +#ifdef VEOF + {C_EOF, VEOF, + {EM_DELETE_OR_LIST, VI_LIST_OR_EOF, ED_UNASSIGNED}}, +#endif /* VEOF */ +#ifdef VWERASE + {C_WERASE, VWERASE, + {ED_DELETE_PREV_WORD, ED_DELETE_PREV_WORD, ED_PREV_WORD}}, +#endif /* VWERASE */ +#ifdef VREPRINT + {C_REPRINT, VREPRINT, + {ED_REDISPLAY, ED_INSERT, ED_REDISPLAY}}, +#endif /* VREPRINT */ +#ifdef VLNEXT + {C_LNEXT, VLNEXT, + {ED_QUOTED_INSERT, ED_QUOTED_INSERT, ED_UNASSIGNED}}, +#endif /* VLNEXT */ + {-1, -1, + {ED_UNASSIGNED, ED_UNASSIGNED, ED_UNASSIGNED}} +}; + +private const ttymodes_t ttymodes[] = { +#ifdef IGNBRK + {"ignbrk", IGNBRK, MD_INP}, +#endif /* IGNBRK */ +#ifdef BRKINT + {"brkint", BRKINT, MD_INP}, +#endif /* BRKINT */ +#ifdef IGNPAR + {"ignpar", IGNPAR, MD_INP}, +#endif /* IGNPAR */ +#ifdef PARMRK + {"parmrk", PARMRK, MD_INP}, +#endif /* PARMRK */ +#ifdef INPCK + {"inpck", INPCK, MD_INP}, +#endif /* INPCK */ +#ifdef ISTRIP + {"istrip", ISTRIP, MD_INP}, +#endif /* ISTRIP */ +#ifdef INLCR + {"inlcr", INLCR, MD_INP}, +#endif /* INLCR */ +#ifdef IGNCR + {"igncr", IGNCR, MD_INP}, +#endif /* IGNCR */ +#ifdef ICRNL + {"icrnl", ICRNL, MD_INP}, +#endif /* ICRNL */ +#ifdef IUCLC + {"iuclc", IUCLC, MD_INP}, +#endif /* IUCLC */ +#ifdef IXON + {"ixon", IXON, MD_INP}, +#endif /* IXON */ +#ifdef IXANY + {"ixany", IXANY, MD_INP}, +#endif /* IXANY */ +#ifdef IXOFF + {"ixoff", IXOFF, MD_INP}, +#endif /* IXOFF */ +#ifdef IMAXBEL + {"imaxbel", IMAXBEL, MD_INP}, +#endif /* IMAXBEL */ + +#ifdef OPOST + {"opost", OPOST, MD_OUT}, +#endif /* OPOST */ +#ifdef OLCUC + {"olcuc", OLCUC, MD_OUT}, +#endif /* OLCUC */ +#ifdef ONLCR + {"onlcr", ONLCR, MD_OUT}, +#endif /* ONLCR */ +#ifdef OCRNL + {"ocrnl", OCRNL, MD_OUT}, +#endif /* OCRNL */ +#ifdef ONOCR + {"onocr", ONOCR, MD_OUT}, +#endif /* ONOCR */ +#ifdef ONOEOT + {"onoeot", ONOEOT, MD_OUT}, +#endif /* ONOEOT */ +#ifdef ONLRET + {"onlret", ONLRET, MD_OUT}, +#endif /* ONLRET */ +#ifdef OFILL + {"ofill", OFILL, MD_OUT}, +#endif /* OFILL */ +#ifdef OFDEL + {"ofdel", OFDEL, MD_OUT}, +#endif /* OFDEL */ +#ifdef NLDLY + {"nldly", NLDLY, MD_OUT}, +#endif /* NLDLY */ +#ifdef CRDLY + {"crdly", CRDLY, MD_OUT}, +#endif /* CRDLY */ +#ifdef TABDLY + {"tabdly", TABDLY, MD_OUT}, +#endif /* TABDLY */ +#ifdef XTABS + {"xtabs", XTABS, MD_OUT}, +#endif /* XTABS */ +#ifdef BSDLY + {"bsdly", BSDLY, MD_OUT}, +#endif /* BSDLY */ +#ifdef VTDLY + {"vtdly", VTDLY, MD_OUT}, +#endif /* VTDLY */ +#ifdef FFDLY + {"ffdly", FFDLY, MD_OUT}, +#endif /* FFDLY */ +#ifdef PAGEOUT + {"pageout", PAGEOUT, MD_OUT}, +#endif /* PAGEOUT */ +#ifdef WRAP + {"wrap", WRAP, MD_OUT}, +#endif /* WRAP */ + +#ifdef CIGNORE + {"cignore", CIGNORE, MD_CTL}, +#endif /* CBAUD */ +#ifdef CBAUD + {"cbaud", CBAUD, MD_CTL}, +#endif /* CBAUD */ +#ifdef CSTOPB + {"cstopb", CSTOPB, MD_CTL}, +#endif /* CSTOPB */ +#ifdef CREAD + {"cread", CREAD, MD_CTL}, +#endif /* CREAD */ +#ifdef PARENB + {"parenb", PARENB, MD_CTL}, +#endif /* PARENB */ +#ifdef PARODD + {"parodd", PARODD, MD_CTL}, +#endif /* PARODD */ +#ifdef HUPCL + {"hupcl", HUPCL, MD_CTL}, +#endif /* HUPCL */ +#ifdef CLOCAL + {"clocal", CLOCAL, MD_CTL}, +#endif /* CLOCAL */ +#ifdef LOBLK + {"loblk", LOBLK, MD_CTL}, +#endif /* LOBLK */ +#ifdef CIBAUD + {"cibaud", CIBAUD, MD_CTL}, +#endif /* CIBAUD */ +#ifdef CRTSCTS +#ifdef CCTS_OFLOW + {"ccts_oflow", CCTS_OFLOW, MD_CTL}, +#else + {"crtscts", CRTSCTS, MD_CTL}, +#endif /* CCTS_OFLOW */ +#endif /* CRTSCTS */ +#ifdef CRTS_IFLOW + {"crts_iflow", CRTS_IFLOW, MD_CTL}, +#endif /* CRTS_IFLOW */ +#ifdef CDTRCTS + {"cdtrcts", CDTRCTS, MD_CTL}, +#endif /* CDTRCTS */ +#ifdef MDMBUF + {"mdmbuf", MDMBUF, MD_CTL}, +#endif /* MDMBUF */ +#ifdef RCV1EN + {"rcv1en", RCV1EN, MD_CTL}, +#endif /* RCV1EN */ +#ifdef XMT1EN + {"xmt1en", XMT1EN, MD_CTL}, +#endif /* XMT1EN */ + +#ifdef ISIG + {"isig", ISIG, MD_LIN}, +#endif /* ISIG */ +#ifdef ICANON + {"icanon", ICANON, MD_LIN}, +#endif /* ICANON */ +#ifdef XCASE + {"xcase", XCASE, MD_LIN}, +#endif /* XCASE */ +#ifdef ECHO + {"echo", ECHO, MD_LIN}, +#endif /* ECHO */ +#ifdef ECHOE + {"echoe", ECHOE, MD_LIN}, +#endif /* ECHOE */ +#ifdef ECHOK + {"echok", ECHOK, MD_LIN}, +#endif /* ECHOK */ +#ifdef ECHONL + {"echonl", ECHONL, MD_LIN}, +#endif /* ECHONL */ +#ifdef NOFLSH + {"noflsh", NOFLSH, MD_LIN}, +#endif /* NOFLSH */ +#ifdef TOSTOP + {"tostop", TOSTOP, MD_LIN}, +#endif /* TOSTOP */ +#ifdef ECHOCTL + {"echoctl", ECHOCTL, MD_LIN}, +#endif /* ECHOCTL */ +#ifdef ECHOPRT + {"echoprt", ECHOPRT, MD_LIN}, +#endif /* ECHOPRT */ +#ifdef ECHOKE + {"echoke", ECHOKE, MD_LIN}, +#endif /* ECHOKE */ +#ifdef DEFECHO + {"defecho", DEFECHO, MD_LIN}, +#endif /* DEFECHO */ +#ifdef FLUSHO + {"flusho", FLUSHO, MD_LIN}, +#endif /* FLUSHO */ +#ifdef PENDIN + {"pendin", PENDIN, MD_LIN}, +#endif /* PENDIN */ +#ifdef IEXTEN + {"iexten", IEXTEN, MD_LIN}, +#endif /* IEXTEN */ +#ifdef NOKERNINFO + {"nokerninfo", NOKERNINFO, MD_LIN}, +#endif /* NOKERNINFO */ +#ifdef ALTWERASE + {"altwerase", ALTWERASE, MD_LIN}, +#endif /* ALTWERASE */ +#ifdef EXTPROC + {"extproc", EXTPROC, MD_LIN}, +#endif /* EXTPROC */ + +#if defined(VINTR) + {"intr", C_SH(C_INTR), MD_CHAR}, +#endif /* VINTR */ +#if defined(VQUIT) + {"quit", C_SH(C_QUIT), MD_CHAR}, +#endif /* VQUIT */ +#if defined(VERASE) + {"erase", C_SH(C_ERASE), MD_CHAR}, +#endif /* VERASE */ +#if defined(VKILL) + {"kill", C_SH(C_KILL), MD_CHAR}, +#endif /* VKILL */ +#if defined(VEOF) + {"eof", C_SH(C_EOF), MD_CHAR}, +#endif /* VEOF */ +#if defined(VEOL) + {"eol", C_SH(C_EOL), MD_CHAR}, +#endif /* VEOL */ +#if defined(VEOL2) + {"eol2", C_SH(C_EOL2), MD_CHAR}, +#endif /* VEOL2 */ +#if defined(VSWTCH) + {"swtch", C_SH(C_SWTCH), MD_CHAR}, +#endif /* VSWTCH */ +#if defined(VDSWTCH) + {"dswtch", C_SH(C_DSWTCH), MD_CHAR}, +#endif /* VDSWTCH */ +#if defined(VERASE2) + {"erase2", C_SH(C_ERASE2), MD_CHAR}, +#endif /* VERASE2 */ +#if defined(VSTART) + {"start", C_SH(C_START), MD_CHAR}, +#endif /* VSTART */ +#if defined(VSTOP) + {"stop", C_SH(C_STOP), MD_CHAR}, +#endif /* VSTOP */ +#if defined(VWERASE) + {"werase", C_SH(C_WERASE), MD_CHAR}, +#endif /* VWERASE */ +#if defined(VSUSP) + {"susp", C_SH(C_SUSP), MD_CHAR}, +#endif /* VSUSP */ +#if defined(VDSUSP) + {"dsusp", C_SH(C_DSUSP), MD_CHAR}, +#endif /* VDSUSP */ +#if defined(VREPRINT) + {"reprint", C_SH(C_REPRINT), MD_CHAR}, +#endif /* VREPRINT */ +#if defined(VDISCARD) + {"discard", C_SH(C_DISCARD), MD_CHAR}, +#endif /* VDISCARD */ +#if defined(VLNEXT) + {"lnext", C_SH(C_LNEXT), MD_CHAR}, +#endif /* VLNEXT */ +#if defined(VSTATUS) + {"status", C_SH(C_STATUS), MD_CHAR}, +#endif /* VSTATUS */ +#if defined(VPAGE) + {"page", C_SH(C_PAGE), MD_CHAR}, +#endif /* VPAGE */ +#if defined(VPGOFF) + {"pgoff", C_SH(C_PGOFF), MD_CHAR}, +#endif /* VPGOFF */ +#if defined(VKILL2) + {"kill2", C_SH(C_KILL2), MD_CHAR}, +#endif /* VKILL2 */ +#if defined(VBRK) + {"brk", C_SH(C_BRK), MD_CHAR}, +#endif /* VBRK */ +#if defined(VMIN) + {"min", C_SH(C_MIN), MD_CHAR}, +#endif /* VMIN */ +#if defined(VTIME) + {"time", C_SH(C_TIME), MD_CHAR}, +#endif /* VTIME */ + {NULL, 0, -1}, +}; + + + +#define tty_getty(el, td) tcgetattr((el)->el_infd, (td)) +#define tty_setty(el, td) tcsetattr((el)->el_infd, TCSADRAIN, (td)) + +#define tty__gettabs(td) ((((td)->c_oflag & TAB3) == TAB3) ? 0 : 1) +#define tty__geteightbit(td) (((td)->c_cflag & CSIZE) == CS8) +#define tty__cooked_mode(td) ((td)->c_lflag & ICANON) + +private void tty__getchar(struct termios *, unsigned char *); +private void tty__setchar(struct termios *, unsigned char *); +private speed_t tty__getspeed(struct termios *); +private int tty_setup(EditLine *); + +#define t_qu t_ts + + +/* tty_setup(): + * Get the tty parameters and initialize the editing state + */ +private int +tty_setup(EditLine *el) +{ + int rst = 1; + + if (el->el_flags & EDIT_DISABLED) + return (0); + + if (tty_getty(el, &el->el_tty.t_ed) == -1) { +#ifdef DEBUG_TTY + (void) fprintf(el->el_errfile, + "tty_setup: tty_getty: %s\n", strerror(errno)); +#endif /* DEBUG_TTY */ + return (-1); + } + el->el_tty.t_ts = el->el_tty.t_ex = el->el_tty.t_ed; + + el->el_tty.t_speed = tty__getspeed(&el->el_tty.t_ex); + el->el_tty.t_tabs = tty__gettabs(&el->el_tty.t_ex); + el->el_tty.t_eight = tty__geteightbit(&el->el_tty.t_ex); + + el->el_tty.t_ex.c_iflag &= ~el->el_tty.t_t[EX_IO][MD_INP].t_clrmask; + el->el_tty.t_ex.c_iflag |= el->el_tty.t_t[EX_IO][MD_INP].t_setmask; + + el->el_tty.t_ex.c_oflag &= ~el->el_tty.t_t[EX_IO][MD_OUT].t_clrmask; + el->el_tty.t_ex.c_oflag |= el->el_tty.t_t[EX_IO][MD_OUT].t_setmask; + + el->el_tty.t_ex.c_cflag &= ~el->el_tty.t_t[EX_IO][MD_CTL].t_clrmask; + el->el_tty.t_ex.c_cflag |= el->el_tty.t_t[EX_IO][MD_CTL].t_setmask; + + el->el_tty.t_ex.c_lflag &= ~el->el_tty.t_t[EX_IO][MD_LIN].t_clrmask; + el->el_tty.t_ex.c_lflag |= el->el_tty.t_t[EX_IO][MD_LIN].t_setmask; + + /* + * Reset the tty chars to reasonable defaults + * If they are disabled, then enable them. + */ + if (rst) { + if (tty__cooked_mode(&el->el_tty.t_ts)) { + tty__getchar(&el->el_tty.t_ts, el->el_tty.t_c[TS_IO]); + /* + * Don't affect CMIN and CTIME for the editor mode + */ + for (rst = 0; rst < C_NCC - 2; rst++) + if (el->el_tty.t_c[TS_IO][rst] != + el->el_tty.t_vdisable + && el->el_tty.t_c[ED_IO][rst] != + el->el_tty.t_vdisable) + el->el_tty.t_c[ED_IO][rst] = + el->el_tty.t_c[TS_IO][rst]; + for (rst = 0; rst < C_NCC; rst++) + if (el->el_tty.t_c[TS_IO][rst] != + el->el_tty.t_vdisable) + el->el_tty.t_c[EX_IO][rst] = + el->el_tty.t_c[TS_IO][rst]; + } + tty__setchar(&el->el_tty.t_ex, el->el_tty.t_c[EX_IO]); + if (tty_setty(el, &el->el_tty.t_ex) == -1) { +#ifdef DEBUG_TTY + (void) fprintf(el->el_errfile, + "tty_setup: tty_setty: %s\n", + strerror(errno)); +#endif /* DEBUG_TTY */ + return (-1); + } + } else + tty__setchar(&el->el_tty.t_ex, el->el_tty.t_c[EX_IO]); + + el->el_tty.t_ed.c_iflag &= ~el->el_tty.t_t[ED_IO][MD_INP].t_clrmask; + el->el_tty.t_ed.c_iflag |= el->el_tty.t_t[ED_IO][MD_INP].t_setmask; + + el->el_tty.t_ed.c_oflag &= ~el->el_tty.t_t[ED_IO][MD_OUT].t_clrmask; + el->el_tty.t_ed.c_oflag |= el->el_tty.t_t[ED_IO][MD_OUT].t_setmask; + + el->el_tty.t_ed.c_cflag &= ~el->el_tty.t_t[ED_IO][MD_CTL].t_clrmask; + el->el_tty.t_ed.c_cflag |= el->el_tty.t_t[ED_IO][MD_CTL].t_setmask; + + el->el_tty.t_ed.c_lflag &= ~el->el_tty.t_t[ED_IO][MD_LIN].t_clrmask; + el->el_tty.t_ed.c_lflag |= el->el_tty.t_t[ED_IO][MD_LIN].t_setmask; + + tty__setchar(&el->el_tty.t_ed, el->el_tty.t_c[ED_IO]); + tty_bind_char(el, 1); + return (0); +} + +protected int +tty_init(EditLine *el) +{ + + el->el_tty.t_mode = EX_IO; + el->el_tty.t_vdisable = _POSIX_VDISABLE; + (void) memcpy(el->el_tty.t_t, ttyperm, sizeof(ttyperm_t)); + (void) memcpy(el->el_tty.t_c, ttychar, sizeof(ttychar_t)); + return (tty_setup(el)); +} + + +/* tty_end(): + * Restore the tty to its original settings + */ +protected void +/*ARGSUSED*/ +tty_end(EditLine *el) +{ + + /* XXX: Maybe reset to an initial state? */ +} + + +/* tty__getspeed(): + * Get the tty speed + */ +private speed_t +tty__getspeed(struct termios *td) +{ + speed_t spd; + + if ((spd = cfgetispeed(td)) == 0) + spd = cfgetospeed(td); + return (spd); +} + + +/* tty__getchar(): + * Get the tty characters + */ +private void +tty__getchar(struct termios *td, unsigned char *s) +{ + +#ifdef VINTR + s[C_INTR] = td->c_cc[VINTR]; +#endif /* VINTR */ +#ifdef VQUIT + s[C_QUIT] = td->c_cc[VQUIT]; +#endif /* VQUIT */ +#ifdef VERASE + s[C_ERASE] = td->c_cc[VERASE]; +#endif /* VERASE */ +#ifdef VKILL + s[C_KILL] = td->c_cc[VKILL]; +#endif /* VKILL */ +#ifdef VEOF + s[C_EOF] = td->c_cc[VEOF]; +#endif /* VEOF */ +#ifdef VEOL + s[C_EOL] = td->c_cc[VEOL]; +#endif /* VEOL */ +#ifdef VEOL2 + s[C_EOL2] = td->c_cc[VEOL2]; +#endif /* VEOL2 */ +#ifdef VSWTCH + s[C_SWTCH] = td->c_cc[VSWTCH]; +#endif /* VSWTCH */ +#ifdef VDSWTCH + s[C_DSWTCH] = td->c_cc[VDSWTCH]; +#endif /* VDSWTCH */ +#ifdef VERASE2 + s[C_ERASE2] = td->c_cc[VERASE2]; +#endif /* VERASE2 */ +#ifdef VSTART + s[C_START] = td->c_cc[VSTART]; +#endif /* VSTART */ +#ifdef VSTOP + s[C_STOP] = td->c_cc[VSTOP]; +#endif /* VSTOP */ +#ifdef VWERASE + s[C_WERASE] = td->c_cc[VWERASE]; +#endif /* VWERASE */ +#ifdef VSUSP + s[C_SUSP] = td->c_cc[VSUSP]; +#endif /* VSUSP */ +#ifdef VDSUSP + s[C_DSUSP] = td->c_cc[VDSUSP]; +#endif /* VDSUSP */ +#ifdef VREPRINT + s[C_REPRINT] = td->c_cc[VREPRINT]; +#endif /* VREPRINT */ +#ifdef VDISCARD + s[C_DISCARD] = td->c_cc[VDISCARD]; +#endif /* VDISCARD */ +#ifdef VLNEXT + s[C_LNEXT] = td->c_cc[VLNEXT]; +#endif /* VLNEXT */ +#ifdef VSTATUS + s[C_STATUS] = td->c_cc[VSTATUS]; +#endif /* VSTATUS */ +#ifdef VPAGE + s[C_PAGE] = td->c_cc[VPAGE]; +#endif /* VPAGE */ +#ifdef VPGOFF + s[C_PGOFF] = td->c_cc[VPGOFF]; +#endif /* VPGOFF */ +#ifdef VKILL2 + s[C_KILL2] = td->c_cc[VKILL2]; +#endif /* KILL2 */ +#ifdef VMIN + s[C_MIN] = td->c_cc[VMIN]; +#endif /* VMIN */ +#ifdef VTIME + s[C_TIME] = td->c_cc[VTIME]; +#endif /* VTIME */ +} /* tty__getchar */ + + +/* tty__setchar(): + * Set the tty characters + */ +private void +tty__setchar(struct termios *td, unsigned char *s) +{ + +#ifdef VINTR + td->c_cc[VINTR] = s[C_INTR]; +#endif /* VINTR */ +#ifdef VQUIT + td->c_cc[VQUIT] = s[C_QUIT]; +#endif /* VQUIT */ +#ifdef VERASE + td->c_cc[VERASE] = s[C_ERASE]; +#endif /* VERASE */ +#ifdef VKILL + td->c_cc[VKILL] = s[C_KILL]; +#endif /* VKILL */ +#ifdef VEOF + td->c_cc[VEOF] = s[C_EOF]; +#endif /* VEOF */ +#ifdef VEOL + td->c_cc[VEOL] = s[C_EOL]; +#endif /* VEOL */ +#ifdef VEOL2 + td->c_cc[VEOL2] = s[C_EOL2]; +#endif /* VEOL2 */ +#ifdef VSWTCH + td->c_cc[VSWTCH] = s[C_SWTCH]; +#endif /* VSWTCH */ +#ifdef VDSWTCH + td->c_cc[VDSWTCH] = s[C_DSWTCH]; +#endif /* VDSWTCH */ +#ifdef VERASE2 + td->c_cc[VERASE2] = s[C_ERASE2]; +#endif /* VERASE2 */ +#ifdef VSTART + td->c_cc[VSTART] = s[C_START]; +#endif /* VSTART */ +#ifdef VSTOP + td->c_cc[VSTOP] = s[C_STOP]; +#endif /* VSTOP */ +#ifdef VWERASE + td->c_cc[VWERASE] = s[C_WERASE]; +#endif /* VWERASE */ +#ifdef VSUSP + td->c_cc[VSUSP] = s[C_SUSP]; +#endif /* VSUSP */ +#ifdef VDSUSP + td->c_cc[VDSUSP] = s[C_DSUSP]; +#endif /* VDSUSP */ +#ifdef VREPRINT + td->c_cc[VREPRINT] = s[C_REPRINT]; +#endif /* VREPRINT */ +#ifdef VDISCARD + td->c_cc[VDISCARD] = s[C_DISCARD]; +#endif /* VDISCARD */ +#ifdef VLNEXT + td->c_cc[VLNEXT] = s[C_LNEXT]; +#endif /* VLNEXT */ +#ifdef VSTATUS + td->c_cc[VSTATUS] = s[C_STATUS]; +#endif /* VSTATUS */ +#ifdef VPAGE + td->c_cc[VPAGE] = s[C_PAGE]; +#endif /* VPAGE */ +#ifdef VPGOFF + td->c_cc[VPGOFF] = s[C_PGOFF]; +#endif /* VPGOFF */ +#ifdef VKILL2 + td->c_cc[VKILL2] = s[C_KILL2]; +#endif /* VKILL2 */ +#ifdef VMIN + td->c_cc[VMIN] = s[C_MIN]; +#endif /* VMIN */ +#ifdef VTIME + td->c_cc[VTIME] = s[C_TIME]; +#endif /* VTIME */ +} /* tty__setchar */ + + +/* tty_bind_char(): + * Rebind the editline functions + */ +protected void +tty_bind_char(EditLine *el, int force) +{ + + unsigned char *t_n = el->el_tty.t_c[ED_IO]; + unsigned char *t_o = el->el_tty.t_ed.c_cc; + unsigned char new[2], old[2]; + const ttymap_t *tp; + el_action_t *map, *alt; + const el_action_t *dmap, *dalt; + new[1] = old[1] = '\0'; + + map = el->el_map.key; + alt = el->el_map.alt; + if (el->el_map.type == MAP_VI) { + dmap = el->el_map.vii; + dalt = el->el_map.vic; + } else { + dmap = el->el_map.emacs; + dalt = NULL; + } + + for (tp = tty_map; tp->nch != -1; tp++) { + new[0] = t_n[tp->nch]; + old[0] = t_o[tp->och]; + if (new[0] == old[0] && !force) + continue; + /* Put the old default binding back, and set the new binding */ + key_clear(el, map, (char *)old); + map[old[0]] = dmap[old[0]]; + key_clear(el, map, (char *)new); + /* MAP_VI == 1, MAP_EMACS == 0... */ + map[new[0]] = tp->bind[el->el_map.type]; + if (dalt) { + key_clear(el, alt, (char *)old); + alt[old[0]] = dalt[old[0]]; + key_clear(el, alt, (char *)new); + alt[new[0]] = tp->bind[el->el_map.type + 1]; + } + } +} + + +/* tty_rawmode(): + * Set terminal into 1 character at a time mode. + */ +protected int +tty_rawmode(EditLine *el) +{ + + if (el->el_tty.t_mode == ED_IO || el->el_tty.t_mode == QU_IO) + return (0); + + if (el->el_flags & EDIT_DISABLED) + return (0); + + if (tty_getty(el, &el->el_tty.t_ts) == -1) { +#ifdef DEBUG_TTY + (void) fprintf(el->el_errfile, "tty_rawmode: tty_getty: %s\n", + strerror(errno)); +#endif /* DEBUG_TTY */ + return (-1); + } + /* + * We always keep up with the eight bit setting and the speed of the + * tty. But only we only believe changes that are made to cooked mode! + */ + el->el_tty.t_eight = tty__geteightbit(&el->el_tty.t_ts); + el->el_tty.t_speed = tty__getspeed(&el->el_tty.t_ts); + + if (tty__getspeed(&el->el_tty.t_ex) != el->el_tty.t_speed || + tty__getspeed(&el->el_tty.t_ed) != el->el_tty.t_speed) { + (void) cfsetispeed(&el->el_tty.t_ex, el->el_tty.t_speed); + (void) cfsetospeed(&el->el_tty.t_ex, el->el_tty.t_speed); + (void) cfsetispeed(&el->el_tty.t_ed, el->el_tty.t_speed); + (void) cfsetospeed(&el->el_tty.t_ed, el->el_tty.t_speed); + } + if (tty__cooked_mode(&el->el_tty.t_ts)) { + if (el->el_tty.t_ts.c_cflag != el->el_tty.t_ex.c_cflag) { + el->el_tty.t_ex.c_cflag = + el->el_tty.t_ts.c_cflag; + el->el_tty.t_ex.c_cflag &= + ~el->el_tty.t_t[EX_IO][MD_CTL].t_clrmask; + el->el_tty.t_ex.c_cflag |= + el->el_tty.t_t[EX_IO][MD_CTL].t_setmask; + + el->el_tty.t_ed.c_cflag = + el->el_tty.t_ts.c_cflag; + el->el_tty.t_ed.c_cflag &= + ~el->el_tty.t_t[ED_IO][MD_CTL].t_clrmask; + el->el_tty.t_ed.c_cflag |= + el->el_tty.t_t[ED_IO][MD_CTL].t_setmask; + } + if ((el->el_tty.t_ts.c_lflag != el->el_tty.t_ex.c_lflag) && + (el->el_tty.t_ts.c_lflag != el->el_tty.t_ed.c_lflag)) { + el->el_tty.t_ex.c_lflag = + el->el_tty.t_ts.c_lflag; + el->el_tty.t_ex.c_lflag &= + ~el->el_tty.t_t[EX_IO][MD_LIN].t_clrmask; + el->el_tty.t_ex.c_lflag |= + el->el_tty.t_t[EX_IO][MD_LIN].t_setmask; + + el->el_tty.t_ed.c_lflag = + el->el_tty.t_ts.c_lflag; + el->el_tty.t_ed.c_lflag &= + ~el->el_tty.t_t[ED_IO][MD_LIN].t_clrmask; + el->el_tty.t_ed.c_lflag |= + el->el_tty.t_t[ED_IO][MD_LIN].t_setmask; + } + if ((el->el_tty.t_ts.c_iflag != el->el_tty.t_ex.c_iflag) && + (el->el_tty.t_ts.c_iflag != el->el_tty.t_ed.c_iflag)) { + el->el_tty.t_ex.c_iflag = + el->el_tty.t_ts.c_iflag; + el->el_tty.t_ex.c_iflag &= + ~el->el_tty.t_t[EX_IO][MD_INP].t_clrmask; + el->el_tty.t_ex.c_iflag |= + el->el_tty.t_t[EX_IO][MD_INP].t_setmask; + + el->el_tty.t_ed.c_iflag = + el->el_tty.t_ts.c_iflag; + el->el_tty.t_ed.c_iflag &= + ~el->el_tty.t_t[ED_IO][MD_INP].t_clrmask; + el->el_tty.t_ed.c_iflag |= + el->el_tty.t_t[ED_IO][MD_INP].t_setmask; + } + if ((el->el_tty.t_ts.c_oflag != el->el_tty.t_ex.c_oflag) && + (el->el_tty.t_ts.c_oflag != el->el_tty.t_ed.c_oflag)) { + el->el_tty.t_ex.c_oflag = + el->el_tty.t_ts.c_oflag; + el->el_tty.t_ex.c_oflag &= + ~el->el_tty.t_t[EX_IO][MD_OUT].t_clrmask; + el->el_tty.t_ex.c_oflag |= + el->el_tty.t_t[EX_IO][MD_OUT].t_setmask; + + el->el_tty.t_ed.c_oflag = + el->el_tty.t_ts.c_oflag; + el->el_tty.t_ed.c_oflag &= + ~el->el_tty.t_t[ED_IO][MD_OUT].t_clrmask; + el->el_tty.t_ed.c_oflag |= + el->el_tty.t_t[ED_IO][MD_OUT].t_setmask; + } + if (tty__gettabs(&el->el_tty.t_ex) == 0) + el->el_tty.t_tabs = 0; + else + el->el_tty.t_tabs = EL_CAN_TAB ? 1 : 0; + + { + int i; + + tty__getchar(&el->el_tty.t_ts, el->el_tty.t_c[TS_IO]); + /* + * Check if the user made any changes. + * If he did, then propagate the changes to the + * edit and execute data structures. + */ + for (i = 0; i < C_NCC; i++) + if (el->el_tty.t_c[TS_IO][i] != + el->el_tty.t_c[EX_IO][i]) + break; + + if (i != C_NCC) { + /* + * Propagate changes only to the unprotected + * chars that have been modified just now. + */ + for (i = 0; i < C_NCC; i++) { + if (!((el->el_tty.t_t[ED_IO][MD_CHAR].t_setmask & C_SH(i))) + && (el->el_tty.t_c[TS_IO][i] != el->el_tty.t_c[EX_IO][i])) + el->el_tty.t_c[ED_IO][i] = el->el_tty.t_c[TS_IO][i]; + if (el->el_tty.t_t[ED_IO][MD_CHAR].t_clrmask & C_SH(i)) + el->el_tty.t_c[ED_IO][i] = el->el_tty.t_vdisable; + } + tty_bind_char(el, 0); + tty__setchar(&el->el_tty.t_ed, el->el_tty.t_c[ED_IO]); + + for (i = 0; i < C_NCC; i++) { + if (!((el->el_tty.t_t[EX_IO][MD_CHAR].t_setmask & C_SH(i))) + && (el->el_tty.t_c[TS_IO][i] != el->el_tty.t_c[EX_IO][i])) + el->el_tty.t_c[EX_IO][i] = el->el_tty.t_c[TS_IO][i]; + if (el->el_tty.t_t[EX_IO][MD_CHAR].t_clrmask & C_SH(i)) + el->el_tty.t_c[EX_IO][i] = el->el_tty.t_vdisable; + } + tty__setchar(&el->el_tty.t_ex, el->el_tty.t_c[EX_IO]); + } + } + } + if (tty_setty(el, &el->el_tty.t_ed) == -1) { +#ifdef DEBUG_TTY + (void) fprintf(el->el_errfile, "tty_rawmode: tty_setty: %s\n", + strerror(errno)); +#endif /* DEBUG_TTY */ + return (-1); + } + el->el_tty.t_mode = ED_IO; + return (0); +} + + +/* tty_cookedmode(): + * Set the tty back to normal mode + */ +protected int +tty_cookedmode(EditLine *el) +{ /* set tty in normal setup */ + + if (el->el_tty.t_mode == EX_IO) + return (0); + + if (el->el_flags & EDIT_DISABLED) + return (0); + + if (tty_setty(el, &el->el_tty.t_ex) == -1) { +#ifdef DEBUG_TTY + (void) fprintf(el->el_errfile, + "tty_cookedmode: tty_setty: %s\n", + strerror(errno)); +#endif /* DEBUG_TTY */ + return (-1); + } + el->el_tty.t_mode = EX_IO; + return (0); +} + + +/* tty_quotemode(): + * Turn on quote mode + */ +protected int +tty_quotemode(EditLine *el) +{ + if (el->el_tty.t_mode == QU_IO) + return (0); + + el->el_tty.t_qu = el->el_tty.t_ed; + + el->el_tty.t_qu.c_iflag &= ~el->el_tty.t_t[QU_IO][MD_INP].t_clrmask; + el->el_tty.t_qu.c_iflag |= el->el_tty.t_t[QU_IO][MD_INP].t_setmask; + + el->el_tty.t_qu.c_oflag &= ~el->el_tty.t_t[QU_IO][MD_OUT].t_clrmask; + el->el_tty.t_qu.c_oflag |= el->el_tty.t_t[QU_IO][MD_OUT].t_setmask; + + el->el_tty.t_qu.c_cflag &= ~el->el_tty.t_t[QU_IO][MD_CTL].t_clrmask; + el->el_tty.t_qu.c_cflag |= el->el_tty.t_t[QU_IO][MD_CTL].t_setmask; + + el->el_tty.t_qu.c_lflag &= ~el->el_tty.t_t[QU_IO][MD_LIN].t_clrmask; + el->el_tty.t_qu.c_lflag |= el->el_tty.t_t[QU_IO][MD_LIN].t_setmask; + + if (tty_setty(el, &el->el_tty.t_qu) == -1) { +#ifdef DEBUG_TTY + (void) fprintf(el->el_errfile, "QuoteModeOn: tty_setty: %s\n", + strerror(errno)); +#endif /* DEBUG_TTY */ + return (-1); + } + el->el_tty.t_mode = QU_IO; + return (0); +} + + +/* tty_noquotemode(): + * Turn off quote mode + */ +protected int +tty_noquotemode(EditLine *el) +{ + + if (el->el_tty.t_mode != QU_IO) + return (0); + if (tty_setty(el, &el->el_tty.t_ed) == -1) { +#ifdef DEBUG_TTY + (void) fprintf(el->el_errfile, "QuoteModeOff: tty_setty: %s\n", + strerror(errno)); +#endif /* DEBUG_TTY */ + return (-1); + } + el->el_tty.t_mode = ED_IO; + return (0); +} + + +/* tty_stty(): + * Stty builtin + */ +protected int +/*ARGSUSED*/ +tty_stty(EditLine *el, int argc, const char **argv) +{ + const ttymodes_t *m; + char x; + int aflag = 0; + const char *s, *d; + const char *name; + int z = EX_IO; + + if (argv == NULL) + return (-1); + name = *argv++; + + while (argv && *argv && argv[0][0] == '-' && argv[0][2] == '\0') + switch (argv[0][1]) { + case 'a': + aflag++; + argv++; + break; + case 'd': + argv++; + z = ED_IO; + break; + case 'x': + argv++; + z = EX_IO; + break; + case 'q': + argv++; + z = QU_IO; + break; + default: + (void) fprintf(el->el_errfile, + "%s: Unknown switch `%c'.\n", + name, argv[0][1]); + return (-1); + } + + if (!argv || !*argv) { + int i = -1; + int len = 0, st = 0, cu; + for (m = ttymodes; m->m_name; m++) { + if (m->m_type != i) { + (void) fprintf(el->el_outfile, "%s%s", + i != -1 ? "\n" : "", + el->el_tty.t_t[z][m->m_type].t_name); + i = m->m_type; + st = len = + strlen(el->el_tty.t_t[z][m->m_type].t_name); + } + x = (el->el_tty.t_t[z][i].t_setmask & m->m_value) + ? '+' : '\0'; + x = (el->el_tty.t_t[z][i].t_clrmask & m->m_value) + ? '-' : x; + + if (x != '\0' || aflag) { + + cu = strlen(m->m_name) + (x != '\0') + 1; + + if (len + cu >= el->el_term.t_size.h) { + (void) fprintf(el->el_outfile, "\n%*s", + st, ""); + len = st + cu; + } else + len += cu; + + if (x != '\0') + (void) fprintf(el->el_outfile, "%c%s ", + x, m->m_name); + else + (void) fprintf(el->el_outfile, "%s ", + m->m_name); + } + } + (void) fprintf(el->el_outfile, "\n"); + return (0); + } + while (argv && (s = *argv++)) { + switch (*s) { + case '+': + case '-': + x = *s++; + break; + default: + x = '\0'; + break; + } + d = s; + for (m = ttymodes; m->m_name; m++) + if (strcmp(m->m_name, d) == 0) + break; + + if (!m->m_name) { + (void) fprintf(el->el_errfile, + "%s: Invalid argument `%s'.\n", name, d); + return (-1); + } + switch (x) { + case '+': + el->el_tty.t_t[z][m->m_type].t_setmask |= m->m_value; + el->el_tty.t_t[z][m->m_type].t_clrmask &= ~m->m_value; + break; + case '-': + el->el_tty.t_t[z][m->m_type].t_setmask &= ~m->m_value; + el->el_tty.t_t[z][m->m_type].t_clrmask |= m->m_value; + break; + default: + el->el_tty.t_t[z][m->m_type].t_setmask &= ~m->m_value; + el->el_tty.t_t[z][m->m_type].t_clrmask &= ~m->m_value; + break; + } + } + return (0); +} + + +#ifdef notyet +/* tty_printchar(): + * DEbugging routine to print the tty characters + */ +private void +tty_printchar(EditLine *el, unsigned char *s) +{ + ttyperm_t *m; + int i; + + for (i = 0; i < C_NCC; i++) { + for (m = el->el_tty.t_t; m->m_name; m++) + if (m->m_type == MD_CHAR && C_SH(i) == m->m_value) + break; + if (m->m_name) + (void) fprintf(el->el_errfile, "%s ^%c ", + m->m_name, s[i] + 'A' - 1); + if (i % 5 == 0) + (void) fprintf(el->el_errfile, "\n"); + } + (void) fprintf(el->el_errfile, "\n"); +} +#endif /* notyet */ diff --git a/main/editline/tty.h b/main/editline/tty.h new file mode 100644 index 000000000..e9597fceb --- /dev/null +++ b/main/editline/tty.h @@ -0,0 +1,484 @@ +/* $NetBSD: tty.h,v 1.9 2002/03/18 16:01:01 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)tty.h 8.1 (Berkeley) 6/4/93 + */ + +/* + * el.tty.h: Local terminal header + */ +#ifndef _h_el_tty +#define _h_el_tty + +#include "histedit.h" +#include +#include + +/* Define our own since everyone gets it wrong! */ +#define CONTROL(A) ((A) & 037) + +/* + * Aix compatible names + */ +# if defined(VWERSE) && !defined(VWERASE) +# define VWERASE VWERSE +# endif /* VWERSE && !VWERASE */ + +# if defined(VDISCRD) && !defined(VDISCARD) +# define VDISCARD VDISCRD +# endif /* VDISCRD && !VDISCARD */ + +# if defined(VFLUSHO) && !defined(VDISCARD) +# define VDISCARD VFLUSHO +# endif /* VFLUSHO && VDISCARD */ + +# if defined(VSTRT) && !defined(VSTART) +# define VSTART VSTRT +# endif /* VSTRT && ! VSTART */ + +# if defined(VSTAT) && !defined(VSTATUS) +# define VSTATUS VSTAT +# endif /* VSTAT && ! VSTATUS */ + +# ifndef ONLRET +# define ONLRET 0 +# endif /* ONLRET */ + +# ifndef TAB3 +# ifdef OXTABS +# define TAB3 OXTABS +# else +# define TAB3 0 +# endif /* OXTABS */ +# endif /* !TAB3 */ + +# if defined(OXTABS) && !defined(XTABS) +# define XTABS OXTABS +# endif /* OXTABS && !XTABS */ + +# ifndef ONLCR +# define ONLCR 0 +# endif /* ONLCR */ + +# ifndef IEXTEN +# define IEXTEN 0 +# endif /* IEXTEN */ + +# ifndef ECHOCTL +# define ECHOCTL 0 +# endif /* ECHOCTL */ + +# ifndef PARENB +# define PARENB 0 +# endif /* PARENB */ + +# ifndef EXTPROC +# define EXTPROC 0 +# endif /* EXTPROC */ + +# ifndef FLUSHO +# define FLUSHO 0 +# endif /* FLUSHO */ + + +# if defined(VDISABLE) && !defined(_POSIX_VDISABLE) +# define _POSIX_VDISABLE VDISABLE +# endif /* VDISABLE && ! _POSIX_VDISABLE */ + +/* + * Work around ISC's definition of IEXTEN which is + * XCASE! + */ +# ifdef ISC +# if defined(IEXTEN) && defined(XCASE) +# if IEXTEN == XCASE +# undef IEXTEN +# define IEXTEN 0 +# endif /* IEXTEN == XCASE */ +# endif /* IEXTEN && XCASE */ +# if defined(IEXTEN) && !defined(XCASE) +# define XCASE IEXTEN +# undef IEXTEN +# define IEXTEN 0 +# endif /* IEXTEN && !XCASE */ +# endif /* ISC */ + +/* + * Work around convex weirdness where turning off IEXTEN makes us + * lose all postprocessing! + */ +#if defined(convex) || defined(__convex__) +# if defined(IEXTEN) && IEXTEN != 0 +# undef IEXTEN +# define IEXTEN 0 +# endif /* IEXTEN != 0 */ +#endif /* convex || __convex__ */ + +/* + * So that we don't lose job control. + */ +#ifdef __SVR4 +# undef CSWTCH +#endif + +#ifndef _POSIX_VDISABLE +# define _POSIX_VDISABLE ((unsigned char) -1) +#endif /* _POSIX_VDISABLE */ + +#if !defined(CREPRINT) && defined(CRPRNT) +# define CREPRINT CRPRNT +#endif /* !CREPRINT && CRPRNT */ +#if !defined(CDISCARD) && defined(CFLUSH) +# define CDISCARD CFLUSH +#endif /* !CDISCARD && CFLUSH */ + +#ifndef CINTR +# define CINTR CONTROL('c') +#endif /* CINTR */ +#ifndef CQUIT +# define CQUIT 034 /* ^\ */ +#endif /* CQUIT */ +#ifndef CERASE +# define CERASE 0177 /* ^? */ +#endif /* CERASE */ +#ifndef CKILL +# define CKILL CONTROL('u') +#endif /* CKILL */ +#ifndef CEOF +# define CEOF CONTROL('d') +#endif /* CEOF */ +#ifndef CEOL +# define CEOL _POSIX_VDISABLE +#endif /* CEOL */ +#ifndef CEOL2 +# define CEOL2 _POSIX_VDISABLE +#endif /* CEOL2 */ +#ifndef CSWTCH +# define CSWTCH _POSIX_VDISABLE +#endif /* CSWTCH */ +#ifndef CDSWTCH +# define CDSWTCH _POSIX_VDISABLE +#endif /* CDSWTCH */ +#ifndef CERASE2 +# define CERASE2 _POSIX_VDISABLE +#endif /* CERASE2 */ +#ifndef CSTART +# define CSTART CONTROL('q') +#endif /* CSTART */ +#ifndef CSTOP +# define CSTOP CONTROL('s') +#endif /* CSTOP */ +#ifndef CSUSP +# define CSUSP CONTROL('z') +#endif /* CSUSP */ +#ifndef CDSUSP +# define CDSUSP CONTROL('y') +#endif /* CDSUSP */ + +#ifdef hpux + +# ifndef CREPRINT +# define CREPRINT _POSIX_VDISABLE +# endif /* CREPRINT */ +# ifndef CDISCARD +# define CDISCARD _POSIX_VDISABLE +# endif /* CDISCARD */ +# ifndef CLNEXT +# define CLNEXT _POSIX_VDISABLE +# endif /* CLNEXT */ +# ifndef CWERASE +# define CWERASE _POSIX_VDISABLE +# endif /* CWERASE */ + +#else /* !hpux */ + +# ifndef CREPRINT +# define CREPRINT CONTROL('r') +# endif /* CREPRINT */ +# ifndef CDISCARD +# define CDISCARD CONTROL('o') +# endif /* CDISCARD */ +# ifndef CLNEXT +# define CLNEXT CONTROL('v') +# endif /* CLNEXT */ +# ifndef CWERASE +# define CWERASE CONTROL('w') +# endif /* CWERASE */ + +#endif /* hpux */ + +#ifndef CSTATUS +# define CSTATUS CONTROL('t') +#endif /* CSTATUS */ +#ifndef CPAGE +# define CPAGE ' ' +#endif /* CPAGE */ +#ifndef CPGOFF +# define CPGOFF CONTROL('m') +#endif /* CPGOFF */ +#ifndef CKILL2 +# define CKILL2 _POSIX_VDISABLE +#endif /* CKILL2 */ +#ifndef CBRK +# ifndef masscomp +# define CBRK 0377 +# else +# define CBRK '\0' +# endif /* masscomp */ +#endif /* CBRK */ +#ifndef CMIN +# define CMIN CEOF +#endif /* CMIN */ +#ifndef CTIME +# define CTIME CEOL +#endif /* CTIME */ + +/* + * Fix for sun inconsistency. On termio VSUSP and the rest of the + * ttychars > NCC are defined. So we undefine them. + */ +#if defined(TERMIO) || defined(POSIX) +# if defined(POSIX) && defined(NCCS) +# define NUMCC NCCS +# else +# ifdef NCC +# define NUMCC NCC +# endif /* NCC */ +# endif /* POSIX && NCCS */ +# ifdef NUMCC +# ifdef VINTR +# if NUMCC <= VINTR +# undef VINTR +# endif /* NUMCC <= VINTR */ +# endif /* VINTR */ +# ifdef VQUIT +# if NUMCC <= VQUIT +# undef VQUIT +# endif /* NUMCC <= VQUIT */ +# endif /* VQUIT */ +# ifdef VERASE +# if NUMCC <= VERASE +# undef VERASE +# endif /* NUMCC <= VERASE */ +# endif /* VERASE */ +# ifdef VKILL +# if NUMCC <= VKILL +# undef VKILL +# endif /* NUMCC <= VKILL */ +# endif /* VKILL */ +# ifdef VEOF +# if NUMCC <= VEOF +# undef VEOF +# endif /* NUMCC <= VEOF */ +# endif /* VEOF */ +# ifdef VEOL +# if NUMCC <= VEOL +# undef VEOL +# endif /* NUMCC <= VEOL */ +# endif /* VEOL */ +# ifdef VEOL2 +# if NUMCC <= VEOL2 +# undef VEOL2 +# endif /* NUMCC <= VEOL2 */ +# endif /* VEOL2 */ +# ifdef VSWTCH +# if NUMCC <= VSWTCH +# undef VSWTCH +# endif /* NUMCC <= VSWTCH */ +# endif /* VSWTCH */ +# ifdef VDSWTCH +# if NUMCC <= VDSWTCH +# undef VDSWTCH +# endif /* NUMCC <= VDSWTCH */ +# endif /* VDSWTCH */ +# ifdef VERASE2 +# if NUMCC <= VERASE2 +# undef VERASE2 +# endif /* NUMCC <= VERASE2 */ +# endif /* VERASE2 */ +# ifdef VSTART +# if NUMCC <= VSTART +# undef VSTART +# endif /* NUMCC <= VSTART */ +# endif /* VSTART */ +# ifdef VSTOP +# if NUMCC <= VSTOP +# undef VSTOP +# endif /* NUMCC <= VSTOP */ +# endif /* VSTOP */ +# ifdef VWERASE +# if NUMCC <= VWERASE +# undef VWERASE +# endif /* NUMCC <= VWERASE */ +# endif /* VWERASE */ +# ifdef VSUSP +# if NUMCC <= VSUSP +# undef VSUSP +# endif /* NUMCC <= VSUSP */ +# endif /* VSUSP */ +# ifdef VDSUSP +# if NUMCC <= VDSUSP +# undef VDSUSP +# endif /* NUMCC <= VDSUSP */ +# endif /* VDSUSP */ +# ifdef VREPRINT +# if NUMCC <= VREPRINT +# undef VREPRINT +# endif /* NUMCC <= VREPRINT */ +# endif /* VREPRINT */ +# ifdef VDISCARD +# if NUMCC <= VDISCARD +# undef VDISCARD +# endif /* NUMCC <= VDISCARD */ +# endif /* VDISCARD */ +# ifdef VLNEXT +# if NUMCC <= VLNEXT +# undef VLNEXT +# endif /* NUMCC <= VLNEXT */ +# endif /* VLNEXT */ +# ifdef VSTATUS +# if NUMCC <= VSTATUS +# undef VSTATUS +# endif /* NUMCC <= VSTATUS */ +# endif /* VSTATUS */ +# ifdef VPAGE +# if NUMCC <= VPAGE +# undef VPAGE +# endif /* NUMCC <= VPAGE */ +# endif /* VPAGE */ +# ifdef VPGOFF +# if NUMCC <= VPGOFF +# undef VPGOFF +# endif /* NUMCC <= VPGOFF */ +# endif /* VPGOFF */ +# ifdef VKILL2 +# if NUMCC <= VKILL2 +# undef VKILL2 +# endif /* NUMCC <= VKILL2 */ +# endif /* VKILL2 */ +# ifdef VBRK +# if NUMCC <= VBRK +# undef VBRK +# endif /* NUMCC <= VBRK */ +# endif /* VBRK */ +# ifdef VMIN +# if NUMCC <= VMIN +# undef VMIN +# endif /* NUMCC <= VMIN */ +# endif /* VMIN */ +# ifdef VTIME +# if NUMCC <= VTIME +# undef VTIME +# endif /* NUMCC <= VTIME */ +# endif /* VTIME */ +# endif /* NUMCC */ +#endif /* !POSIX */ + +#define C_INTR 0 +#define C_QUIT 1 +#define C_ERASE 2 +#define C_KILL 3 +#define C_EOF 4 +#define C_EOL 5 +#define C_EOL2 6 +#define C_SWTCH 7 +#define C_DSWTCH 8 +#define C_ERASE2 9 +#define C_START 10 +#define C_STOP 11 +#define C_WERASE 12 +#define C_SUSP 13 +#define C_DSUSP 14 +#define C_REPRINT 15 +#define C_DISCARD 16 +#define C_LNEXT 17 +#define C_STATUS 18 +#define C_PAGE 19 +#define C_PGOFF 20 +#define C_KILL2 21 +#define C_BRK 22 +#define C_MIN 23 +#define C_TIME 24 +#define C_NCC 25 +#define C_SH(A) (1 << (A)) + +/* + * Terminal dependend data structures + */ +#define EX_IO 0 /* while we are executing */ +#define ED_IO 1 /* while we are editing */ +#define TS_IO 2 /* new mode from terminal */ +#define QU_IO 2 /* used only for quoted chars */ +#define NN_IO 3 /* The number of entries */ + +#define MD_INP 0 +#define MD_OUT 1 +#define MD_CTL 2 +#define MD_LIN 3 +#define MD_CHAR 4 +#define MD_NN 5 + +typedef struct { + const char *t_name; + u_int t_setmask; + u_int t_clrmask; +} ttyperm_t[NN_IO][MD_NN]; + +typedef unsigned char ttychar_t[NN_IO][C_NCC]; + +protected int tty_init(EditLine *); +protected void tty_end(EditLine *); +protected int tty_stty(EditLine *, int, const char **); +protected int tty_rawmode(EditLine *); +protected int tty_cookedmode(EditLine *); +protected int tty_quotemode(EditLine *); +protected int tty_noquotemode(EditLine *); +protected void tty_bind_char(EditLine *, int); + +typedef struct { + ttyperm_t t_t; + ttychar_t t_c; + struct termios t_ex, t_ed, t_ts; + int t_tabs; + int t_eight; + speed_t t_speed; + int t_mode; + unsigned char t_vdisable; +} el_tty_t; + + +#endif /* _h_el_tty */ diff --git a/main/editline/vi.c b/main/editline/vi.c new file mode 100644 index 000000000..5683c7de0 --- /dev/null +++ b/main/editline/vi.c @@ -0,0 +1,941 @@ +/* $NetBSD: vi.c,v 1.9 2002/03/18 16:01:01 christos Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Christos Zoulas of Cornell University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS OR CONTRIBUTORS 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. + */ + +#include "config.h" +#if !defined(lint) && !defined(SCCSID) +#if 0 +static char sccsid[] = "@(#)vi.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: vi.c,v 1.9 2002/03/18 16:01:01 christos Exp $"); +#endif +#endif /* not lint && not SCCSID */ + +/* + * vi.c: Vi mode commands. + */ +#include "el.h" + +private el_action_t cv_action(EditLine *, int); +private el_action_t cv_paste(EditLine *, int); + +/* cv_action(): + * Handle vi actions. + */ +private el_action_t +cv_action(EditLine *el, int c) +{ + char *cp, *kp; + + if (el->el_chared.c_vcmd.action & DELETE) { + el->el_chared.c_vcmd.action = NOP; + el->el_chared.c_vcmd.pos = 0; + + el->el_chared.c_undo.isize = 0; + el->el_chared.c_undo.dsize = 0; + kp = el->el_chared.c_undo.buf; + for (cp = el->el_line.buffer; cp < el->el_line.lastchar; cp++) { + *kp++ = *cp; + el->el_chared.c_undo.dsize++; + } + + el->el_chared.c_undo.action = INSERT; + el->el_chared.c_undo.ptr = el->el_line.buffer; + el->el_line.lastchar = el->el_line.buffer; + el->el_line.cursor = el->el_line.buffer; + if (c & INSERT) + el->el_map.current = el->el_map.key; + + return (CC_REFRESH); + } + el->el_chared.c_vcmd.pos = el->el_line.cursor; + el->el_chared.c_vcmd.action = c; + return (CC_ARGHACK); + +#ifdef notdef + /* + * I don't think that this is needed. But we keep it for now + */ + else + if (el_chared.c_vcmd.action == NOP) { + el->el_chared.c_vcmd.pos = el->el_line.cursor; + el->el_chared.c_vcmd.action = c; + return (CC_ARGHACK); + } else { + el->el_chared.c_vcmd.action = 0; + el->el_chared.c_vcmd.pos = 0; + return (CC_ERROR); + } +#endif +} + + +/* cv_paste(): + * Paste previous deletion before or after the cursor + */ +private el_action_t +cv_paste(EditLine *el, int c) +{ + char *ptr; + c_undo_t *un = &el->el_chared.c_undo; + +#ifdef DEBUG_PASTE + (void) fprintf(el->el_errfile, "Paste: %x \"%s\" +%d -%d\n", + un->action, un->buf, un->isize, un->dsize); +#endif + if (un->isize == 0) + return (CC_ERROR); + + if (!c && el->el_line.cursor < el->el_line.lastchar) + el->el_line.cursor++; + ptr = el->el_line.cursor; + + c_insert(el, (int) un->isize); + if (el->el_line.cursor + un->isize > el->el_line.lastchar) + return (CC_ERROR); + (void) memcpy(ptr, un->buf, un->isize); + return (CC_REFRESH); +} + + +/* vi_paste_next(): + * Vi paste previous deletion to the right of the cursor + * [p] + */ +protected el_action_t +/*ARGSUSED*/ +vi_paste_next(EditLine *el, int c) +{ + + return (cv_paste(el, 0)); +} + + +/* vi_paste_prev(): + * Vi paste previous deletion to the left of the cursor + * [P] + */ +protected el_action_t +/*ARGSUSED*/ +vi_paste_prev(EditLine *el, int c) +{ + + return (cv_paste(el, 1)); +} + + +/* vi_prev_space_word(): + * Vi move to the previous space delimited word + * [B] + */ +protected el_action_t +/*ARGSUSED*/ +vi_prev_space_word(EditLine *el, int c) +{ + + if (el->el_line.cursor == el->el_line.buffer) + return (CC_ERROR); + + el->el_line.cursor = cv_prev_word(el, el->el_line.cursor, + el->el_line.buffer, + el->el_state.argument, + cv__isword); + + if (el->el_chared.c_vcmd.action & DELETE) { + cv_delfini(el); + return (CC_REFRESH); + } + return (CC_CURSOR); +} + + +/* vi_prev_word(): + * Vi move to the previous word + * [B] + */ +protected el_action_t +/*ARGSUSED*/ +vi_prev_word(EditLine *el, int c) +{ + + if (el->el_line.cursor == el->el_line.buffer) + return (CC_ERROR); + + el->el_line.cursor = cv_prev_word(el, el->el_line.cursor, + el->el_line.buffer, + el->el_state.argument, + ce__isword); + + if (el->el_chared.c_vcmd.action & DELETE) { + cv_delfini(el); + return (CC_REFRESH); + } + return (CC_CURSOR); +} + + +/* vi_next_space_word(): + * Vi move to the next space delimited word + * [W] + */ +protected el_action_t +/*ARGSUSED*/ +vi_next_space_word(EditLine *el, int c) +{ + + if (el->el_line.cursor == el->el_line.lastchar) + return (CC_ERROR); + + el->el_line.cursor = cv_next_word(el, el->el_line.cursor, + el->el_line.lastchar, + el->el_state.argument, + cv__isword); + + if (el->el_map.type == MAP_VI) + if (el->el_chared.c_vcmd.action & DELETE) { + cv_delfini(el); + return (CC_REFRESH); + } + return (CC_CURSOR); +} + + +/* vi_next_word(): + * Vi move to the next word + * [w] + */ +protected el_action_t +/*ARGSUSED*/ +vi_next_word(EditLine *el, int c) +{ + + if (el->el_line.cursor == el->el_line.lastchar) + return (CC_ERROR); + + el->el_line.cursor = cv_next_word(el, el->el_line.cursor, + el->el_line.lastchar, + el->el_state.argument, + ce__isword); + + if (el->el_map.type == MAP_VI) + if (el->el_chared.c_vcmd.action & DELETE) { + cv_delfini(el); + return (CC_REFRESH); + } + return (CC_CURSOR); +} + + +/* vi_change_case(): + * Vi change case of character under the cursor and advance one character + * [~] + */ +protected el_action_t +vi_change_case(EditLine *el, int c) +{ + + if (el->el_line.cursor < el->el_line.lastchar) { + c = *el->el_line.cursor; + if (isupper(c)) + *el->el_line.cursor++ = tolower(c); + else if (islower(c)) + *el->el_line.cursor++ = toupper(c); + else + el->el_line.cursor++; + re_fastaddc(el); + return (CC_NORM); + } + return (CC_ERROR); +} + + +/* vi_change_meta(): + * Vi change prefix command + * [c] + */ +protected el_action_t +/*ARGSUSED*/ +vi_change_meta(EditLine *el, int c) +{ + + /* + * Delete with insert == change: first we delete and then we leave in + * insert mode. + */ + return (cv_action(el, DELETE | INSERT)); +} + + +/* vi_insert_at_bol(): + * Vi enter insert mode at the beginning of line + * [I] + */ +protected el_action_t +/*ARGSUSED*/ +vi_insert_at_bol(EditLine *el, int c) +{ + + el->el_line.cursor = el->el_line.buffer; + el->el_chared.c_vcmd.ins = el->el_line.cursor; + + el->el_chared.c_undo.ptr = el->el_line.cursor; + el->el_chared.c_undo.action = DELETE; + + el->el_map.current = el->el_map.key; + return (CC_CURSOR); +} + + +/* vi_replace_char(): + * Vi replace character under the cursor with the next character typed + * [r] + */ +protected el_action_t +/*ARGSUSED*/ +vi_replace_char(EditLine *el, int c) +{ + + el->el_map.current = el->el_map.key; + el->el_state.inputmode = MODE_REPLACE_1; + el->el_chared.c_undo.action = CHANGE; + el->el_chared.c_undo.ptr = el->el_line.cursor; + el->el_chared.c_undo.isize = 0; + el->el_chared.c_undo.dsize = 0; + return (CC_NORM); +} + + +/* vi_replace_mode(): + * Vi enter replace mode + * [R] + */ +protected el_action_t +/*ARGSUSED*/ +vi_replace_mode(EditLine *el, int c) +{ + + el->el_map.current = el->el_map.key; + el->el_state.inputmode = MODE_REPLACE; + el->el_chared.c_undo.action = CHANGE; + el->el_chared.c_undo.ptr = el->el_line.cursor; + el->el_chared.c_undo.isize = 0; + el->el_chared.c_undo.dsize = 0; + return (CC_NORM); +} + + +/* vi_substitute_char(): + * Vi replace character under the cursor and enter insert mode + * [r] + */ +protected el_action_t +/*ARGSUSED*/ +vi_substitute_char(EditLine *el, int c) +{ + + c_delafter(el, el->el_state.argument); + el->el_map.current = el->el_map.key; + return (CC_REFRESH); +} + + +/* vi_substitute_line(): + * Vi substitute entire line + * [S] + */ +protected el_action_t +/*ARGSUSED*/ +vi_substitute_line(EditLine *el, int c) +{ + + (void) em_kill_line(el, 0); + el->el_map.current = el->el_map.key; + return (CC_REFRESH); +} + + +/* vi_change_to_eol(): + * Vi change to end of line + * [C] + */ +protected el_action_t +/*ARGSUSED*/ +vi_change_to_eol(EditLine *el, int c) +{ + + (void) ed_kill_line(el, 0); + el->el_map.current = el->el_map.key; + return (CC_REFRESH); +} + + +/* vi_insert(): + * Vi enter insert mode + * [i] + */ +protected el_action_t +/*ARGSUSED*/ +vi_insert(EditLine *el, int c) +{ + + el->el_map.current = el->el_map.key; + + el->el_chared.c_vcmd.ins = el->el_line.cursor; + el->el_chared.c_undo.ptr = el->el_line.cursor; + el->el_chared.c_undo.action = DELETE; + + return (CC_NORM); +} + + +/* vi_add(): + * Vi enter insert mode after the cursor + * [a] + */ +protected el_action_t +/*ARGSUSED*/ +vi_add(EditLine *el, int c) +{ + int ret; + + el->el_map.current = el->el_map.key; + if (el->el_line.cursor < el->el_line.lastchar) { + el->el_line.cursor++; + if (el->el_line.cursor > el->el_line.lastchar) + el->el_line.cursor = el->el_line.lastchar; + ret = CC_CURSOR; + } else + ret = CC_NORM; + + el->el_chared.c_vcmd.ins = el->el_line.cursor; + el->el_chared.c_undo.ptr = el->el_line.cursor; + el->el_chared.c_undo.action = DELETE; + + return (ret); +} + + +/* vi_add_at_eol(): + * Vi enter insert mode at end of line + * [A] + */ +protected el_action_t +/*ARGSUSED*/ +vi_add_at_eol(EditLine *el, int c) +{ + + el->el_map.current = el->el_map.key; + el->el_line.cursor = el->el_line.lastchar; + + /* Mark where insertion begins */ + el->el_chared.c_vcmd.ins = el->el_line.lastchar; + el->el_chared.c_undo.ptr = el->el_line.lastchar; + el->el_chared.c_undo.action = DELETE; + return (CC_CURSOR); +} + + +/* vi_delete_meta(): + * Vi delete prefix command + * [d] + */ +protected el_action_t +/*ARGSUSED*/ +vi_delete_meta(EditLine *el, int c) +{ + + return (cv_action(el, DELETE)); +} + + +/* vi_end_word(): + * Vi move to the end of the current space delimited word + * [E] + */ +protected el_action_t +/*ARGSUSED*/ +vi_end_word(EditLine *el, int c) +{ + + if (el->el_line.cursor == el->el_line.lastchar) + return (CC_ERROR); + + el->el_line.cursor = cv__endword(el->el_line.cursor, + el->el_line.lastchar, el->el_state.argument); + + if (el->el_chared.c_vcmd.action & DELETE) { + el->el_line.cursor++; + cv_delfini(el); + return (CC_REFRESH); + } + return (CC_CURSOR); +} + + +/* vi_to_end_word(): + * Vi move to the end of the current word + * [e] + */ +protected el_action_t +/*ARGSUSED*/ +vi_to_end_word(EditLine *el, int c) +{ + + if (el->el_line.cursor == el->el_line.lastchar) + return (CC_ERROR); + + el->el_line.cursor = cv__endword(el->el_line.cursor, + el->el_line.lastchar, el->el_state.argument); + + if (el->el_chared.c_vcmd.action & DELETE) { + el->el_line.cursor++; + cv_delfini(el); + return (CC_REFRESH); + } + return (CC_CURSOR); +} + + +/* vi_undo(): + * Vi undo last change + * [u] + */ +protected el_action_t +/*ARGSUSED*/ +vi_undo(EditLine *el, int c) +{ + char *cp, *kp; + char temp; + int i, size; + c_undo_t *un = &el->el_chared.c_undo; + +#ifdef DEBUG_UNDO + (void) fprintf(el->el_errfile, "Undo: %x \"%s\" +%d -%d\n", + un->action, un->buf, un->isize, un->dsize); +#endif + switch (un->action) { + case DELETE: + if (un->dsize == 0) + return (CC_NORM); + + (void) memcpy(un->buf, un->ptr, un->dsize); + for (cp = un->ptr; cp <= el->el_line.lastchar; cp++) + *cp = cp[un->dsize]; + + el->el_line.lastchar -= un->dsize; + el->el_line.cursor = un->ptr; + + un->action = INSERT; + un->isize = un->dsize; + un->dsize = 0; + break; + + case DELETE | INSERT: + size = un->isize - un->dsize; + if (size > 0) + i = un->dsize; + else + i = un->isize; + cp = un->ptr; + kp = un->buf; + while (i-- > 0) { + temp = *kp; + *kp++ = *cp; + *cp++ = temp; + } + if (size > 0) { + el->el_line.cursor = cp; + c_insert(el, size); + while (size-- > 0 && cp < el->el_line.lastchar) { + temp = *kp; + *kp++ = *cp; + *cp++ = temp; + } + } else if (size < 0) { + size = -size; + for (; cp <= el->el_line.lastchar; cp++) { + *kp++ = *cp; + *cp = cp[size]; + } + el->el_line.lastchar -= size; + } + el->el_line.cursor = un->ptr; + i = un->dsize; + un->dsize = un->isize; + un->isize = i; + break; + + case INSERT: + if (un->isize == 0) + return (CC_NORM); + + el->el_line.cursor = un->ptr; + c_insert(el, (int) un->isize); + (void) memcpy(un->ptr, un->buf, un->isize); + un->action = DELETE; + un->dsize = un->isize; + un->isize = 0; + break; + + case CHANGE: + if (un->isize == 0) + return (CC_NORM); + + el->el_line.cursor = un->ptr; + size = (int) (el->el_line.cursor - el->el_line.lastchar); + if (size < un->isize) + size = un->isize; + cp = un->ptr; + kp = un->buf; + for (i = 0; i < size; i++) { + temp = *kp; + *kp++ = *cp; + *cp++ = temp; + } + un->dsize = 0; + break; + + default: + return (CC_ERROR); + } + + return (CC_REFRESH); +} + + +/* vi_command_mode(): + * Vi enter command mode (use alternative key bindings) + * [] + */ +protected el_action_t +/*ARGSUSED*/ +vi_command_mode(EditLine *el, int c) +{ + int size; + + /* [Esc] cancels pending action */ + el->el_chared.c_vcmd.ins = 0; + el->el_chared.c_vcmd.action = NOP; + el->el_chared.c_vcmd.pos = 0; + + el->el_state.doingarg = 0; + size = el->el_chared.c_undo.ptr - el->el_line.cursor; + if (size < 0) + size = -size; + if (el->el_chared.c_undo.action == (INSERT | DELETE) || + el->el_chared.c_undo.action == DELETE) + el->el_chared.c_undo.dsize = size; + else + el->el_chared.c_undo.isize = size; + + el->el_state.inputmode = MODE_INSERT; + el->el_map.current = el->el_map.alt; +#ifdef VI_MOVE + if (el->el_line.cursor > el->el_line.buffer) + el->el_line.cursor--; +#endif + return (CC_CURSOR); +} + + +/* vi_zero(): + * Vi move to the beginning of line + * [0] + */ +protected el_action_t +vi_zero(EditLine *el, int c) +{ + + if (el->el_state.doingarg) { + if (el->el_state.argument > 1000000) + return (CC_ERROR); + el->el_state.argument = + (el->el_state.argument * 10) + (c - '0'); + return (CC_ARGHACK); + } else { + el->el_line.cursor = el->el_line.buffer; + if (el->el_chared.c_vcmd.action & DELETE) { + cv_delfini(el); + return (CC_REFRESH); + } + return (CC_CURSOR); + } +} + + +/* vi_delete_prev_char(): + * Vi move to previous character (backspace) + * [^H] + */ +protected el_action_t +/*ARGSUSED*/ +vi_delete_prev_char(EditLine *el, int c) +{ + + if (el->el_chared.c_vcmd.ins == 0) + return (CC_ERROR); + + if (el->el_chared.c_vcmd.ins > + el->el_line.cursor - el->el_state.argument) + return (CC_ERROR); + + c_delbefore(el, el->el_state.argument); + el->el_line.cursor -= el->el_state.argument; + + return (CC_REFRESH); +} + + +/* vi_list_or_eof(): + * Vi list choices for completion or indicate end of file if empty line + * [^D] + */ +protected el_action_t +/*ARGSUSED*/ +vi_list_or_eof(EditLine *el, int c) +{ + +#ifdef notyet + if (el->el_line.cursor == el->el_line.lastchar && + el->el_line.cursor == el->el_line.buffer) { +#endif + term_overwrite(el, STReof, 4); /* then do a EOF */ + term__flush(); + return (CC_EOF); +#ifdef notyet + } else { + re_goto_bottom(el); + *el->el_line.lastchar = '\0'; /* just in case */ + return (CC_LIST_CHOICES); + } +#endif +} + + +/* vi_kill_line_prev(): + * Vi cut from beginning of line to cursor + * [^U] + */ +protected el_action_t +/*ARGSUSED*/ +vi_kill_line_prev(EditLine *el, int c) +{ + char *kp, *cp; + + cp = el->el_line.buffer; + kp = el->el_chared.c_kill.buf; + while (cp < el->el_line.cursor) + *kp++ = *cp++; /* copy it */ + el->el_chared.c_kill.last = kp; + c_delbefore(el, el->el_line.cursor - el->el_line.buffer); + el->el_line.cursor = el->el_line.buffer; /* zap! */ + return (CC_REFRESH); +} + + +/* vi_search_prev(): + * Vi search history previous + * [?] + */ +protected el_action_t +/*ARGSUSED*/ +vi_search_prev(EditLine *el, int c) +{ + + return (cv_search(el, ED_SEARCH_PREV_HISTORY)); +} + + +/* vi_search_next(): + * Vi search history next + * [/] + */ +protected el_action_t +/*ARGSUSED*/ +vi_search_next(EditLine *el, int c) +{ + + return (cv_search(el, ED_SEARCH_NEXT_HISTORY)); +} + + +/* vi_repeat_search_next(): + * Vi repeat current search in the same search direction + * [n] + */ +protected el_action_t +/*ARGSUSED*/ +vi_repeat_search_next(EditLine *el, int c) +{ + + if (el->el_search.patlen == 0) + return (CC_ERROR); + else + return (cv_repeat_srch(el, el->el_search.patdir)); +} + + +/* vi_repeat_search_prev(): + * Vi repeat current search in the opposite search direction + * [N] + */ +/*ARGSUSED*/ +protected el_action_t +vi_repeat_search_prev(EditLine *el, int c) +{ + + if (el->el_search.patlen == 0) + return (CC_ERROR); + else + return (cv_repeat_srch(el, + el->el_search.patdir == ED_SEARCH_PREV_HISTORY ? + ED_SEARCH_NEXT_HISTORY : ED_SEARCH_PREV_HISTORY)); +} + + +/* vi_next_char(): + * Vi move to the character specified next + * [f] + */ +protected el_action_t +/*ARGSUSED*/ +vi_next_char(EditLine *el, int c) +{ + char ch; + + if (el_getc(el, &ch) != 1) + return (ed_end_of_file(el, 0)); + + el->el_search.chadir = CHAR_FWD; + el->el_search.chacha = ch; + + return (cv_csearch_fwd(el, ch, el->el_state.argument, 0)); + +} + + +/* vi_prev_char(): + * Vi move to the character specified previous + * [F] + */ +protected el_action_t +/*ARGSUSED*/ +vi_prev_char(EditLine *el, int c) +{ + char ch; + + if (el_getc(el, &ch) != 1) + return (ed_end_of_file(el, 0)); + + el->el_search.chadir = CHAR_BACK; + el->el_search.chacha = ch; + + return (cv_csearch_back(el, ch, el->el_state.argument, 0)); +} + + +/* vi_to_next_char(): + * Vi move up to the character specified next + * [t] + */ +protected el_action_t +/*ARGSUSED*/ +vi_to_next_char(EditLine *el, int c) +{ + char ch; + + if (el_getc(el, &ch) != 1) + return (ed_end_of_file(el, 0)); + + return (cv_csearch_fwd(el, ch, el->el_state.argument, 1)); + +} + + +/* vi_to_prev_char(): + * Vi move up to the character specified previous + * [T] + */ +protected el_action_t +/*ARGSUSED*/ +vi_to_prev_char(EditLine *el, int c) +{ + char ch; + + if (el_getc(el, &ch) != 1) + return (ed_end_of_file(el, 0)); + + return (cv_csearch_back(el, ch, el->el_state.argument, 1)); +} + + +/* vi_repeat_next_char(): + * Vi repeat current character search in the same search direction + * [;] + */ +protected el_action_t +/*ARGSUSED*/ +vi_repeat_next_char(EditLine *el, int c) +{ + + if (el->el_search.chacha == 0) + return (CC_ERROR); + + return (el->el_search.chadir == CHAR_FWD + ? cv_csearch_fwd(el, el->el_search.chacha, + el->el_state.argument, 0) + : cv_csearch_back(el, el->el_search.chacha, + el->el_state.argument, 0)); +} + + +/* vi_repeat_prev_char(): + * Vi repeat current character search in the opposite search direction + * [,] + */ +protected el_action_t +/*ARGSUSED*/ +vi_repeat_prev_char(EditLine *el, int c) +{ + + if (el->el_search.chacha == 0) + return (CC_ERROR); + + return el->el_search.chadir == CHAR_BACK ? + cv_csearch_fwd(el, el->el_search.chacha, el->el_state.argument, 0) : + cv_csearch_back(el, el->el_search.chacha, el->el_state.argument, 0); +} diff --git a/main/enum.c b/main/enum.c new file mode 100644 index 000000000..fff932f2f --- /dev/null +++ b/main/enum.c @@ -0,0 +1,663 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * Funding provided by nic.at + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief ENUM Support for Asterisk + * + * \author Mark Spencer + * + * \arg Funding provided by nic.at + * + * \par Enum standards + * + * - NAPTR records: http://ietf.nri.reston.va.us/rfc/rfc2915.txt + * - DNS SRV records: http://www.ietf.org/rfc/rfc2782.txt + * - ENUM http://www.ietf.org/rfc/rfc3761.txt + * - ENUM for H.323: http://www.ietf.org/rfc/rfc3762.txt + * - ENUM SIP: http://www.ietf.org/rfc/rfc3764.txt + * - IANA ENUM Services: http://www.iana.org/assignments/enum-services + * + * \par Possible improvement + * \todo Implement a caching mechanism for multile enum lookups + * - See http://bugs.digium.com/view.php?id=6739 + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#if __APPLE_CC__ >= 1495 +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/logger.h" +#include "asterisk/options.h" +#include "asterisk/enum.h" +#include "asterisk/dns.h" +#include "asterisk/channel.h" +#include "asterisk/config.h" +#include "asterisk/utils.h" + +#ifdef __APPLE__ +#undef T_NAPTR +#define T_NAPTR 35 +#endif + +#ifdef __APPLE__ +#undef T_TXT +#define T_TXT 16 +#endif + +#define TOPLEV "e164.arpa." /*!< The IETF Enum standard root, managed by the ITU */ + +/* Linked list from config file */ +static struct enum_search { + char toplev[512]; + struct enum_search *next; +} *toplevs; + +static int enumver = 0; + +AST_MUTEX_DEFINE_STATIC(enumlock); + +struct naptr { + unsigned short order; + unsigned short pref; +} __attribute__ ((__packed__)); + +/*! \brief Parse NAPTR record information elements */ +static unsigned int parse_ie(char *data, unsigned int maxdatalen, unsigned char *src, unsigned int srclen) +{ + unsigned int len, olen; + + len = olen = (unsigned int) src[0]; + src++; + srclen--; + + if (len > srclen) { + ast_log(LOG_WARNING, "ENUM parsing failed: Wanted %d characters, got %d\n", len, srclen); + return -1; + } + + if (len > maxdatalen) + len = maxdatalen; + memcpy(data, src, len); + + return olen + 1; +} + +/*! \brief Parse DNS NAPTR record used in ENUM ---*/ +static int parse_naptr(char *dst, int dstsize, char *tech, int techsize, unsigned char *answer, int len, char *naptrinput) +{ + char tech_return[80]; + unsigned char *oanswer = answer; + char flags[512] = ""; + char services[512] = ""; + char *p; + char regexp[512] = ""; + char repl[512] = ""; + char temp[512] = ""; + char delim; + char *delim2; + char *pattern, *subst, *d; + int res; + int regexp_len, size, backref; + int d_len = sizeof(temp) - 1; + regex_t preg; + regmatch_t pmatch[9]; + + tech_return[0] = '\0'; + + dst[0] = '\0'; + + if (len < sizeof(struct naptr)) { + ast_log(LOG_WARNING, "NAPTR record length too short\n"); + return -1; + } + answer += sizeof(struct naptr); + len -= sizeof(struct naptr); + if ((res = parse_ie(flags, sizeof(flags) - 1, answer, len)) < 0) { + ast_log(LOG_WARNING, "Failed to get flags from NAPTR record\n"); + return -1; + } else { + answer += res; + len -= res; + } + if ((res = parse_ie(services, sizeof(services) - 1, answer, len)) < 0) { + ast_log(LOG_WARNING, "Failed to get services from NAPTR record\n"); + return -1; + } else { + answer += res; + len -= res; + } + if ((res = parse_ie(regexp, sizeof(regexp) - 1, answer, len)) < 0) { + ast_log(LOG_WARNING, "Failed to get regexp from NAPTR record\n"); + return -1; + } else { + answer += res; + len -= res; + } + + if ((res = dn_expand(oanswer, answer + len, answer, repl, sizeof(repl) - 1)) < 0) { + ast_log(LOG_WARNING, "Failed to expand hostname\n"); + return -1; + } + + if (option_debug > 2) /* Advanced NAPTR debugging */ + ast_log(LOG_DEBUG, "NAPTR input='%s', flags='%s', services='%s', regexp='%s', repl='%s'\n", + naptrinput, flags, services, regexp, repl); + + if (tolower(flags[0]) != 'u') { + ast_log(LOG_WARNING, "NAPTR Flag must be 'U' or 'u'.\n"); + return -1; + } + + p = strstr(services, "e2u+"); + if (p == NULL) + p = strstr(services, "E2U+"); + if (p){ + p = p + 4; + if (strchr(p, ':')){ + p = strchr(p, ':') + 1; + } + ast_copy_string(tech_return, p, sizeof(tech_return)); + } else { + + p = strstr(services, "+e2u"); + if (p == NULL) + p = strstr(services, "+E2U"); + if (p) { + *p = 0; + p = strchr(services, ':'); + if (p) + *p = 0; + ast_copy_string(tech_return, services, sizeof(tech_return)); + } + } + + /* DEDBUGGING STUB + ast_copy_string(regexp, "!^\\+43(.*)$!\\1@bla.fasel!", sizeof(regexp) - 1); + */ + + regexp_len = strlen(regexp); + if (regexp_len < 7) { + ast_log(LOG_WARNING, "Regex too short to be meaningful.\n"); + return -1; + } + + + delim = regexp[0]; + delim2 = strchr(regexp + 1, delim); + if ((delim2 == NULL) || (regexp[regexp_len-1] != delim)) { + ast_log(LOG_WARNING, "Regex delimiter error (on \"%s\").\n",regexp); + return -1; + } + + pattern = regexp + 1; + *delim2 = 0; + subst = delim2 + 1; + regexp[regexp_len-1] = 0; + +/* + * now do the regex wizardry. + */ + + if (regcomp(&preg, pattern, REG_EXTENDED | REG_NEWLINE)) { + ast_log(LOG_WARNING, "NAPTR Regex compilation error (regex = \"%s\").\n",regexp); + return -1; + } + + if (preg.re_nsub > 9) { + ast_log(LOG_WARNING, "NAPTR Regex compilation error: too many subs.\n"); + regfree(&preg); + return -1; + } + + if (regexec(&preg, naptrinput, 9, pmatch, 0)) { + ast_log(LOG_WARNING, "NAPTR Regex match failed.\n"); + regfree(&preg); + return -1; + } + regfree(&preg); + + d = temp; + d_len--; + while (*subst && (d_len > 0)) { + if ((subst[0] == '\\') && isdigit(subst[1]) && (pmatch[subst[1]-'0'].rm_so != -1)) { + backref = subst[1]-'0'; + size = pmatch[backref].rm_eo - pmatch[backref].rm_so; + if (size > d_len) { + ast_log(LOG_WARNING, "Not enough space during NAPTR regex substitution.\n"); + return -1; + } + memcpy(d, naptrinput + pmatch[backref].rm_so, size); + d += size; + d_len -= size; + subst += 2; + } else if (isprint(*subst)) { + *d++ = *subst++; + d_len--; + } else { + ast_log(LOG_WARNING, "Error during regex substitution.\n"); + return -1; + } + } + *d = 0; + ast_copy_string(dst, temp, dstsize); + dst[dstsize - 1] = '\0'; + + if (*tech != '\0'){ /* check if it is requested NAPTR */ + if (!strncasecmp(tech, "ALL", techsize)){ + return 1; /* return or count any RR */ + } + if (!strncasecmp(tech_return, tech, sizeof(tech_return)txt = NULL; + c->txtlen = 0; + return 0; + } + + /* skip over first byte, as for some reason it's a vertical tab character */ + answer += 1; + len -= 1; + + /* answer is not null-terminated, but should be */ + /* this is safe to do, as answer has extra bytes on the end we can + * safely overwrite with a null */ + answer[len] = '\0'; + /* now increment len so that len includes the null, so that we can + * compare apples to apples */ + len +=1; + + /* finally, copy the answer into c->txt */ + ast_copy_string(c->txt, (const char *) answer, len < c->txtlen ? len : (c->txtlen)); + + /* just to be safe, let's make sure c->txt is null terminated */ + c->txt[(c->txtlen)-1] = '\0'; + + return 1; +} + +/*! \brief Callback from ENUM lookup function */ +static int enum_callback(void *context, unsigned char *answer, int len, unsigned char *fullanswer) +{ + struct enum_context *c = context; + void *p = NULL; + int res; + + res = parse_naptr(c->dst, c->dstlen, c->tech, c->techlen, answer, len, c->naptrinput); + + if (res < 0) { + ast_log(LOG_WARNING, "Failed to parse naptr :(\n"); + return -1; + } else if (res > 0 && !ast_strlen_zero(c->dst)){ /* ok, we got needed NAPTR */ + if (c->options & ENUMLOOKUP_OPTIONS_COUNT){ /* counting RRs */ + c->position++; + snprintf(c->dst, c->dstlen, "%d", c->position); + } else { + if ((p = ast_realloc(c->naptr_rrs, sizeof(*c->naptr_rrs) * (c->naptr_rrs_count + 1)))) { + c->naptr_rrs = p; + memcpy(&c->naptr_rrs[c->naptr_rrs_count].naptr, answer, sizeof(c->naptr_rrs->naptr)); + c->naptr_rrs[c->naptr_rrs_count].result = strdup(c->dst); + c->naptr_rrs[c->naptr_rrs_count].tech = strdup(c->tech); + c->naptr_rrs[c->naptr_rrs_count].sort_pos = c->naptr_rrs_count; + c->naptr_rrs_count++; + } + c->dst[0] = 0; + } + return 0; + } + + if (c->options & ENUMLOOKUP_OPTIONS_COUNT) { /* counting RRs */ + snprintf(c->dst, c->dstlen, "%d", c->position); + } + + return 0; +} + +/*! \brief ENUM lookup */ +int ast_get_enum(struct ast_channel *chan, const char *number, char *dst, int dstlen, char *tech, int techlen, char* suffix, char* options) +{ + struct enum_context context; + char tmp[259 + 512]; + char naptrinput[512]; + int pos = strlen(number) - 1; + int newpos = 0; + int ret = -1; + struct enum_search *s = NULL; + int version = -1; + /* for ISN rewrite */ + char *p1 = NULL; + char *p2 = NULL; + int k = 0; + int i = 0; + int z = 0; + + ast_copy_string(naptrinput, number[0] == 'n' ? number+1 : number, sizeof(naptrinput)); + + context.naptrinput = naptrinput; /* The number */ + context.dst = dst; /* Return string */ + context.dstlen = dstlen; + context.tech = tech; + context.techlen = techlen; + context.options = 0; + context.position = 1; + context.naptr_rrs = NULL; + context.naptr_rrs_count = 0; + + if (options != NULL) { + if (*options == 'c') { + context.options = ENUMLOOKUP_OPTIONS_COUNT; + context.position = 0; + } else { + context.position = atoi(options); + if (context.position < 1) + context.position = 1; + } + } + + if (pos > 128) + pos = 128; + + /* ISN rewrite */ + p1 = strchr(number, '*'); + + if (number[0] == 'n') { /* do not perform ISN rewrite ('n' is testing flag) */ + p1 = NULL; + k = 1; /* strip 'n' from number */ + } + + if (p1 != NULL) { + p2 = p1+1; + while (p1 > number){ + p1--; + tmp[newpos++] = *p1; + tmp[newpos++] = '.'; + } + if (*p2) { + while(*p2 && newpos < 128){ + tmp[newpos++] = *p2; + p2++; + } + tmp[newpos++] = '.'; + } + + } else { + while (pos >= k) { + if (isdigit(number[pos])) { + tmp[newpos++] = number[pos]; + tmp[newpos++] = '.'; + } + pos--; + } + } + + if (chan && ast_autoservice_start(chan) < 0) + return -1; + + for (;;) { + ast_mutex_lock(&enumlock); + if (version != enumver) { + /* Ooh, a reload... */ + s = toplevs; + version = enumver; + } else { + s = s->next; + } + ast_copy_string(tmp + newpos, suffix ? suffix : s->toplev, sizeof(tmp) - newpos); + ast_mutex_unlock(&enumlock); + if (!s) + break; + ret = ast_search_dns(&context, tmp, C_IN, T_NAPTR, enum_callback); + if (ret > 0) + break; + if (suffix != NULL) + break; + } + if (ret < 0) { + ast_log(LOG_DEBUG, "No such number found: %s (%s)\n", tmp, strerror(errno)); + strcpy(dst, "0"); + ret = 0; + } + + if (context.naptr_rrs_count >= context.position && ! (context.options & ENUMLOOKUP_OPTIONS_COUNT)) { + /* sort array by NAPTR order/preference */ + for (k = 0; k < context.naptr_rrs_count; k++) { + for (i = 0; i < context.naptr_rrs_count; i++) { + /* use order first and then preference to compare */ + if ((ntohs(context.naptr_rrs[k].naptr.order) < ntohs(context.naptr_rrs[i].naptr.order) + && context.naptr_rrs[k].sort_pos > context.naptr_rrs[i].sort_pos) + || (ntohs(context.naptr_rrs[k].naptr.order) > ntohs(context.naptr_rrs[i].naptr.order) + && context.naptr_rrs[k].sort_pos < context.naptr_rrs[i].sort_pos)){ + z = context.naptr_rrs[k].sort_pos; + context.naptr_rrs[k].sort_pos = context.naptr_rrs[i].sort_pos; + context.naptr_rrs[i].sort_pos = z; + continue; + } + if (ntohs(context.naptr_rrs[k].naptr.order) == ntohs(context.naptr_rrs[i].naptr.order)) { + if ((ntohs(context.naptr_rrs[k].naptr.pref) < ntohs(context.naptr_rrs[i].naptr.pref) + && context.naptr_rrs[k].sort_pos > context.naptr_rrs[i].sort_pos) + || (ntohs(context.naptr_rrs[k].naptr.pref) > ntohs(context.naptr_rrs[i].naptr.pref) + && context.naptr_rrs[k].sort_pos < context.naptr_rrs[i].sort_pos)){ + z = context.naptr_rrs[k].sort_pos; + context.naptr_rrs[k].sort_pos = context.naptr_rrs[i].sort_pos; + context.naptr_rrs[i].sort_pos = z; + } + } + } + } + for (k = 0; k < context.naptr_rrs_count; k++) { + if (context.naptr_rrs[k].sort_pos == context.position-1) { + ast_copy_string(context.dst, context.naptr_rrs[k].result, dstlen); + ast_copy_string(context.tech, context.naptr_rrs[k].tech, techlen); + break; + } + } + } else if (!(context.options & ENUMLOOKUP_OPTIONS_COUNT)) { + context.dst[0] = 0; + } + if (chan) + ret |= ast_autoservice_stop(chan); + + for (k = 0; k < context.naptr_rrs_count; k++) { + free(context.naptr_rrs[k].result); + free(context.naptr_rrs[k].tech); + } + + free(context.naptr_rrs); + + return ret; +} + +/*! \brief Get TXT record from DNS. + Really has nothing to do with enum, but anyway... + */ +int ast_get_txt(struct ast_channel *chan, const char *number, char *dst, int dstlen, char *tech, int techlen, char *txt, int txtlen) +{ + struct enum_context context; + char tmp[259 + 512]; + char naptrinput[512] = "+"; + int pos = strlen(number) - 1; + int newpos = 0; + int ret = -1; + struct enum_search *s = NULL; + int version = -1; + + strncat(naptrinput, number, sizeof(naptrinput) - 2); + + context.naptrinput = naptrinput; + context.dst = dst; + context.dstlen = dstlen; + context.tech = tech; + context.techlen = techlen; + context.txt = txt; + context.txtlen = txtlen; + + if (pos > 128) + pos = 128; + while (pos >= 0) { + tmp[newpos++] = number[pos--]; + tmp[newpos++] = '.'; + } + + if (chan && ast_autoservice_start(chan) < 0) + return -1; + + for (;;) { + ast_mutex_lock(&enumlock); + if (version != enumver) { + /* Ooh, a reload... */ + s = toplevs; + version = enumver; + } else { + s = s->next; + } + if (s) { + ast_copy_string(tmp + newpos, s->toplev, sizeof(tmp) - newpos); + } + ast_mutex_unlock(&enumlock); + if (!s) + break; + + ret = ast_search_dns(&context, tmp, C_IN, T_TXT, txt_callback); + if (ret > 0) + break; + } + if (ret < 0) { + if (option_debug > 1) + ast_log(LOG_DEBUG, "No such number found in ENUM: %s (%s)\n", tmp, strerror(errno)); + ret = 0; + } + if (chan) + ret |= ast_autoservice_stop(chan); + return ret; +} + +/*! \brief Add enum tree to linked list */ +static struct enum_search *enum_newtoplev(char *s) +{ + struct enum_search *tmp; + + if ((tmp = ast_calloc(1, sizeof(*tmp)))) { + ast_copy_string(tmp->toplev, s, sizeof(tmp->toplev)); + } + return tmp; +} + +/*! \brief Initialize the ENUM support subsystem */ +int ast_enum_init(void) +{ + struct ast_config *cfg; + struct enum_search *s, *sl; + struct ast_variable *v; + + /* Destroy existing list */ + ast_mutex_lock(&enumlock); + s = toplevs; + while(s) { + sl = s; + s = s->next; + free(sl); + } + toplevs = NULL; + cfg = ast_config_load("enum.conf"); + if (cfg) { + sl = NULL; + v = ast_variable_browse(cfg, "general"); + while(v) { + if (!strcasecmp(v->name, "search")) { + s = enum_newtoplev(v->value); + if (s) { + if (sl) + sl->next = s; + else + toplevs = s; + sl = s; + } + } + v = v->next; + } + ast_config_destroy(cfg); + } else { + toplevs = enum_newtoplev(TOPLEV); + } + enumver++; + ast_mutex_unlock(&enumlock); + return 0; +} + +int ast_enum_reload(void) +{ + return ast_enum_init(); +} diff --git a/main/file.c b/main/file.c new file mode 100644 index 000000000..69b7ec197 --- /dev/null +++ b/main/file.c @@ -0,0 +1,1161 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Generic File Format Support. + * + * \author Mark Spencer + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/frame.h" +#include "asterisk/file.h" +#include "asterisk/cli.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/sched.h" +#include "asterisk/options.h" +#include "asterisk/translate.h" +#include "asterisk/utils.h" +#include "asterisk/lock.h" +#include "asterisk/app.h" +#include "asterisk/pbx.h" +#include "asterisk/linkedlists.h" +#include "asterisk/module.h" + +/* + * The following variable controls the layout of localized sound files. + * If 0, use the historical layout with prefix just before the filename + * (i.e. digits/en/1.gsm , digits/it/1.gsm or default to digits/1.gsm), + * if 1 put the prefix at the beginning of the filename + * (i.e. en/digits/1.gsm, it/digits/1.gsm or default to digits/1.gsm). + * The latter permits a language to be entirely in one directory. + */ +int ast_language_is_prefix; + +static AST_LIST_HEAD_STATIC(formats, ast_format); + +int __ast_format_register(const struct ast_format *f, struct ast_module *mod) +{ + struct ast_format *tmp; + + if (AST_LIST_LOCK(&formats)) { + ast_log(LOG_WARNING, "Unable to lock format list\n"); + return -1; + } + AST_LIST_TRAVERSE(&formats, tmp, list) { + if (!strcasecmp(f->name, tmp->name)) { + AST_LIST_UNLOCK(&formats); + ast_log(LOG_WARNING, "Tried to register '%s' format, already registered\n", f->name); + return -1; + } + } + if (!(tmp = ast_calloc(1, sizeof(*tmp)))) { + AST_LIST_UNLOCK(&formats); + return -1; + } + *tmp = *f; + tmp->module = mod; + if (tmp->buf_size) { + /* + * Align buf_size properly, rounding up to the machine-specific + * alignment for pointers. + */ + struct _test_align { void *a, *b; } p; + int align = (char *)&p.b - (char *)&p.a; + tmp->buf_size = ((f->buf_size + align - 1)/align)*align; + } + + memset(&tmp->list, 0, sizeof(tmp->list)); + + AST_LIST_INSERT_HEAD(&formats, tmp, list); + AST_LIST_UNLOCK(&formats); + if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Registered file format %s, extension(s) %s\n", f->name, f->exts); + + return 0; +} + +int ast_format_unregister(const char *name) +{ + struct ast_format *tmp; + int res = -1; + + if (AST_LIST_LOCK(&formats)) { + ast_log(LOG_WARNING, "Unable to lock format list\n"); + return -1; + } + AST_LIST_TRAVERSE_SAFE_BEGIN(&formats, tmp, list) { + if (!strcasecmp(name, tmp->name)) { + AST_LIST_REMOVE_CURRENT(&formats, list); + free(tmp); + res = 0; + } + } + AST_LIST_TRAVERSE_SAFE_END + AST_LIST_UNLOCK(&formats); + + if (!res) { + if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Unregistered format %s\n", name); + } else + ast_log(LOG_WARNING, "Tried to unregister format %s, already unregistered\n", name); + + return res; +} + +int ast_stopstream(struct ast_channel *tmp) +{ + /* Stop a running stream if there is one */ + if (tmp->stream) { + ast_closestream(tmp->stream); + tmp->stream = NULL; + if (tmp->oldwriteformat && ast_set_write_format(tmp, tmp->oldwriteformat)) + ast_log(LOG_WARNING, "Unable to restore format back to %d\n", tmp->oldwriteformat); + } + return 0; +} + +int ast_writestream(struct ast_filestream *fs, struct ast_frame *f) +{ + int res = -1; + int alt = 0; + if (f->frametype == AST_FRAME_VIDEO) { + if (fs->fmt->format < AST_FORMAT_MAX_AUDIO) { + /* This is the audio portion. Call the video one... */ + if (!fs->vfs && fs->filename) { + const char *type = ast_getformatname(f->subclass & ~0x1); + fs->vfs = ast_writefile(fs->filename, type, NULL, fs->flags, 0, fs->mode); + ast_log(LOG_DEBUG, "Opened video output file\n"); + } + if (fs->vfs) + return ast_writestream(fs->vfs, f); + /* else ignore */ + return 0; + } else { + /* Might / might not have mark set */ + alt = 1; + } + } else if (f->frametype != AST_FRAME_VOICE) { + ast_log(LOG_WARNING, "Tried to write non-voice frame\n"); + return -1; + } + if (((fs->fmt->format | alt) & f->subclass) == f->subclass) { + res = fs->fmt->write(fs, f); + if (res < 0) + ast_log(LOG_WARNING, "Natural write failed\n"); + else if (res > 0) + ast_log(LOG_WARNING, "Huh??\n"); + } else { + /* XXX If they try to send us a type of frame that isn't the normal frame, and isn't + the one we've setup a translator for, we do the "wrong thing" XXX */ + if (fs->trans && f->subclass != fs->lastwriteformat) { + ast_translator_free_path(fs->trans); + fs->trans = NULL; + } + if (!fs->trans) + fs->trans = ast_translator_build_path(fs->fmt->format, f->subclass); + if (!fs->trans) + ast_log(LOG_WARNING, "Unable to translate to format %s, source format %s\n", + fs->fmt->name, ast_getformatname(f->subclass)); + else { + struct ast_frame *trf; + fs->lastwriteformat = f->subclass; + /* Get the translated frame but don't consume the original in case they're using it on another stream */ + trf = ast_translate(fs->trans, f, 0); + if (trf) { + res = fs->fmt->write(fs, trf); + if (res) + ast_log(LOG_WARNING, "Translated frame write failed\n"); + } else + res = 0; + } + } + return res; +} + +static int copy(const char *infile, const char *outfile) +{ + int ifd, ofd, len; + char buf[4096]; /* XXX make it lerger. */ + + if ((ifd = open(infile, O_RDONLY)) < 0) { + ast_log(LOG_WARNING, "Unable to open %s in read-only mode\n", infile); + return -1; + } + if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, 0600)) < 0) { + ast_log(LOG_WARNING, "Unable to open %s in write-only mode\n", outfile); + close(ifd); + return -1; + } + while ( (len = read(ifd, buf, sizeof(buf)) ) ) { + int res; + if (len < 0) { + ast_log(LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno)); + break; + } + /* XXX handle partial writes */ + res = write(ofd, buf, len); + if (res != len) { + ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno)); + len = -1; /* error marker */ + break; + } + } + close(ifd); + close(ofd); + if (len < 0) { + unlink(outfile); + return -1; /* error */ + } + return 0; /* success */ +} + +/*! + * \brief construct a filename. Absolute pathnames are preserved, + * relative names are prefixed by the sounds/ directory. + * The wav49 suffix is replaced by 'WAV'. + * Returns a malloc'ed string to be freed by the caller. + */ +static char *build_filename(const char *filename, const char *ext) +{ + char *fn = NULL; + + if (!strcmp(ext, "wav49")) + ext = "WAV"; + + if (filename[0] == '/') + asprintf(&fn, "%s.%s", filename, ext); + else + asprintf(&fn, "%s/sounds/%s.%s", + ast_config_AST_DATA_DIR, filename, ext); + return fn; +} + +/* compare type against the list 'exts' */ +/* XXX need a better algorithm */ +static int exts_compare(const char *exts, const char *type) +{ + char tmp[256]; + char *stringp = tmp, *ext; + + ast_copy_string(tmp, exts, sizeof(tmp)); + while ((ext = strsep(&stringp, "|"))) { + if (!strcmp(ext, type)) + return 1; + } + + return 0; +} + +static struct ast_filestream *get_filestream(struct ast_format *fmt, FILE *bfile) +{ + struct ast_filestream *s; + + int l = sizeof(*s) + fmt->buf_size + fmt->desc_size; /* total allocation size */ + if ( (s = ast_calloc(1, l)) == NULL) + return NULL; + s->fmt = fmt; + s->f = bfile; + + if (fmt->desc_size) + s->private = ((char *)(s+1)) + fmt->buf_size; + if (fmt->buf_size) + s->buf = (char *)(s+1); + s->fr.src = fmt->name; + return s; +} + +/* + * Default implementations of open and rewrite. + * Only use them if you don't have expensive stuff to do. + */ +enum wrap_fn { WRAP_OPEN, WRAP_REWRITE }; + +static int fn_wrapper(struct ast_filestream *s, const char *comment, enum wrap_fn mode) +{ + struct ast_format *f = s->fmt; + int ret = -1; + + if (mode == WRAP_OPEN && f->open && f->open(s)) + ast_log(LOG_WARNING, "Unable to open format %s\n", f->name); + else if (mode == WRAP_REWRITE && f->rewrite && f->rewrite(s, comment)) + ast_log(LOG_WARNING, "Unable to rewrite format %s\n", f->name); + else { + /* preliminary checks succeed. update usecount */ + ast_module_ref(f->module); + ret = 0; + } + return ret; +} + +static int rewrite_wrapper(struct ast_filestream *s, const char *comment) +{ + return fn_wrapper(s, comment, WRAP_REWRITE); +} + +static int open_wrapper(struct ast_filestream *s) +{ + return fn_wrapper(s, NULL, WRAP_OPEN); +} + +enum file_action { + ACTION_EXISTS = 1, /* return matching format if file exists, 0 otherwise */ + ACTION_DELETE, /* delete file, return 0 on success, -1 on error */ + ACTION_RENAME, /* rename file. return 0 on success, -1 on error */ + ACTION_OPEN, + ACTION_COPY /* copy file. return 0 on success, -1 on error */ +}; + +/*! + * \brief perform various actions on a file. Second argument + * arg2 depends on the command: + * unused for EXISTS and DELETE + * destination file name (const char *) for COPY and RENAME + * struct ast_channel * for OPEN + * if fmt is NULL, OPEN will return the first matching entry, + * whereas other functions will run on all matching entries. + */ +static int ast_filehelper(const char *filename, const void *arg2, const char *fmt, const enum file_action action) +{ + struct ast_format *f; + int res = (action == ACTION_EXISTS) ? 0 : -1; + + if (AST_LIST_LOCK(&formats)) { + ast_log(LOG_WARNING, "Unable to lock format list\n"); + return res; + } + /* Check for a specific format */ + AST_LIST_TRAVERSE(&formats, f, list) { + char *stringp, *ext = NULL; + + if (fmt && !exts_compare(f->exts, fmt)) + continue; + + /* Look for a file matching the supported extensions. + * The file must exist, and for OPEN, must match + * one of the formats supported by the channel. + */ + stringp = ast_strdupa(f->exts); /* this is in the stack so does not need to be freed */ + while ( (ext = strsep(&stringp, "|")) ) { + struct stat st; + char *fn = build_filename(filename, ext); + + if (fn == NULL) + continue; + + if ( stat(fn, &st) ) { /* file not existent */ + free(fn); + continue; + } + /* for 'OPEN' we need to be sure that the format matches + * what the channel can process + */ + if (action == ACTION_OPEN) { + struct ast_channel *chan = (struct ast_channel *)arg2; + FILE *bfile; + struct ast_filestream *s; + + if ( !(chan->writeformat & f->format) && + !(f->format >= AST_FORMAT_MAX_AUDIO && fmt)) { + free(fn); + continue; /* not a supported format */ + } + if ( (bfile = fopen(fn, "r")) == NULL) { + free(fn); + continue; /* cannot open file */ + } + s = get_filestream(f, bfile); + if (!s) { + fclose(bfile); + free(fn); /* cannot allocate descriptor */ + continue; + } + if (open_wrapper(s)) { + fclose(bfile); + free(fn); + free(s); + continue; /* cannot run open on file */ + } + /* ok this is good for OPEN */ + res = 1; /* found */ + s->lasttimeout = -1; + s->fmt = f; + s->trans = NULL; + s->filename = NULL; + if (s->fmt->format < AST_FORMAT_MAX_AUDIO) + chan->stream = s; + else + chan->vstream = s; + break; + } + switch (action) { + case ACTION_OPEN: + break; /* will never get here */ + + case ACTION_EXISTS: /* return the matching format */ + res |= f->format; + break; + + case ACTION_DELETE: + if ( (res = unlink(fn)) ) + ast_log(LOG_WARNING, "unlink(%s) failed: %s\n", fn, strerror(errno)); + break; + + case ACTION_RENAME: + case ACTION_COPY: { + char *nfn = build_filename((const char *)arg2, ext); + if (!nfn) + ast_log(LOG_WARNING, "Out of memory\n"); + else { + res = action == ACTION_COPY ? copy(fn, nfn) : rename(fn, nfn); + if (res) + ast_log(LOG_WARNING, "%s(%s,%s) failed: %s\n", + action == ACTION_COPY ? "copy" : "rename", + fn, nfn, strerror(errno)); + free(nfn); + } + } + break; + + default: + ast_log(LOG_WARNING, "Unknown helper %d\n", action); + } + free(fn); + } + } + AST_LIST_UNLOCK(&formats); + return res; +} + +/*! + * \brief helper routine to locate a file with a given format + * and language preference. + * Try preflang, preflang with stripped '_' suffix, or NULL. + * In the standard asterisk, language goes just before the last component. + * In an alternative configuration, the language should be a prefix + * to the actual filename. + * + * The last parameter(s) point to a buffer of sufficient size, + * which on success is filled with the matching filename. + */ +static int fileexists_core(const char *filename, const char *fmt, const char *preflang, + char *buf, int buflen) +{ + int res = -1; + int langlen; /* length of language string */ + const char *c = strrchr(filename, '/'); + int offset = c ? c - filename + 1 : 0; /* points right after the last '/' */ + + if (preflang == NULL) + preflang = ""; + langlen = strlen(preflang); + + if (buflen < langlen + strlen(filename) + 2) { + ast_log(LOG_WARNING, "buffer too small\n"); + buf[0] = '\0'; /* set to empty */ + buf = alloca(langlen + strlen(filename) + 2); /* room for everything */ + } + if (buf == NULL) + return 0; + buf[0] = '\0'; + for (;;) { + if (ast_language_is_prefix) { /* new layout */ + if (langlen) { + strcpy(buf, preflang); + buf[langlen] = '/'; + strcpy(buf + langlen + 1, filename); + } else + strcpy(buf, filename); /* first copy the full string */ + } else { /* old layout */ + strcpy(buf, filename); /* first copy the full string */ + if (langlen) { + /* insert the language and suffix if needed */ + strcpy(buf + offset, preflang); + sprintf(buf + offset + langlen, "/%s", filename + offset); + } + } + res = ast_filehelper(buf, NULL, fmt, ACTION_EXISTS); + if (res > 0) /* found format */ + break; + if (langlen == 0) /* no more formats */ + break; + if (preflang[langlen] == '_') /* we are on the local suffix */ + langlen = 0; /* try again with no language */ + else + langlen = (c = strchr(preflang, '_')) ? c - preflang : 0; + } + return res; +} + +struct ast_filestream *ast_openstream(struct ast_channel *chan, const char *filename, const char *preflang) +{ + return ast_openstream_full(chan, filename, preflang, 0); +} + +struct ast_filestream *ast_openstream_full(struct ast_channel *chan, const char *filename, const char *preflang, int asis) +{ + /* + * Use fileexists_core() to find a file in a compatible + * language and format, set up a suitable translator, + * and open the stream. + */ + int fmts, res, buflen; + char *buf; + + if (!asis) { + /* do this first, otherwise we detect the wrong writeformat */ + ast_stopstream(chan); + if (chan->generator) + ast_deactivate_generator(chan); + } + if (preflang == NULL) + preflang = ""; + buflen = strlen(preflang) + strlen(filename) + 2; + buf = alloca(buflen); + if (buf == NULL) + return NULL; + fmts = fileexists_core(filename, NULL, preflang, buf, buflen); + if (fmts > 0) + fmts &= AST_FORMAT_AUDIO_MASK; + if (fmts < 1) { + ast_log(LOG_WARNING, "File %s does not exist in any format\n", filename); + return NULL; + } + chan->oldwriteformat = chan->writeformat; + /* Set the channel to a format we can work with */ + res = ast_set_write_format(chan, fmts); + res = ast_filehelper(buf, chan, NULL, ACTION_OPEN); + if (res >= 0) + return chan->stream; + return NULL; +} + +struct ast_filestream *ast_openvstream(struct ast_channel *chan, const char *filename, const char *preflang) +{ + /* As above, but for video. But here we don't have translators + * so we must enforce a format. + */ + unsigned int format; + char *buf; + int buflen; + + if (preflang == NULL) + preflang = ""; + buflen = strlen(preflang) + strlen(filename) + 2; + buf = alloca(buflen); + if (buf == NULL) + return NULL; + + for (format = AST_FORMAT_MAX_AUDIO << 1; format <= AST_FORMAT_MAX_VIDEO; format = format << 1) { + int fd; + const char *fmt; + + if (!(chan->nativeformats & format)) + continue; + fmt = ast_getformatname(format); + if ( fileexists_core(filename, fmt, preflang, buf, buflen) < 1) /* no valid format */ + continue; + fd = ast_filehelper(buf, chan, fmt, ACTION_OPEN); + if (fd >= 0) + return chan->vstream; + ast_log(LOG_WARNING, "File %s has video but couldn't be opened\n", filename); + } + return NULL; +} + +struct ast_frame *ast_readframe(struct ast_filestream *s) +{ + struct ast_frame *f = NULL; + int whennext = 0; + if (s && s->fmt) + f = s->fmt->read(s, &whennext); + return f; +} + +static int ast_readaudio_callback(void *data) +{ + struct ast_filestream *s = data; + int whennext = 0; + + while(!whennext) { + struct ast_frame *fr = s->fmt->read(s, &whennext); + if (!fr /* stream complete */ || ast_write(s->owner, fr) /* error writing */) { + if (fr) + ast_log(LOG_WARNING, "Failed to write frame\n"); + s->owner->streamid = -1; +#ifdef HAVE_ZAPTEL + ast_settimeout(s->owner, 0, NULL, NULL); +#endif + return 0; + } + } + if (whennext != s->lasttimeout) { +#ifdef HAVE_ZAPTEL + if (s->owner->timingfd > -1) + ast_settimeout(s->owner, whennext, ast_readaudio_callback, s); + else +#endif + s->owner->streamid = ast_sched_add(s->owner->sched, whennext/8, ast_readaudio_callback, s); + s->lasttimeout = whennext; + return 0; + } + return 1; +} + +static int ast_readvideo_callback(void *data) +{ + struct ast_filestream *s = data; + int whennext = 0; + + while (!whennext) { + struct ast_frame *fr = s->fmt->read(s, &whennext); + if (!fr || ast_write(s->owner, fr)) { /* no stream or error, as above */ + if (fr) + ast_log(LOG_WARNING, "Failed to write frame\n"); + s->owner->vstreamid = -1; + return 0; + } + } + if (whennext != s->lasttimeout) { + s->owner->vstreamid = ast_sched_add(s->owner->sched, whennext/8, ast_readvideo_callback, s); + s->lasttimeout = whennext; + return 0; + } + return 1; +} + +int ast_applystream(struct ast_channel *chan, struct ast_filestream *s) +{ + s->owner = chan; + return 0; +} + +int ast_playstream(struct ast_filestream *s) +{ + if (s->fmt->format < AST_FORMAT_MAX_AUDIO) + ast_readaudio_callback(s); + else + ast_readvideo_callback(s); + return 0; +} + +int ast_seekstream(struct ast_filestream *fs, off_t sample_offset, int whence) +{ + return fs->fmt->seek(fs, sample_offset, whence); +} + +int ast_truncstream(struct ast_filestream *fs) +{ + return fs->fmt->trunc(fs); +} + +off_t ast_tellstream(struct ast_filestream *fs) +{ + return fs->fmt->tell(fs); +} + +int ast_stream_fastforward(struct ast_filestream *fs, off_t ms) +{ + return ast_seekstream(fs, ms * DEFAULT_SAMPLES_PER_MS, SEEK_CUR); +} + +int ast_stream_rewind(struct ast_filestream *fs, off_t ms) +{ + return ast_seekstream(fs, -ms * DEFAULT_SAMPLES_PER_MS, SEEK_CUR); +} + +int ast_closestream(struct ast_filestream *f) +{ + char *cmd = NULL; + size_t size = 0; + /* Stop a running stream if there is one */ + if (f->owner) { + if (f->fmt->format < AST_FORMAT_MAX_AUDIO) { + f->owner->stream = NULL; + if (f->owner->streamid > -1) + ast_sched_del(f->owner->sched, f->owner->streamid); + f->owner->streamid = -1; +#ifdef HAVE_ZAPTEL + ast_settimeout(f->owner, 0, NULL, NULL); +#endif + } else { + f->owner->vstream = NULL; + if (f->owner->vstreamid > -1) + ast_sched_del(f->owner->sched, f->owner->vstreamid); + f->owner->vstreamid = -1; + } + } + /* destroy the translator on exit */ + if (f->trans) + ast_translator_free_path(f->trans); + + if (f->realfilename && f->filename) { + size = strlen(f->filename) + strlen(f->realfilename) + 15; + cmd = alloca(size); + memset(cmd,0,size); + snprintf(cmd,size,"/bin/mv -f %s %s",f->filename,f->realfilename); + ast_safe_system(cmd); + } + + if (f->filename) + free(f->filename); + if (f->realfilename) + free(f->realfilename); + if (f->fmt->close) + f->fmt->close(f); + fclose(f->f); + if (f->vfs) + ast_closestream(f->vfs); + ast_module_unref(f->fmt->module); + free(f); + return 0; +} + + +/* + * Look the various language-specific places where a file could exist. + */ +int ast_fileexists(const char *filename, const char *fmt, const char *preflang) +{ + char *buf; + int buflen; + + if (preflang == NULL) + preflang = ""; + buflen = strlen(preflang) + strlen(filename) + 2; /* room for everything */ + buf = alloca(buflen); + if (buf == NULL) + return 0; + return fileexists_core(filename, fmt, preflang, buf, buflen); +} + +int ast_filedelete(const char *filename, const char *fmt) +{ + return ast_filehelper(filename, NULL, fmt, ACTION_DELETE); +} + +int ast_filerename(const char *filename, const char *filename2, const char *fmt) +{ + return ast_filehelper(filename, filename2, fmt, ACTION_RENAME); +} + +int ast_filecopy(const char *filename, const char *filename2, const char *fmt) +{ + return ast_filehelper(filename, filename2, fmt, ACTION_COPY); +} + +int ast_streamfile(struct ast_channel *chan, const char *filename, const char *preflang) +{ + struct ast_filestream *fs; + struct ast_filestream *vfs=NULL; + char fmt[256]; + + fs = ast_openstream(chan, filename, preflang); + if (fs) + vfs = ast_openvstream(chan, filename, preflang); + if (vfs) + ast_log(LOG_DEBUG, "Ooh, found a video stream, too, format %s\n", ast_getformatname(vfs->fmt->format)); + if (fs){ + if (ast_applystream(chan, fs)) + return -1; + if (vfs && ast_applystream(chan, vfs)) + return -1; + if (ast_playstream(fs)) + return -1; + if (vfs && ast_playstream(vfs)) + return -1; +#if 1 + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Playing '%s' (language '%s')\n", filename, preflang ? preflang : "default"); +#endif + return 0; + } + ast_log(LOG_WARNING, "Unable to open %s (format %s): %s\n", filename, ast_getformatname_multiple(fmt, sizeof(fmt), chan->nativeformats), strerror(errno)); + return -1; +} + +struct ast_filestream *ast_readfile(const char *filename, const char *type, const char *comment, int flags, int check, mode_t mode) +{ + FILE *bfile; + struct ast_format *f; + struct ast_filestream *fs = NULL; + char *fn; + + if (AST_LIST_LOCK(&formats)) { + ast_log(LOG_WARNING, "Unable to lock format list\n"); + return NULL; + } + + AST_LIST_TRAVERSE(&formats, f, list) { + fs = NULL; + if (!exts_compare(f->exts, type)) + continue; + + fn = build_filename(filename, type); + errno = 0; + bfile = fopen(fn, "r"); + if (!bfile || (fs = get_filestream(f, bfile)) == NULL || + open_wrapper(fs) ) { + ast_log(LOG_WARNING, "Unable to open %s\n", fn); + fclose(bfile); + free(fn); + if (fs) + free(fs); + continue; + } + /* found it */ + fs->trans = NULL; + fs->fmt = f; + fs->flags = flags; + fs->mode = mode; + fs->filename = strdup(filename); + fs->vfs = NULL; + break; + } + + AST_LIST_UNLOCK(&formats); + if (!fs) + ast_log(LOG_WARNING, "No such format '%s'\n", type); + + return fs; +} + +struct ast_filestream *ast_writefile(const char *filename, const char *type, const char *comment, int flags, int check, mode_t mode) +{ + int fd, myflags = 0; + /* compiler claims this variable can be used before initialization... */ + FILE *bfile = NULL; + struct ast_format *f; + struct ast_filestream *fs = NULL; + char *buf = NULL; + size_t size = 0; + int format_found = 0; + + if (AST_LIST_LOCK(&formats)) { + ast_log(LOG_WARNING, "Unable to lock format list\n"); + return NULL; + } + + /* set the O_TRUNC flag if and only if there is no O_APPEND specified */ + /* We really can't use O_APPEND as it will break WAV header updates */ + if (flags & O_APPEND) { + flags &= ~O_APPEND; + } else { + myflags = O_TRUNC; + } + + myflags |= O_WRONLY | O_CREAT; + + /* XXX need to fix this - we should just do the fopen, + * not open followed by fdopen() + */ + AST_LIST_TRAVERSE(&formats, f, list) { + char *fn, *orig_fn = NULL; + if (fs) + break; + + if (!exts_compare(f->exts, type)) + continue; + else + format_found = 1; + + fn = build_filename(filename, type); + fd = open(fn, flags | myflags, mode); + if (fd > -1) { + /* fdopen() the resulting file stream */ + bfile = fdopen(fd, ((flags | myflags) & O_RDWR) ? "w+" : "w"); + if (!bfile) { + ast_log(LOG_WARNING, "Whoa, fdopen failed: %s!\n", strerror(errno)); + close(fd); + fd = -1; + } + } + + if (ast_opt_cache_record_files && (fd > -1)) { + char *c; + + fclose(bfile); /* this also closes fd */ + /* + We touch orig_fn just as a place-holder so other things (like vmail) see the file is there. + What we are really doing is writing to record_cache_dir until we are done then we will mv the file into place. + */ + orig_fn = ast_strdupa(fn); + for (c = fn; *c; c++) + if (*c == '/') + *c = '_'; + + size = strlen(fn) + strlen(record_cache_dir) + 2; + buf = alloca(size); + strcpy(buf, record_cache_dir); + strcat(buf, "/"); + strcat(buf, fn); + free(fn); + fn = buf; + fd = open(fn, flags | myflags, mode); + if (fd > -1) { + /* fdopen() the resulting file stream */ + bfile = fdopen(fd, ((flags | myflags) & O_RDWR) ? "w+" : "w"); + if (!bfile) { + ast_log(LOG_WARNING, "Whoa, fdopen failed: %s!\n", strerror(errno)); + close(fd); + fd = -1; + } + } + } + if (fd > -1) { + errno = 0; + fs = get_filestream(f, bfile); + if (!fs || rewrite_wrapper(fs, comment)) { + ast_log(LOG_WARNING, "Unable to rewrite %s\n", fn); + close(fd); + if (orig_fn) { + unlink(fn); + unlink(orig_fn); + } + if (fs) + free(fs); + } + fs->trans = NULL; + fs->fmt = f; + fs->flags = flags; + fs->mode = mode; + if (orig_fn) { + fs->realfilename = strdup(orig_fn); + fs->filename = strdup(fn); + } else { + fs->realfilename = NULL; + fs->filename = strdup(filename); + } + fs->vfs = NULL; + /* If truncated, we'll be at the beginning; if not truncated, then append */ + f->seek(fs, 0, SEEK_END); + } else if (errno != EEXIST) { + ast_log(LOG_WARNING, "Unable to open file %s: %s\n", fn, strerror(errno)); + if (orig_fn) + unlink(orig_fn); + } + /* if buf != NULL then fn is already free and pointing to it */ + if (!buf) + free(fn); + } + + AST_LIST_UNLOCK(&formats); + + if (!format_found) + ast_log(LOG_WARNING, "No such format '%s'\n", type); + + return fs; +} + +/*! + * \brief the core of all waitstream() functions + */ +static int waitstream_core(struct ast_channel *c, const char *breakon, + const char *forward, const char *rewind, int skip_ms, + int audiofd, int cmdfd, const char *context) +{ + if (!breakon) + breakon = ""; + if (!forward) + forward = ""; + if (!rewind) + rewind = ""; + + while (c->stream) { + int res; + int ms = ast_sched_wait(c->sched); + if (ms < 0 && !c->timingfunc) { + ast_stopstream(c); + break; + } + if (ms < 0) + ms = 1000; + if (!cmdfd) { + res = ast_waitfor(c, ms); + if (res < 0) { + ast_log(LOG_WARNING, "Select failed (%s)\n", strerror(errno)); + return res; + } + } else { + int outfd; + struct ast_channel *rchan = ast_waitfor_nandfds(&c, 1, &cmdfd, (cmdfd > -1) ? 1 : 0, NULL, &outfd, &ms); + if (!rchan && (outfd < 0) && (ms)) { + /* Continue */ + if (errno == EINTR) + continue; + ast_log(LOG_WARNING, "Wait failed (%s)\n", strerror(errno)); + return -1; + } else if (outfd > -1) { /* this requires cmdfd set */ + /* The FD we were watching has something waiting */ + return 1; + } + /* if rchan is set, it is 'c' */ + res = rchan ? 1 : 0; /* map into 'res' values */ + } + if (res > 0) { + struct ast_frame *fr = ast_read(c); + if (!fr) + return -1; + switch(fr->frametype) { + case AST_FRAME_DTMF: + if (context) { + const char exten[2] = { fr->subclass, '\0' }; + if (ast_exists_extension(c, context, exten, 1, c->cid.cid_num)) { + ast_frfree(fr); + return res; + } + } else { + res = fr->subclass; + if (strchr(forward,res)) { + ast_stream_fastforward(c->stream, skip_ms); + } else if (strchr(rewind,res)) { + ast_stream_rewind(c->stream, skip_ms); + } else if (strchr(breakon, res)) { + ast_frfree(fr); + return res; + } + } + break; + case AST_FRAME_CONTROL: + switch(fr->subclass) { + case AST_CONTROL_HANGUP: + ast_frfree(fr); + return -1; + case AST_CONTROL_RINGING: + case AST_CONTROL_ANSWER: + case AST_CONTROL_VIDUPDATE: + /* Unimportant */ + break; + default: + ast_log(LOG_WARNING, "Unexpected control subclass '%d'\n", fr->subclass); + } + case AST_FRAME_VOICE: + /* Write audio if appropriate */ + if (audiofd > -1) + write(audiofd, fr->data, fr->datalen); + } + /* Ignore all others */ + ast_frfree(fr); + } + ast_sched_runq(c->sched); + } + return (c->_softhangup ? -1 : 0); +} + +int ast_waitstream_fr(struct ast_channel *c, const char *breakon, const char *forward, const char *rewind, int ms) +{ + return waitstream_core(c, breakon, forward, rewind, ms, + -1 /* no audiofd */, -1 /* no cmdfd */, NULL /* no context */); +} + +int ast_waitstream(struct ast_channel *c, const char *breakon) +{ + return waitstream_core(c, breakon, NULL, NULL, 0, -1, -1, NULL); +} + +int ast_waitstream_full(struct ast_channel *c, const char *breakon, int audiofd, int cmdfd) +{ + return waitstream_core(c, breakon, NULL, NULL, 0, + audiofd, cmdfd, NULL /* no context */); +} + +int ast_waitstream_exten(struct ast_channel *c, const char *context) +{ + /* Waitstream, with return in the case of a valid 1 digit extension */ + /* in the current or specified context being pressed */ + + if (!context) + context = c->context; + return waitstream_core(c, NULL, NULL, NULL, 0, + -1, -1, context); +} + +/* + * if the file name is non-empty, try to play it. + * Return 0 if success, -1 if error, digit if interrupted by a digit. + * If digits == "" then we can simply check for non-zero. + */ +int ast_stream_and_wait(struct ast_channel *chan, const char *file, + const char *language, const char *digits) +{ + int res = 0; + if (!ast_strlen_zero(file)) { + res = ast_streamfile(chan, file, language); + if (!res) + res = ast_waitstream(chan, digits); + } + return res; +} + +static int show_file_formats(int fd, int argc, char *argv[]) +{ +#define FORMAT "%-10s %-10s %-20s\n" +#define FORMAT2 "%-10s %-10s %-20s\n" + struct ast_format *f; + int count_fmt = 0; + + if (argc != 3) + return RESULT_SHOWUSAGE; + ast_cli(fd, FORMAT, "Format", "Name", "Extensions"); + + if (AST_LIST_LOCK(&formats)) { + ast_log(LOG_WARNING, "Unable to lock format list\n"); + return -1; + } + + AST_LIST_TRAVERSE(&formats, f, list) { + ast_cli(fd, FORMAT2, ast_getformatname(f->format), f->name, f->exts); + count_fmt++; + } + AST_LIST_UNLOCK(&formats); + ast_cli(fd, "%d file formats registered.\n", count_fmt); + return RESULT_SUCCESS; +#undef FORMAT +#undef FORMAT2 +} + +struct ast_cli_entry show_file = +{ + { "show", "file", "formats" }, + show_file_formats, + "Displays file formats", + "Usage: show file formats\n" + " displays currently registered file formats (if any)\n" +}; + +int ast_file_init(void) +{ + ast_cli_register(&show_file); + return 0; +} diff --git a/main/fixedjitterbuf.c b/main/fixedjitterbuf.c new file mode 100644 index 000000000..1d7a5cc30 --- /dev/null +++ b/main/fixedjitterbuf.c @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2005, Attractel OOD + * + * Contributors: + * Slav Klenov + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + * + * A license has been granted to Digium (via disclaimer) for the use of + * this code. + */ + +/*! \file + * + * \brief Jitterbuffering algorithm. + * + * \author Slav Klenov + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include + +#include "asterisk/utils.h" +#include "fixedjitterbuf.h" + +#undef FIXED_JB_DEBUG + +#ifdef FIXED_JB_DEBUG +#define ASSERT(a) +#else +#define ASSERT(a) assert(a) +#endif + +/*! \brief private fixed_jb structure */ +struct fixed_jb +{ + struct fixed_jb_frame *frames; + struct fixed_jb_frame *tail; + struct fixed_jb_conf conf; + long rxcore; + long delay; + long next_delivery; + int force_resynch; +}; + + +static struct fixed_jb_frame *alloc_jb_frame(struct fixed_jb *jb); +static void release_jb_frame(struct fixed_jb *jb, struct fixed_jb_frame *frame); +static void get_jb_head(struct fixed_jb *jb, struct fixed_jb_frame *frame); +static int resynch_jb(struct fixed_jb *jb, void *data, long ms, long ts, long now); + +static inline struct fixed_jb_frame *alloc_jb_frame(struct fixed_jb *jb) +{ + return ast_calloc(1, sizeof(struct fixed_jb_frame)); +} + +static inline void release_jb_frame(struct fixed_jb *jb, struct fixed_jb_frame *frame) +{ + free(frame); +} + +static void get_jb_head(struct fixed_jb *jb, struct fixed_jb_frame *frame) +{ + struct fixed_jb_frame *fr; + + /* unlink the frame */ + fr = jb->frames; + jb->frames = fr->next; + if (jb->frames) { + jb->frames->prev = NULL; + } else { + /* the jb is empty - update tail */ + jb->tail = NULL; + } + + /* update next */ + jb->next_delivery = fr->delivery + fr->ms; + + /* copy the destination */ + memcpy(frame, fr, sizeof(struct fixed_jb_frame)); + + /* and release the frame */ + release_jb_frame(jb, fr); +} + + +struct fixed_jb *fixed_jb_new(struct fixed_jb_conf *conf) +{ + struct fixed_jb *jb; + + if (!(jb = ast_calloc(1, sizeof(*jb)))) + return NULL; + + /* First copy our config */ + memcpy(&jb->conf, conf, sizeof(struct fixed_jb_conf)); + + /* we dont need the passed config anymore - continue working with the saved one */ + conf = &jb->conf; + + /* validate the configuration */ + if (conf->jbsize < 1) + conf->jbsize = FIXED_JB_SIZE_DEFAULT; + + if (conf->resync_threshold < 1) + conf->resync_threshold = FIXED_JB_RESYNCH_THRESHOLD_DEFAULT; + + /* Set the constant delay to the jitterbuf */ + jb->delay = conf->jbsize; + + return jb; +} + + +void fixed_jb_destroy(struct fixed_jb *jb) +{ + /* jitterbuf MUST be empty before it can be destroyed */ + ASSERT(jb->frames == NULL); + + free(jb); +} + + +static int resynch_jb(struct fixed_jb *jb, void *data, long ms, long ts, long now) +{ + long diff, offset; + struct fixed_jb_frame *frame; + + /* If jb is empty, just reinitialize the jb */ + if (!jb->frames) { + /* debug check: tail should also be NULL */ + ASSERT(jb->tail == NULL); + + return fixed_jb_put_first(jb, data, ms, ts, now); + } + + /* Adjust all jb state just as the new frame is with delivery = the delivery of the last + frame (e.g. this one with max delivery) + the length of the last frame. */ + + /* Get the diff in timestamps */ + diff = ts - jb->tail->ts; + + /* Ideally this should be just the length of the last frame. The deviation is the desired + offset */ + offset = diff - jb->tail->ms; + + /* Do we really need to resynch, or this is just a frame for dropping? */ + if (!jb->force_resynch && (offset < jb->conf.resync_threshold && offset > -jb->conf.resync_threshold)) + return FIXED_JB_DROP; + + /* Reset the force resynch flag */ + jb->force_resynch = 0; + + /* apply the offset to the jb state */ + jb->rxcore -= offset; + frame = jb->frames; + while (frame) { + frame->ts += offset; + frame = frame->next; + } + + /* now jb_put() should add the frame at a last position */ + return fixed_jb_put(jb, data, ms, ts, now); +} + + +void fixed_jb_set_force_resynch(struct fixed_jb *jb) +{ + jb->force_resynch = 1; +} + + +int fixed_jb_put_first(struct fixed_jb *jb, void *data, long ms, long ts, long now) +{ + /* this is our first frame - set the base of the receivers time */ + jb->rxcore = now - ts; + + /* init next for a first time - it should be the time the first frame should be played */ + jb->next_delivery = now + jb->delay; + + /* put the frame */ + return fixed_jb_put(jb, data, ms, ts, now); +} + + +int fixed_jb_put(struct fixed_jb *jb, void *data, long ms, long ts, long now) +{ + struct fixed_jb_frame *frame, *next, *newframe; + long delivery; + + /* debug check the validity of the input params */ + ASSERT(data != NULL); + /* do not allow frames shorter than 2 ms */ + ASSERT(ms >= 2); + ASSERT(ts >= 0); + ASSERT(now >= 0); + + delivery = jb->rxcore + jb->delay + ts; + + /* check if the new frame is not too late */ + if (delivery < jb->next_delivery) { + /* should drop the frame, but let first resynch_jb() check if this is not a jump in ts, or + the force resynch flag was not set. */ + return resynch_jb(jb, data, ms, ts, now); + } + + /* what if the delivery time is bigger than next + delay? Seems like a frame for the future. + However, allow more resync_threshold ms in advance */ + if (delivery > jb->next_delivery + jb->delay + jb->conf.resync_threshold) { + /* should drop the frame, but let first resynch_jb() check if this is not a jump in ts, or + the force resynch flag was not set. */ + return resynch_jb(jb, data, ms, ts, now); + } + + /* find the right place in the frames list, sorted by delivery time */ + frame = jb->tail; + while (frame && frame->delivery > delivery) { + frame = frame->prev; + } + + /* Check if the new delivery time is not covered already by the chosen frame */ + if (frame && (frame->delivery == delivery || + delivery < frame->delivery + frame->ms || + (frame->next && delivery + ms > frame->next->delivery))) + { + /* TODO: Should we check for resynch here? Be careful to do not allow threshold smaller than + the size of the jb */ + + /* should drop the frame, but let first resynch_jb() check if this is not a jump in ts, or + the force resynch flag was not set. */ + return resynch_jb(jb, data, ms, ts, now); + } + + /* Reset the force resynch flag */ + jb->force_resynch = 0; + + /* Get a new frame */ + newframe = alloc_jb_frame(jb); + newframe->data = data; + newframe->ts = ts; + newframe->ms = ms; + newframe->delivery = delivery; + + /* and insert it right on place */ + if (frame) { + next = frame->next; + frame->next = newframe; + if (next) { + newframe->next = next; + next->prev = newframe; + } else { + /* insert after the last frame - should update tail */ + jb->tail = newframe; + newframe->next = NULL; + } + newframe->prev = frame; + + return FIXED_JB_OK; + } else if (!jb->frames) { + /* the frame list is empty or thats just the first frame ever */ + /* tail should also be NULL is that case */ + ASSERT(jb->tail == NULL); + jb->frames = jb->tail = newframe; + newframe->next = NULL; + newframe->prev = NULL; + + return FIXED_JB_OK; + } else { + /* insert on a first position - should update frames head */ + newframe->next = jb->frames; + newframe->prev = NULL; + jb->frames->prev = newframe; + jb->frames = newframe; + + return FIXED_JB_OK; + } +} + + +int fixed_jb_get(struct fixed_jb *jb, struct fixed_jb_frame *frame, long now, long interpl) +{ + ASSERT(now >= 0); + ASSERT(interpl >= 2); + + if (now < jb->next_delivery) { + /* too early for the next frame */ + return FIXED_JB_NOFRAME; + } + + /* Is the jb empty? */ + if (!jb->frames) { + /* should interpolate a frame */ + /* update next */ + jb->next_delivery += interpl; + + return FIXED_JB_INTERP; + } + + /* Isn't it too late for the first frame available in the jb? */ + if (now > jb->frames->delivery + jb->frames->ms) { + /* yes - should drop this frame and update next to point the next frame (get_jb_head() does it) */ + get_jb_head(jb, frame); + + return FIXED_JB_DROP; + } + + /* isn't it too early to play the first frame available? */ + if (now < jb->frames->delivery) { + /* yes - should interpolate one frame */ + /* update next */ + jb->next_delivery += interpl; + + return FIXED_JB_INTERP; + } + + /* we have a frame for playing now (get_jb_head() updates next) */ + get_jb_head(jb, frame); + + return FIXED_JB_OK; +} + + +long fixed_jb_next(struct fixed_jb *jb) +{ + return jb->next_delivery; +} + + +int fixed_jb_remove(struct fixed_jb *jb, struct fixed_jb_frame *frameout) +{ + if (!jb->frames) + return FIXED_JB_NOFRAME; + + get_jb_head(jb, frameout); + + return FIXED_JB_OK; +} diff --git a/main/fixedjitterbuf.h b/main/fixedjitterbuf.h new file mode 100644 index 000000000..541e99d2d --- /dev/null +++ b/main/fixedjitterbuf.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2005, Attractel OOD + * + * Contributors: + * Slav Klenov + * + * Copyright on this file is disclaimed to Digium for inclusion in Asterisk + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Jitterbuffering algorithm. + * + */ + +#ifndef _FIXEDJITTERBUF_H_ +#define _FIXEDJITTERBUF_H_ + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + + +/* return codes */ +enum { + FIXED_JB_OK, + FIXED_JB_DROP, + FIXED_JB_INTERP, + FIXED_JB_NOFRAME +}; + + +/* defaults */ +#define FIXED_JB_SIZE_DEFAULT 200 +#define FIXED_JB_RESYNCH_THRESHOLD_DEFAULT 1000 + + +/* jb configuration properties */ +struct fixed_jb_conf +{ + long jbsize; + long resync_threshold; +}; + + +struct fixed_jb_frame +{ + void *data; + long ts; + long ms; + long delivery; + struct fixed_jb_frame *next; + struct fixed_jb_frame *prev; +}; + + +struct fixed_jb; + + +/* jb interface */ + +struct fixed_jb * fixed_jb_new(struct fixed_jb_conf *conf); + +void fixed_jb_destroy(struct fixed_jb *jb); + +int fixed_jb_put_first(struct fixed_jb *jb, void *data, long ms, long ts, long now); + +int fixed_jb_put(struct fixed_jb *jb, void *data, long ms, long ts, long now); + +int fixed_jb_get(struct fixed_jb *jb, struct fixed_jb_frame *frame, long now, long interpl); + +long fixed_jb_next(struct fixed_jb *jb); + +int fixed_jb_remove(struct fixed_jb *jb, struct fixed_jb_frame *frameout); + +void fixed_jb_set_force_resynch(struct fixed_jb *jb); + + +#if defined(__cplusplus) || defined(c_plusplus) +} +#endif + +#endif /* _FIXEDJITTERBUF_H_ */ diff --git a/main/frame.c b/main/frame.c new file mode 100644 index 000000000..0cc44a7eb --- /dev/null +++ b/main/frame.c @@ -0,0 +1,1369 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Frame and codec manipulation routines + * + * \author Mark Spencer + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/frame.h" +#include "asterisk/logger.h" +#include "asterisk/options.h" +#include "asterisk/channel.h" +#include "asterisk/cli.h" +#include "asterisk/term.h" +#include "asterisk/utils.h" + +#ifdef TRACE_FRAMES +static int headers = 0; +static struct ast_frame *headerlist = NULL; +AST_MUTEX_DEFINE_STATIC(framelock); +#endif + +#define SMOOTHER_SIZE 8000 + +enum frame_type { + TYPE_HIGH, /* 0x0 */ + TYPE_LOW, /* 0x1 */ + TYPE_SILENCE, /* 0x2 */ + TYPE_DONTSEND /* 0x3 */ +}; + +#define TYPE_MASK 0x3 + +struct ast_smoother { + int size; + int format; + int readdata; + int optimizablestream; + int flags; + float samplesperbyte; + struct ast_frame f; + struct timeval delivery; + char data[SMOOTHER_SIZE]; + char framedata[SMOOTHER_SIZE + AST_FRIENDLY_OFFSET]; + struct ast_frame *opt; + int len; +}; + +/*! \brief Definition of supported media formats (codecs) */ +static struct ast_format_list { + int visible; /*!< Can we see this entry */ + int bits; /*!< bitmask value */ + char *name; /*!< short name */ + char *desc; /*!< Description */ +} AST_FORMAT_LIST[] = { /*!< Bit number: comment - Bit numbers are hard coded in show_codec() */ + { 1, AST_FORMAT_G723_1 , "g723" , "G.723.1"}, /*!< 1 */ + { 1, AST_FORMAT_GSM, "gsm" , "GSM"}, /*!< 2: codec_gsm.c */ + { 1, AST_FORMAT_ULAW, "ulaw", "G.711 u-law" }, /*!< 3: codec_ulaw.c */ + { 1, AST_FORMAT_ALAW, "alaw", "G.711 A-law" }, /*!< 4: codec_alaw.c */ + { 1, AST_FORMAT_G726, "g726", "G.726 RFC3551" },/*!< 5: codec_g726.c */ + { 1, AST_FORMAT_ADPCM, "adpcm" , "ADPCM"}, /*!< 6: codec_adpcm.c */ + { 1, AST_FORMAT_SLINEAR, "slin", "16 bit Signed Linear PCM"}, /*!< 7 */ + { 1, AST_FORMAT_LPC10, "lpc10", "LPC10" }, /*!< 8: codec_lpc10.c */ + { 1, AST_FORMAT_G729A, "g729", "G.729A" }, /*!< 9: Binary commercial distribution */ + { 1, AST_FORMAT_SPEEX, "speex", "SpeeX" }, /*!< 10: codec_speex.c */ + { 1, AST_FORMAT_ILBC, "ilbc", "iLBC"}, /*!< 11: codec_ilbc.c */ + { 1, AST_FORMAT_G726_AAL2, "g726aal2", "G.726 AAL2" }, /*!< 12: codec_g726.c */ + { 0, 0, "nothing", "undefined" }, + { 0, 0, "nothing", "undefined" }, + { 0, 0, "nothing", "undefined" }, + { 0, 0, "nothing", "undefined" }, + { 0, AST_FORMAT_MAX_AUDIO, "maxaudio", "Maximum audio format" }, + { 1, AST_FORMAT_JPEG, "jpeg", "JPEG image"}, /*!< 17: See format_jpeg.c */ + { 1, AST_FORMAT_PNG, "png", "PNG image"}, /*!< 18: Image format */ + { 1, AST_FORMAT_H261, "h261", "H.261 Video" }, /*!< 19: Video Passthrough */ + { 1, AST_FORMAT_H263, "h263", "H.263 Video" }, /*!< 20: Passthrough support, see format_h263.c */ + { 1, AST_FORMAT_H263_PLUS, "h263p", "H.263+ Video" }, /*!< 21: See format_h263.c */ + { 1, AST_FORMAT_H264, "h264", "H.264 Video" }, /*!< 22: Passthrough support, see format_h263.c */ + { 0, 0, "nothing", "undefined" }, + { 0, 0, "nothing", "undefined" }, + { 0, 0, "nothing", "undefined" }, + { 0, AST_FORMAT_MAX_VIDEO, "maxvideo", "Maximum video format" }, +}; + +struct ast_frame ast_null_frame = { AST_FRAME_NULL, }; + +void ast_smoother_reset(struct ast_smoother *s, int size) +{ + memset(s, 0, sizeof(*s)); + s->size = size; +} + +struct ast_smoother *ast_smoother_new(int size) +{ + struct ast_smoother *s; + if (size < 1) + return NULL; + if ((s = ast_malloc(sizeof(*s)))) + ast_smoother_reset(s, size); + return s; +} + +int ast_smoother_get_flags(struct ast_smoother *s) +{ + return s->flags; +} + +void ast_smoother_set_flags(struct ast_smoother *s, int flags) +{ + s->flags = flags; +} + +int __ast_smoother_feed(struct ast_smoother *s, struct ast_frame *f, int swap) +{ + if (f->frametype != AST_FRAME_VOICE) { + ast_log(LOG_WARNING, "Huh? Can't smooth a non-voice frame!\n"); + return -1; + } + if (!s->format) { + s->format = f->subclass; + s->samplesperbyte = (float)f->samples / (float)f->datalen; + } else if (s->format != f->subclass) { + ast_log(LOG_WARNING, "Smoother was working on %d format frames, now trying to feed %d?\n", s->format, f->subclass); + return -1; + } + if (s->len + f->datalen > SMOOTHER_SIZE) { + ast_log(LOG_WARNING, "Out of smoother space\n"); + return -1; + } + if (((f->datalen == s->size) || ((f->datalen < 10) && (s->flags & AST_SMOOTHER_FLAG_G729))) + && !s->opt && (f->offset >= AST_MIN_OFFSET)) { + if (!s->len) { + /* Optimize by sending the frame we just got + on the next read, thus eliminating the douple + copy */ + s->opt = f; + return 0; + } else { + s->optimizablestream++; + if (s->optimizablestream > 10) { + /* For the past 10 rounds, we have input and output + frames of the correct size for this smoother, yet + we were unable to optimize because there was still + some cruft left over. Lets just drop the cruft so + we can move to a fully optimized path */ + s->len = 0; + s->opt = f; + return 0; + } + } + } else + s->optimizablestream = 0; + if (s->flags & AST_SMOOTHER_FLAG_G729) { + if (s->len % 10) { + ast_log(LOG_NOTICE, "Dropping extra frame of G.729 since we already have a VAD frame at the end\n"); + return 0; + } + } + if (swap) + ast_swapcopy_samples(s->data+s->len, f->data, f->samples); + else + memcpy(s->data + s->len, f->data, f->datalen); + /* If either side is empty, reset the delivery time */ + if (!s->len || ast_tvzero(f->delivery) || ast_tvzero(s->delivery)) /* XXX really ? */ + s->delivery = f->delivery; + s->len += f->datalen; + return 0; +} + +struct ast_frame *ast_smoother_read(struct ast_smoother *s) +{ + struct ast_frame *opt; + int len; + + /* IF we have an optimization frame, send it */ + if (s->opt) { + if (s->opt->offset < AST_FRIENDLY_OFFSET) + ast_log(LOG_WARNING, "Returning a frame of inappropriate offset (%d).", + s->opt->offset); + opt = s->opt; + s->opt = NULL; + return opt; + } + + /* Make sure we have enough data */ + if (s->len < s->size) { + /* Or, if this is a G.729 frame with VAD on it, send it immediately anyway */ + if (!((s->flags & AST_SMOOTHER_FLAG_G729) && (s->size % 10))) + return NULL; + } + len = s->size; + if (len > s->len) + len = s->len; + /* Make frame */ + s->f.frametype = AST_FRAME_VOICE; + s->f.subclass = s->format; + s->f.data = s->framedata + AST_FRIENDLY_OFFSET; + s->f.offset = AST_FRIENDLY_OFFSET; + s->f.datalen = len; + /* Samples will be improper given VAD, but with VAD the concept really doesn't even exist */ + s->f.samples = len * s->samplesperbyte; /* XXX rounding */ + s->f.delivery = s->delivery; + /* Fill Data */ + memcpy(s->f.data, s->data, len); + s->len -= len; + /* Move remaining data to the front if applicable */ + if (s->len) { + /* In principle this should all be fine because if we are sending + G.729 VAD, the next timestamp will take over anyawy */ + memmove(s->data, s->data + len, s->len); + if (!ast_tvzero(s->delivery)) { + /* If we have delivery time, increment it, otherwise, leave it at 0 */ + s->delivery = ast_tvadd(s->delivery, ast_samp2tv(s->f.samples, 8000)); + } + } + /* Return frame */ + return &s->f; +} + +void ast_smoother_free(struct ast_smoother *s) +{ + free(s); +} + +static struct ast_frame *ast_frame_header_new(void) +{ + struct ast_frame *f = ast_calloc(1, sizeof(*f)); +#ifdef TRACE_FRAMES + if (f) { + f->prev = NULL; + ast_mutex_lock(&framelock); + headers++; + f->next = headerlist; + if (headerlist) + headerlist->prev = f; + headerlist = f; + ast_mutex_unlock(&framelock); + } +#endif + return f; +} + +/*! + * \todo Important: I should be made more efficient. Frame headers should + * most definitely be cached + */ +void ast_frfree(struct ast_frame *fr) +{ + if (fr->mallocd & AST_MALLOCD_DATA) { + if (fr->data) + free(fr->data - fr->offset); + } + if (fr->mallocd & AST_MALLOCD_SRC) { + if (fr->src) + free((char *)fr->src); + } + if (fr->mallocd & AST_MALLOCD_HDR) { +#ifdef TRACE_FRAMES + ast_mutex_lock(&framelock); + headers--; + if (fr->next) + fr->next->prev = fr->prev; + if (fr->prev) + fr->prev->next = fr->next; + else + headerlist = fr->next; + ast_mutex_unlock(&framelock); +#endif + free(fr); + } +} + +/*! + * \brief 'isolates' a frame by duplicating non-malloc'ed components + * (header, src, data). + * On return all components are malloc'ed + */ +struct ast_frame *ast_frisolate(struct ast_frame *fr) +{ + struct ast_frame *out; + void *newdata; + + if (!(fr->mallocd & AST_MALLOCD_HDR)) { + /* Allocate a new header if needed */ + if (!(out = ast_frame_header_new())) + return NULL; + out->frametype = fr->frametype; + out->subclass = fr->subclass; + out->datalen = fr->datalen; + out->samples = fr->samples; + out->offset = fr->offset; + out->data = fr->data; + /* Copy the timing data */ + out->has_timing_info = fr->has_timing_info; + if (fr->has_timing_info) { + out->ts = fr->ts; + out->len = fr->len; + out->seqno = fr->seqno; + } + } else + out = fr; + + if (!(fr->mallocd & AST_MALLOCD_SRC)) { + if (fr->src) { + if (!(out->src = ast_strdup(fr->src))) { + if (out != fr) + free(out); + return NULL; + } + } + } else + out->src = fr->src; + + if (!(fr->mallocd & AST_MALLOCD_DATA)) { + if (!(newdata = ast_malloc(fr->datalen + AST_FRIENDLY_OFFSET))) { + if (out->src != fr->src) + free((void *) out->src); + if (out != fr) + free(out); + return NULL; + } + newdata += AST_FRIENDLY_OFFSET; + out->offset = AST_FRIENDLY_OFFSET; + out->datalen = fr->datalen; + memcpy(newdata, fr->data, fr->datalen); + out->data = newdata; + } + + out->mallocd = AST_MALLOCD_HDR | AST_MALLOCD_SRC | AST_MALLOCD_DATA; + + return out; +} + +struct ast_frame *ast_frdup(const struct ast_frame *f) +{ + struct ast_frame *out; + int len, srclen = 0; + void *buf; + /* Start with standard stuff */ + len = sizeof(*out) + AST_FRIENDLY_OFFSET + f->datalen; + /* If we have a source, add space for it */ + /* + * XXX Watch out here - if we receive a src which is not terminated + * properly, we can be easily attacked. Should limit the size we deal with. + */ + if (f->src) + srclen = strlen(f->src); + if (srclen > 0) + len += srclen + 1; + if (!(buf = ast_calloc(1, len))) + return NULL; + out = buf; + /* Set us as having malloc'd header only, so it will eventually + get freed. */ + out->frametype = f->frametype; + out->subclass = f->subclass; + out->datalen = f->datalen; + out->samples = f->samples; + out->delivery = f->delivery; + out->mallocd = AST_MALLOCD_HDR; + out->offset = AST_FRIENDLY_OFFSET; + if (out->datalen) { + out->data = buf + sizeof(*out) + AST_FRIENDLY_OFFSET; + memcpy(out->data, f->data, out->datalen); + } + if (srclen > 0) { + out->src = buf + sizeof(*out) + AST_FRIENDLY_OFFSET + f->datalen; + /* Must have space since we allocated for it */ + strcpy((char *)out->src, f->src); + } + out->has_timing_info = f->has_timing_info; + if (f->has_timing_info) { + out->ts = f->ts; + out->len = f->len; + out->seqno = f->seqno; + } + return out; +} + +#if 0 +/* + * XXX + * This function is badly broken - it does not handle correctly + * partial reads on either header or body. + * However is it never used anywhere so we leave it commented out + */ +struct ast_frame *ast_fr_fdread(int fd) +{ + char buf[65536]; + int res; + struct ast_frame *f = (struct ast_frame *)buf; + int ttl = sizeof(*f); + /* Read a frame directly from there. They're always in the + right format. */ + + while(ttl) { + res = read(fd, buf, ttl); + if (res < 0) { + ast_log(LOG_WARNING, "Bad read on %d: %s\n", fd, strerror(errno)); + return NULL; + } + ttl -= res; + } + + /* read the frame header */ + + /* Re-write data position */ + f->data = buf + sizeof(*f); + f->offset = 0; + /* Forget about being mallocd */ + f->mallocd = 0; + /* Re-write the source */ + f->src = (char *)__FUNCTION__; + if (f->datalen > sizeof(buf) - sizeof(*f)) { + /* Really bad read */ + ast_log(LOG_WARNING, "Strange read (%d bytes)\n", f->datalen); + return NULL; + } + if (f->datalen) { + if ((res = read(fd, f->data, f->datalen)) != f->datalen) { + /* Bad read */ + ast_log(LOG_WARNING, "How very strange, expected %d, got %d\n", f->datalen, res); + return NULL; + } + } + if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) { + return NULL; + } + return ast_frisolate(f); +} + +/* Some convenient routines for sending frames to/from stream or datagram + sockets, pipes, etc (maybe even files) */ + +/* + * XXX this function is also partly broken because it does not handle + * partial writes. We comment it out too, and also the unique + * client it has, ast_fr_fdhangup() + */ +int ast_fr_fdwrite(int fd, struct ast_frame *frame) +{ + /* Write the frame exactly */ + if (write(fd, frame, sizeof(*frame)) != sizeof(*frame)) { + ast_log(LOG_WARNING, "Write error: %s\n", strerror(errno)); + return -1; + } + if (write(fd, frame->data, frame->datalen) != frame->datalen) { + ast_log(LOG_WARNING, "Write error: %s\n", strerror(errno)); + return -1; + } + return 0; +} + +int ast_fr_fdhangup(int fd) +{ + struct ast_frame hangup = { + AST_FRAME_CONTROL, + AST_CONTROL_HANGUP + }; + return ast_fr_fdwrite(fd, &hangup); +} + +#endif /* unused functions */ + +void ast_swapcopy_samples(void *dst, const void *src, int samples) +{ + int i; + unsigned short *dst_s = dst; + const unsigned short *src_s = src; + + for (i = 0; i < samples; i++) + dst_s[i] = (src_s[i]<<8) | (src_s[i]>>8); +} + + +struct ast_format_list *ast_get_format_list_index(int index) +{ + return &AST_FORMAT_LIST[index]; +} + +struct ast_format_list *ast_get_format_list(size_t *size) +{ + *size = (sizeof(AST_FORMAT_LIST) / sizeof(AST_FORMAT_LIST[0])); + return AST_FORMAT_LIST; +} + +char* ast_getformatname(int format) +{ + int x; + char *ret = "unknown"; + for (x = 0; x < sizeof(AST_FORMAT_LIST) / sizeof(AST_FORMAT_LIST[0]); x++) { + if(AST_FORMAT_LIST[x].visible && AST_FORMAT_LIST[x].bits == format) { + ret = AST_FORMAT_LIST[x].name; + break; + } + } + return ret; +} + +char *ast_getformatname_multiple(char *buf, size_t size, int format) +{ + int x; + unsigned len; + char *start, *end = buf; + + if (!size) + return buf; + snprintf(end, size, "0x%x (", format); + len = strlen(end); + end += len; + size -= len; + start = end; + for (x = 0; x < sizeof(AST_FORMAT_LIST) / sizeof(AST_FORMAT_LIST[0]); x++) { + if (AST_FORMAT_LIST[x].visible && (AST_FORMAT_LIST[x].bits & format)) { + snprintf(end, size,"%s|",AST_FORMAT_LIST[x].name); + len = strlen(end); + end += len; + size -= len; + } + } + if (start == end) + snprintf(start, size, "nothing)"); + else if (size > 1) + *(end -1) = ')'; + return buf; +} + +static struct ast_codec_alias_table { + char *alias; + char *realname; +} ast_codec_alias_table[] = { + { "slinear", "slin"}, + { "g723.1", "g723"}, +}; + +static const char *ast_expand_codec_alias(const char *in) +{ + int x; + + for (x = 0; x < sizeof(ast_codec_alias_table) / sizeof(ast_codec_alias_table[0]); x++) { + if(!strcmp(in,ast_codec_alias_table[x].alias)) + return ast_codec_alias_table[x].realname; + } + return in; +} + +int ast_getformatbyname(const char *name) +{ + int x, all, format = 0; + + all = strcasecmp(name, "all") ? 0 : 1; + for (x = 0; x < sizeof(AST_FORMAT_LIST) / sizeof(AST_FORMAT_LIST[0]); x++) { + if(AST_FORMAT_LIST[x].visible && (all || + !strcasecmp(AST_FORMAT_LIST[x].name,name) || + !strcasecmp(AST_FORMAT_LIST[x].name,ast_expand_codec_alias(name)))) { + format |= AST_FORMAT_LIST[x].bits; + if(!all) + break; + } + } + + return format; +} + +char *ast_codec2str(int codec) +{ + int x; + char *ret = "unknown"; + for (x = 0; x < sizeof(AST_FORMAT_LIST) / sizeof(AST_FORMAT_LIST[0]); x++) { + if(AST_FORMAT_LIST[x].visible && AST_FORMAT_LIST[x].bits == codec) { + ret = AST_FORMAT_LIST[x].desc; + break; + } + } + return ret; +} + +static int show_codecs(int fd, int argc, char *argv[]) +{ + int i, found=0; + char hex[25]; + + if ((argc < 2) || (argc > 3)) + return RESULT_SHOWUSAGE; + + if (!ast_opt_dont_warn) + ast_cli(fd, "Disclaimer: this command is for informational purposes only.\n" + "\tIt does not indicate anything about your configuration.\n"); + + ast_cli(fd, "%11s %9s %10s TYPE %8s %s\n","INT","BINARY","HEX","NAME","DESC"); + ast_cli(fd, "--------------------------------------------------------------------------------\n"); + if ((argc == 2) || (!strcasecmp(argv[1],"audio"))) { + found = 1; + for (i=0;i<12;i++) { + snprintf(hex,25,"(0x%x)",1<\n" +" Displays codec mapping\n"; + +/*! Dump a frame for debugging purposes */ +void ast_frame_dump(const char *name, struct ast_frame *f, char *prefix) +{ + const char noname[] = "unknown"; + char ftype[40] = "Unknown Frametype"; + char cft[80]; + char subclass[40] = "Unknown Subclass"; + char csub[80]; + char moreinfo[40] = ""; + char cn[60]; + char cp[40]; + char cmn[40]; + + if (!name) + name = noname; + + + if (!f) { + ast_verbose("%s [ %s (NULL) ] [%s]\n", + term_color(cp, prefix, COLOR_BRMAGENTA, COLOR_BLACK, sizeof(cp)), + term_color(cft, "HANGUP", COLOR_BRRED, COLOR_BLACK, sizeof(cft)), + term_color(cn, name, COLOR_YELLOW, COLOR_BLACK, sizeof(cn))); + return; + } + /* XXX We should probably print one each of voice and video when the format changes XXX */ + if (f->frametype == AST_FRAME_VOICE) + return; + if (f->frametype == AST_FRAME_VIDEO) + return; + switch(f->frametype) { + case AST_FRAME_DTMF: + strcpy(ftype, "DTMF"); + subclass[0] = f->subclass; + subclass[1] = '\0'; + break; + case AST_FRAME_CONTROL: + strcpy(ftype, "Control"); + switch(f->subclass) { + case AST_CONTROL_HANGUP: + strcpy(subclass, "Hangup"); + break; + case AST_CONTROL_RING: + strcpy(subclass, "Ring"); + break; + case AST_CONTROL_RINGING: + strcpy(subclass, "Ringing"); + break; + case AST_CONTROL_ANSWER: + strcpy(subclass, "Answer"); + break; + case AST_CONTROL_BUSY: + strcpy(subclass, "Busy"); + break; + case AST_CONTROL_TAKEOFFHOOK: + strcpy(subclass, "Take Off Hook"); + break; + case AST_CONTROL_OFFHOOK: + strcpy(subclass, "Line Off Hook"); + break; + case AST_CONTROL_CONGESTION: + strcpy(subclass, "Congestion"); + break; + case AST_CONTROL_FLASH: + strcpy(subclass, "Flash"); + break; + case AST_CONTROL_WINK: + strcpy(subclass, "Wink"); + break; + case AST_CONTROL_OPTION: + strcpy(subclass, "Option"); + break; + case AST_CONTROL_RADIO_KEY: + strcpy(subclass, "Key Radio"); + break; + case AST_CONTROL_RADIO_UNKEY: + strcpy(subclass, "Unkey Radio"); + break; + case -1: + strcpy(subclass, "Stop generators"); + break; + default: + snprintf(subclass, sizeof(subclass), "Unknown control '%d'", f->subclass); + } + break; + case AST_FRAME_NULL: + strcpy(ftype, "Null Frame"); + strcpy(subclass, "N/A"); + break; + case AST_FRAME_IAX: + /* Should never happen */ + strcpy(ftype, "IAX Specific"); + snprintf(subclass, sizeof(subclass), "IAX Frametype %d", f->subclass); + break; + case AST_FRAME_TEXT: + strcpy(ftype, "Text"); + strcpy(subclass, "N/A"); + ast_copy_string(moreinfo, f->data, sizeof(moreinfo)); + break; + case AST_FRAME_IMAGE: + strcpy(ftype, "Image"); + snprintf(subclass, sizeof(subclass), "Image format %s\n", ast_getformatname(f->subclass)); + break; + case AST_FRAME_HTML: + strcpy(ftype, "HTML"); + switch(f->subclass) { + case AST_HTML_URL: + strcpy(subclass, "URL"); + ast_copy_string(moreinfo, f->data, sizeof(moreinfo)); + break; + case AST_HTML_DATA: + strcpy(subclass, "Data"); + break; + case AST_HTML_BEGIN: + strcpy(subclass, "Begin"); + break; + case AST_HTML_END: + strcpy(subclass, "End"); + break; + case AST_HTML_LDCOMPLETE: + strcpy(subclass, "Load Complete"); + break; + case AST_HTML_NOSUPPORT: + strcpy(subclass, "No Support"); + break; + case AST_HTML_LINKURL: + strcpy(subclass, "Link URL"); + ast_copy_string(moreinfo, f->data, sizeof(moreinfo)); + break; + case AST_HTML_UNLINK: + strcpy(subclass, "Unlink"); + break; + case AST_HTML_LINKREJECT: + strcpy(subclass, "Link Reject"); + break; + default: + snprintf(subclass, sizeof(subclass), "Unknown HTML frame '%d'\n", f->subclass); + break; + } + break; + case AST_FRAME_MODEM: + strcpy(ftype, "Modem"); + switch (f->subclass) { + case AST_MODEM_T38: + strcpy(subclass, "T.38"); + break; + case AST_MODEM_V150: + strcpy(subclass, "V.150"); + break; + default: + snprintf(subclass, sizeof(subclass), "Unknown MODEM frame '%d'\n", f->subclass); + break; + } + break; + default: + snprintf(ftype, sizeof(ftype), "Unknown Frametype '%d'", f->frametype); + } + if (!ast_strlen_zero(moreinfo)) + ast_verbose("%s [ TYPE: %s (%d) SUBCLASS: %s (%d) '%s' ] [%s]\n", + term_color(cp, prefix, COLOR_BRMAGENTA, COLOR_BLACK, sizeof(cp)), + term_color(cft, ftype, COLOR_BRRED, COLOR_BLACK, sizeof(cft)), + f->frametype, + term_color(csub, subclass, COLOR_BRCYAN, COLOR_BLACK, sizeof(csub)), + f->subclass, + term_color(cmn, moreinfo, COLOR_BRGREEN, COLOR_BLACK, sizeof(cmn)), + term_color(cn, name, COLOR_YELLOW, COLOR_BLACK, sizeof(cn))); + else + ast_verbose("%s [ TYPE: %s (%d) SUBCLASS: %s (%d) ] [%s]\n", + term_color(cp, prefix, COLOR_BRMAGENTA, COLOR_BLACK, sizeof(cp)), + term_color(cft, ftype, COLOR_BRRED, COLOR_BLACK, sizeof(cft)), + f->frametype, + term_color(csub, subclass, COLOR_BRCYAN, COLOR_BLACK, sizeof(csub)), + f->subclass, + term_color(cn, name, COLOR_YELLOW, COLOR_BLACK, sizeof(cn))); +} + + +#ifdef TRACE_FRAMES +static int show_frame_stats(int fd, int argc, char *argv[]) +{ + struct ast_frame *f; + int x=1; + if (argc != 3) + return RESULT_SHOWUSAGE; + ast_mutex_lock(&framelock); + ast_cli(fd, " Framer Statistics \n"); + ast_cli(fd, "---------------------------\n"); + ast_cli(fd, "Total allocated headers: %d\n", headers); + ast_cli(fd, "Queue Dump:\n"); + for (f=headerlist; f; f = f->next) { + ast_cli(fd, "%d. Type %d, subclass %d from %s\n", x++, f->frametype, f->subclass, f->src ? f->src : ""); + } + ast_mutex_unlock(&framelock); + return RESULT_SUCCESS; +} + +static char frame_stats_usage[] = +"Usage: show frame stats\n" +" Displays debugging statistics from framer\n"; +#endif + +/* Builtin Asterisk CLI-commands for debugging */ +static struct ast_cli_entry my_clis[] = { +{ { "show", "codecs", NULL }, show_codecs, "Shows codecs", frame_show_codecs_usage }, +{ { "show", "audio", "codecs", NULL }, show_codecs, "Shows audio codecs", frame_show_codecs_usage }, +{ { "show", "video", "codecs", NULL }, show_codecs, "Shows video codecs", frame_show_codecs_usage }, +{ { "show", "image", "codecs", NULL }, show_codecs, "Shows image codecs", frame_show_codecs_usage }, +{ { "show", "codec", NULL }, show_codec_n, "Shows a specific codec", frame_show_codec_n_usage }, +#ifdef TRACE_FRAMES +{ { "show", "frame", "stats", NULL }, show_frame_stats, "Shows frame statistics", frame_stats_usage }, +#endif +}; + +int init_framer(void) +{ + ast_cli_register_multiple(my_clis, sizeof(my_clis)/sizeof(my_clis[0]) ); + return 0; +} + +void ast_codec_pref_convert(struct ast_codec_pref *pref, char *buf, size_t size, int right) +{ + int x, differential = (int) 'A', mem; + char *from, *to; + + if(right) { + from = pref->order; + to = buf; + mem = size; + } else { + to = pref->order; + from = buf; + mem = 32; + } + + memset(to, 0, mem); + for (x = 0; x < 32 ; x++) { + if(!from[x]) + break; + to[x] = right ? (from[x] + differential) : (from[x] - differential); + } +} + +int ast_codec_pref_string(struct ast_codec_pref *pref, char *buf, size_t size) +{ + int x, codec; + size_t total_len, slen; + char *formatname; + + memset(buf,0,size); + total_len = size; + buf[0] = '('; + total_len--; + for(x = 0; x < 32 ; x++) { + if(total_len <= 0) + break; + if(!(codec = ast_codec_pref_index(pref,x))) + break; + if((formatname = ast_getformatname(codec))) { + slen = strlen(formatname); + if(slen > total_len) + break; + strncat(buf,formatname,total_len); + total_len -= slen; + } + if(total_len && x < 31 && ast_codec_pref_index(pref , x + 1)) { + strncat(buf,"|",total_len); + total_len--; + } + } + if(total_len) { + strncat(buf,")",total_len); + total_len--; + } + + return size - total_len; +} + +int ast_codec_pref_index(struct ast_codec_pref *pref, int index) +{ + int slot = 0; + + + if((index >= 0) && (index < sizeof(pref->order))) { + slot = pref->order[index]; + } + + return slot ? AST_FORMAT_LIST[slot-1].bits : 0; +} + +/*! \brief Remove codec from pref list */ +void ast_codec_pref_remove(struct ast_codec_pref *pref, int format) +{ + struct ast_codec_pref oldorder; + int x, y = 0; + int slot; + + if(!pref->order[0]) + return; + + memcpy(&oldorder, pref, sizeof(oldorder)); + memset(pref, 0, sizeof(*pref)); + + for (x = 0; x < sizeof(AST_FORMAT_LIST) / sizeof(AST_FORMAT_LIST[0]); x++) { + slot = oldorder.order[x]; + if(! slot) + break; + if(AST_FORMAT_LIST[slot-1].bits != format) + pref->order[y++] = slot; + } + +} + +/*! \brief Append codec to list */ +int ast_codec_pref_append(struct ast_codec_pref *pref, int format) +{ + int x, newindex = -1; + + ast_codec_pref_remove(pref, format); + + for (x = 0; x < sizeof(AST_FORMAT_LIST) / sizeof(AST_FORMAT_LIST[0]); x++) { + if(AST_FORMAT_LIST[x].bits == format) { + newindex = x + 1; + break; + } + } + + if(newindex) { + for (x = 0; x < sizeof(AST_FORMAT_LIST) / sizeof(AST_FORMAT_LIST[0]); x++) { + if(!pref->order[x]) { + pref->order[x] = newindex; + break; + } + } + } + + return x; +} + + +/*! \brief Pick a codec */ +int ast_codec_choose(struct ast_codec_pref *pref, int formats, int find_best) +{ + int x, ret = 0, slot; + + for (x = 0; x < sizeof(AST_FORMAT_LIST) / sizeof(AST_FORMAT_LIST[0]); x++) { + slot = pref->order[x]; + + if (!slot) + break; + if (formats & AST_FORMAT_LIST[slot-1].bits) { + ret = AST_FORMAT_LIST[slot-1].bits; + break; + } + } + if(ret & AST_FORMAT_AUDIO_MASK) + return ret; + + if (option_debug > 3) + ast_log(LOG_DEBUG, "Could not find preferred codec - %s\n", find_best ? "Going for the best codec" : "Returning zero codec"); + + return find_best ? ast_best_codec(formats) : 0; +} + +void ast_parse_allow_disallow(struct ast_codec_pref *pref, int *mask, const char *list, int allowing) +{ + char *parse; + char *this; + int format; + + parse = ast_strdupa(list); + while ((this = strsep(&parse, ","))) { + if (!(format = ast_getformatbyname(this))) { + ast_log(LOG_WARNING, "Cannot %s unknown format '%s'\n", allowing ? "allow" : "disallow", this); + continue; + } + + if (mask) { + if (allowing) + *mask |= format; + else + *mask &= ~format; + } + + /* Set up a preference list for audio. Do not include video in preferences + since we can not transcode video and have to use whatever is offered + */ + if (pref && (format & AST_FORMAT_AUDIO_MASK)) { + if (strcasecmp(this, "all")) { + if (allowing) + ast_codec_pref_append(pref, format); + else + ast_codec_pref_remove(pref, format); + } else if (!allowing) { + memset(pref, 0, sizeof(*pref)); + } + } + } +} + +static int g723_len(unsigned char buf) +{ + enum frame_type type = buf & TYPE_MASK; + + switch(type) { + case TYPE_DONTSEND: + return 0; + break; + case TYPE_SILENCE: + return 4; + break; + case TYPE_HIGH: + return 24; + break; + case TYPE_LOW: + return 20; + break; + default: + ast_log(LOG_WARNING, "Badly encoded frame (%d)\n", type); + } + return -1; +} + +static int g723_samples(unsigned char *buf, int maxlen) +{ + int pos = 0; + int samples = 0; + int res; + while(pos < maxlen) { + res = g723_len(buf[pos]); + if (res <= 0) + break; + samples += 240; + pos += res; + } + return samples; +} + +static unsigned char get_n_bits_at(unsigned char *data, int n, int bit) +{ + int byte = bit / 8; /* byte containing first bit */ + int rem = 8 - (bit % 8); /* remaining bits in first byte */ + unsigned char ret = 0; + + if (n <= 0 || n > 8) + return 0; + + if (rem < n) { + ret = (data[byte] << (n - rem)); + ret |= (data[byte + 1] >> (8 - n + rem)); + } else { + ret = (data[byte] >> (rem - n)); + } + + return (ret & (0xff >> (8 - n))); +} + +static int speex_get_wb_sz_at(unsigned char *data, int len, int bit) +{ + static int SpeexWBSubModeSz[] = { + 0, 36, 112, 192, + 352, 0, 0, 0 }; + int off = bit; + unsigned char c; + + /* skip up to two wideband frames */ + if (((len * 8 - off) >= 5) && + get_n_bits_at(data, 1, off)) { + c = get_n_bits_at(data, 3, off + 1); + off += SpeexWBSubModeSz[c]; + + if (((len * 8 - off) >= 5) && + get_n_bits_at(data, 1, off)) { + c = get_n_bits_at(data, 3, off + 1); + off += SpeexWBSubModeSz[c]; + + if (((len * 8 - off) >= 5) && + get_n_bits_at(data, 1, off)) { + ast_log(LOG_WARNING, "Encountered corrupt speex frame; too many wideband frames in a row.\n"); + return -1; + } + } + + } + return off - bit; +} + +static int speex_samples(unsigned char *data, int len) +{ + static int SpeexSubModeSz[] = { + 5, 43, 119, 160, + 220, 300, 364, 492, + 79, 0, 0, 0, + 0, 0, 0, 0 }; + static int SpeexInBandSz[] = { + 1, 1, 4, 4, + 4, 4, 4, 4, + 8, 8, 16, 16, + 32, 32, 64, 64 }; + int bit = 0; + int cnt = 0; + int off; + unsigned char c; + + while ((len * 8 - bit) >= 5) { + /* skip wideband frames */ + off = speex_get_wb_sz_at(data, len, bit); + if (off < 0) { + ast_log(LOG_WARNING, "Had error while reading wideband frames for speex samples\n"); + break; + } + bit += off; + + if ((len * 8 - bit) < 5) { + ast_log(LOG_WARNING, "Not enough bits remaining after wide band for speex samples.\n"); + break; + } + + /* get control bits */ + c = get_n_bits_at(data, 5, bit); + bit += 5; + + if (c == 15) { + /* terminator */ + break; + } else if (c == 14) { + /* in-band signal; next 4 bits contain signal id */ + c = get_n_bits_at(data, 4, bit); + bit += 4; + bit += SpeexInBandSz[c]; + } else if (c == 13) { + /* user in-band; next 5 bits contain msg len */ + c = get_n_bits_at(data, 5, bit); + bit += 5; + bit += c * 8; + } else if (c > 8) { + /* unknown */ + break; + } else { + /* skip number bits for submode (less the 5 control bits) */ + bit += SpeexSubModeSz[c] - 5; + cnt += 160; /* new frame */ + } + } + return cnt; +} + +int ast_codec_get_samples(struct ast_frame *f) +{ + int samples=0; + switch(f->subclass) { + case AST_FORMAT_SPEEX: + samples = speex_samples(f->data, f->datalen); + break; + case AST_FORMAT_G723_1: + samples = g723_samples(f->data, f->datalen); + break; + case AST_FORMAT_ILBC: + samples = 240 * (f->datalen / 50); + break; + case AST_FORMAT_GSM: + samples = 160 * (f->datalen / 33); + break; + case AST_FORMAT_G729A: + samples = f->datalen * 8; + break; + case AST_FORMAT_SLINEAR: + samples = f->datalen / 2; + break; + case AST_FORMAT_LPC10: + /* assumes that the RTP packet contains one LPC10 frame */ + samples = 22 * 8; + samples += (((char *)(f->data))[7] & 0x1) * 8; + break; + case AST_FORMAT_ULAW: + case AST_FORMAT_ALAW: + samples = f->datalen; + break; + case AST_FORMAT_ADPCM: + case AST_FORMAT_G726: + case AST_FORMAT_G726_AAL2: + samples = f->datalen * 2; + break; + default: + ast_log(LOG_WARNING, "Unable to calculate samples for format %s\n", ast_getformatname(f->subclass)); + } + return samples; +} + +int ast_codec_get_len(int format, int samples) +{ + int len = 0; + + /* XXX Still need speex, g723, and lpc10 XXX */ + switch(format) { + case AST_FORMAT_ILBC: + len = (samples / 240) * 50; + break; + case AST_FORMAT_GSM: + len = (samples / 160) * 33; + break; + case AST_FORMAT_G729A: + len = samples / 8; + break; + case AST_FORMAT_SLINEAR: + len = samples * 2; + break; + case AST_FORMAT_ULAW: + case AST_FORMAT_ALAW: + len = samples; + break; + case AST_FORMAT_ADPCM: + case AST_FORMAT_G726: + case AST_FORMAT_G726_AAL2: + len = samples / 2; + break; + default: + ast_log(LOG_WARNING, "Unable to calculate sample length for format %s\n", ast_getformatname(format)); + } + + return len; +} + +int ast_frame_adjust_volume(struct ast_frame *f, int adjustment) +{ + int count; + short *fdata = f->data; + short adjust_value = abs(adjustment); + + if ((f->frametype != AST_FRAME_VOICE) || (f->subclass != AST_FORMAT_SLINEAR)) + return -1; + + if (!adjustment) + return 0; + + for (count = 0; count < f->samples; count++) { + if (adjustment > 0) { + ast_slinear_saturated_multiply(&fdata[count], &adjust_value); + } else if (adjustment < 0) { + ast_slinear_saturated_divide(&fdata[count], &adjust_value); + } + } + + return 0; +} + +int ast_frame_slinear_sum(struct ast_frame *f1, struct ast_frame *f2) +{ + int count; + short *data1, *data2; + + if ((f1->frametype != AST_FRAME_VOICE) || (f1->subclass != AST_FORMAT_SLINEAR)) + return -1; + + if ((f2->frametype != AST_FRAME_VOICE) || (f2->subclass != AST_FORMAT_SLINEAR)) + return -1; + + if (f1->samples != f2->samples) + return -1; + + for (count = 0, data1 = f1->data, data2 = f2->data; + count < f1->samples; + count++, data1++, data2++) + ast_slinear_saturated_add(data1, data2); + + return 0; +} + +struct ast_frame *ast_frame_enqueue(struct ast_frame *head, struct ast_frame *f, int maxlen, int dupe) +{ + struct ast_frame *cur, *oldhead; + int len=0; + if (f && dupe) + f = ast_frdup(f); + if (!f) + return head; + + f->next = NULL; + if (!head) + return f; + cur = head; + while(cur->next) { + cur = cur->next; + len++; + if (len >= maxlen) { + oldhead = head; + head = head->next; + ast_frfree(oldhead); + } + } + return head; +} diff --git a/main/fskmodem.c b/main/fskmodem.c new file mode 100644 index 000000000..9bbb2203f --- /dev/null +++ b/main/fskmodem.c @@ -0,0 +1,305 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer + * + * Includes code and algorithms from the Zapata library. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief FSK Modulator/Demodulator + * + * \author Mark Spencer + * + * \arg Includes code and algorithms from the Zapata library. + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include + +#include "asterisk/fskmodem.h" + +#define NBW 2 +#define BWLIST {75,800} +#define NF 6 +#define FLIST {1400,1800,1200,2200,1300,2100} + +#define STATE_SEARCH_STARTBIT 0 +#define STATE_SEARCH_STARTBIT2 1 +#define STATE_SEARCH_STARTBIT3 2 +#define STATE_GET_BYTE 3 + +static inline float get_sample(short **buffer, int *len) +{ + float retval; + retval = (float) **buffer / 256; + (*buffer)++; + (*len)--; + return retval; +} + +#define GET_SAMPLE get_sample(&buffer, len) + +/* Coeficientes para filtros de entrada */ +/* Tabla de coeficientes, generada a partir del programa "mkfilter" */ +/* Formato: coef[IDX_FREC][IDX_BW][IDX_COEF] */ +/* IDX_COEF=0 => 1/GAIN */ +/* IDX_COEF=1-6 => Coeficientes y[n] */ + +static double coef_in[NF][NBW][8]={ +#include "coef_in.h" +}; + +/* Coeficientes para filtro de salida */ +/* Tabla de coeficientes, generada a partir del programa "mkfilter" */ +/* Formato: coef[IDX_BW][IDX_COEF] */ +/* IDX_COEF=0 => 1/GAIN */ +/* IDX_COEF=1-6 => Coeficientes y[n] */ + +static double coef_out[NBW][8]={ +#include "coef_out.h" +}; + + +/*! Filtro pasa-banda para frecuencia de MARCA */ +static inline float filtroM(fsk_data *fskd,float in) +{ + int i,j; + double s; + double *pc; + + pc=&coef_in[fskd->f_mark_idx][fskd->bw][0]; + fskd->fmxv[(fskd->fmp+6)&7]=in*(*pc++); + + s=(fskd->fmxv[(fskd->fmp+6)&7] - fskd->fmxv[fskd->fmp]) + 3 * (fskd->fmxv[(fskd->fmp+2)&7] - fskd->fmxv[(fskd->fmp+4)&7]); + for (i=0,j=fskd->fmp;i<6;i++,j++) s+=fskd->fmyv[j&7]*(*pc++); + fskd->fmyv[j&7]=s; + fskd->fmp++; fskd->fmp&=7; + return s; +} + +/*! Filtro pasa-banda para frecuencia de ESPACIO */ +static inline float filtroS(fsk_data *fskd,float in) +{ + int i,j; + double s; + double *pc; + + pc=&coef_in[fskd->f_space_idx][fskd->bw][0]; + fskd->fsxv[(fskd->fsp+6)&7]=in*(*pc++); + + s=(fskd->fsxv[(fskd->fsp+6)&7] - fskd->fsxv[fskd->fsp]) + 3 * (fskd->fsxv[(fskd->fsp+2)&7] - fskd->fsxv[(fskd->fsp+4)&7]); + for (i=0,j=fskd->fsp;i<6;i++,j++) s+=fskd->fsyv[j&7]*(*pc++); + fskd->fsyv[j&7]=s; + fskd->fsp++; fskd->fsp&=7; + return s; +} + +/*! Filtro pasa-bajos para datos demodulados */ +static inline float filtroL(fsk_data *fskd,float in) +{ + int i,j; + double s; + double *pc; + + pc=&coef_out[fskd->bw][0]; + fskd->flxv[(fskd->flp + 6) & 7]=in * (*pc++); + + s= (fskd->flxv[fskd->flp] + fskd->flxv[(fskd->flp+6)&7]) + + 6 * (fskd->flxv[(fskd->flp+1)&7] + fskd->flxv[(fskd->flp+5)&7]) + + 15 * (fskd->flxv[(fskd->flp+2)&7] + fskd->flxv[(fskd->flp+4)&7]) + + 20 * fskd->flxv[(fskd->flp+3)&7]; + + for (i=0,j=fskd->flp;i<6;i++,j++) s+=fskd->flyv[j&7]*(*pc++); + fskd->flyv[j&7]=s; + fskd->flp++; fskd->flp&=7; + return s; +} + +static inline int demodulador(fsk_data *fskd, float *retval, float x) +{ + float xS,xM; + + fskd->cola_in[fskd->pcola]=x; + + xS=filtroS(fskd,x); + xM=filtroM(fskd,x); + + fskd->cola_filtro[fskd->pcola]=xM-xS; + + x=filtroL(fskd,xM*xM - xS*xS); + + fskd->cola_demod[fskd->pcola++]=x; + fskd->pcola &= (NCOLA-1); + + *retval = x; + return(0); +} + +static int get_bit_raw(fsk_data *fskd, short *buffer, int *len) +{ + /* Esta funcion implementa un DPLL para sincronizarse con los bits */ + float x,spb,spb2,ds; + int f; + + spb=fskd->spb; + if (fskd->spb == 7) spb = 8000.0 / 1200.0; + ds=spb/32.; + spb2=spb/2.; + + for (f=0;;){ + if (demodulador(fskd,&x, GET_SAMPLE)) return(-1); + if ((x*fskd->x0)<0) { /* Transicion */ + if (!f) { + if (fskd->cont<(spb2)) fskd->cont+=ds; else fskd->cont-=ds; + f=1; + } + } + fskd->x0=x; + fskd->cont+=1.; + if (fskd->cont>spb) { + fskd->cont-=spb; + break; + } + } + f=(x>0)?0x80:0; + return(f); +} + +int fsk_serie(fsk_data *fskd, short *buffer, int *len, int *outbyte) +{ + int a; + int i,j,n1,r; + int samples=0; + int olen; + switch(fskd->state) { + /* Pick up where we left off */ + case STATE_SEARCH_STARTBIT2: + goto search_startbit2; + case STATE_SEARCH_STARTBIT3: + goto search_startbit3; + case STATE_GET_BYTE: + goto getbyte; + } + /* Esperamos bit de start */ + do { +/* this was jesus's nice, reasonable, working (at least with RTTY) code +to look for the beginning of the start bit. Unfortunately, since TTY/TDD's +just start sending a start bit with nothing preceding it at the beginning +of a transmission (what a LOSING design), we cant do it this elegantly */ +/* + if (demodulador(zap,&x1)) return(-1); + for(;;) { + if (demodulador(zap,&x2)) return(-1); + if (x1>0 && x2<0) break; + x1=x2; + } +*/ +/* this is now the imprecise, losing, but functional code to detect the +beginning of a start bit in the TDD sceanario. It just looks for sufficient +level to maybe, perhaps, guess, maybe that its maybe the beginning of +a start bit, perhaps. This whole thing stinks! */ + if (demodulador(fskd,&fskd->x1,GET_SAMPLE)) return(-1); + samples++; + for(;;) + { +search_startbit2: + if (!*len) { + fskd->state = STATE_SEARCH_STARTBIT2; + return 0; + } + samples++; + if (demodulador(fskd,&fskd->x2,GET_SAMPLE)) return(-1); +#if 0 + printf("x2 = %5.5f ", fskd->x2); +#endif + if (fskd->x2 < -0.5) break; + } +search_startbit3: + /* Esperamos 0.5 bits antes de usar DPLL */ + i=fskd->spb/2; + if (*len < i) { + fskd->state = STATE_SEARCH_STARTBIT3; + return 0; + } + for(;i;i--) { if (demodulador(fskd,&fskd->x1,GET_SAMPLE)) return(-1); +#if 0 + printf("x1 = %5.5f ", fskd->x1); +#endif + samples++; } + + /* x1 debe ser negativo (confirmación del bit de start) */ + + } while (fskd->x1>0); + fskd->state = STATE_GET_BYTE; + +getbyte: + + /* Need at least 80 samples (for 1200) or + 1320 (for 45.5) to be sure we'll have a byte */ + if (fskd->nbit < 8) { + if (*len < 1320) + return 0; + } else { + if (*len < 80) + return 0; + } + /* Leemos ahora los bits de datos */ + j=fskd->nbit; + for (a=n1=0;j;j--) { + olen = *len; + i=get_bit_raw(fskd, buffer, len); + buffer += (olen - *len); + if (i == -1) return(-1); + if (i) n1++; + a>>=1; a|=i; + } + j=8-fskd->nbit; + a>>=j; + + /* Leemos bit de paridad (si existe) y la comprobamos */ + if (fskd->paridad) { + olen = *len; + i=get_bit_raw(fskd, buffer, len); + buffer += (olen - *len); + if (i == -1) return(-1); + if (i) n1++; + if (fskd->paridad==1) { /* paridad=1 (par) */ + if (n1&1) a|=0x100; /* error */ + } else { /* paridad=2 (impar) */ + if (!(n1&1)) a|=0x100; /* error */ + } + } + + /* Leemos bits de STOP. Todos deben ser 1 */ + + for (j=fskd->nstop;j;j--) { + r = get_bit_raw(fskd, buffer, len); + if (r == -1) return(-1); + if (!r) a|=0x200; + } + + /* Por fin retornamos */ + /* Bit 8 : Error de paridad */ + /* Bit 9 : Error de Framming */ + + *outbyte = a; + fskd->state = STATE_SEARCH_STARTBIT; + return 1; +} diff --git a/main/http.c b/main/http.c new file mode 100644 index 000000000..230fec9af --- /dev/null +++ b/main/http.c @@ -0,0 +1,698 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \brief http server for AMI access + * + * \author Mark Spencer + * This program implements a tiny http server supporting the "get" method + * only and was inspired by micro-httpd by Jef Poskanzer + * + * \ref AstHTTP - AMI over the http protocol + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/cli.h" +#include "asterisk/http.h" +#include "asterisk/utils.h" +#include "asterisk/strings.h" +#include "asterisk/options.h" +#include "asterisk/config.h" + +#define MAX_PREFIX 80 +#define DEFAULT_PREFIX "/asterisk" + +struct ast_http_server_instance { + FILE *f; + int fd; + struct sockaddr_in requestor; + ast_http_callback callback; +}; + +static struct ast_http_uri *uris; + +static int httpfd = -1; +static pthread_t master = AST_PTHREADT_NULL; +static char prefix[MAX_PREFIX]; +static int prefix_len = 0; +static struct sockaddr_in oldsin; +static int enablestatic=0; + +/*! \brief Limit the kinds of files we're willing to serve up */ +static struct { + char *ext; + char *mtype; +} mimetypes[] = { + { "png", "image/png" }, + { "jpg", "image/jpeg" }, + { "js", "application/x-javascript" }, + { "wav", "audio/x-wav" }, + { "mp3", "audio/mpeg" }, +}; + +static char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen) +{ + int x; + if (ftype) { + for (x=0;x 1024) + goto out403; + + path = alloca(len); + sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_DIR, uri); + if (stat(path, &st)) + goto out404; + if (S_ISDIR(st.st_mode)) + goto out404; + fd = open(path, O_RDONLY); + if (fd < 0) + goto out403; + + len = st.st_size + strlen(mtype) + 40; + + blob = malloc(len); + if (blob) { + c = blob; + sprintf(c, "Content-type: %s\r\n\r\n", mtype); + c += strlen(c); + *contentlength = read(fd, c, st.st_size); + if (*contentlength < 0) { + close(fd); + free(blob); + goto out403; + } + } + close(fd); + return blob; + +out404: + *status = 404; + *title = strdup("Not Found"); + return ast_http_error(404, "Not Found", NULL, "Nothing to see here. Move along."); + +out403: + *status = 403; + *title = strdup("Access Denied"); + return ast_http_error(403, "Access Denied", NULL, "Sorry, I cannot let you do that, Dave."); +} + + +static char *httpstatus_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength) +{ + char result[4096]; + size_t reslen = sizeof(result); + char *c=result; + struct ast_variable *v; + + ast_build_string(&c, &reslen, + "\r\n" + "Asterisk HTTP Status\r\n" + "\r\n" + "\r\n"); + + ast_build_string(&c, &reslen, "\r\n", prefix); + ast_build_string(&c, &reslen, "\r\n", + ast_inet_ntoa(oldsin.sin_addr)); + ast_build_string(&c, &reslen, "\r\n", + ntohs(oldsin.sin_port)); + ast_build_string(&c, &reslen, "\r\n"); + v = vars; + while(v) { + if (strncasecmp(v->name, "cookie_", 7)) + ast_build_string(&c, &reslen, "\r\n", v->name, v->value); + v = v->next; + } + ast_build_string(&c, &reslen, "\r\n"); + v = vars; + while(v) { + if (!strncasecmp(v->name, "cookie_", 7)) + ast_build_string(&c, &reslen, "\r\n", v->name, v->value); + v = v->next; + } + ast_build_string(&c, &reslen, "
\r\n" + "

  Asterisk™ HTTP Status

Prefix%s
Bind Address%s
Bind Port%d

Submitted Variable '%s'%s

Cookie '%s'%s
Asterisk and Digium are registered trademarks of Digium, Inc.
\r\n"); + return strdup(result); +} + +static struct ast_http_uri statusuri = { + .callback = httpstatus_callback, + .description = "Asterisk HTTP General Status", + .uri = "httpstatus", + .has_subtree = 0, +}; + +static struct ast_http_uri staticuri = { + .callback = static_callback, + .description = "Asterisk HTTP Static Delivery", + .uri = "static", + .has_subtree = 1, +}; + +char *ast_http_error(int status, const char *title, const char *extra_header, const char *text) +{ + char *c = NULL; + asprintf(&c, + "Content-type: text/html\r\n" + "%s" + "\r\n" + "\r\n" + "\r\n" + "%d %s\r\n" + "\r\n" + "

%s

\r\n" + "

%s

\r\n" + "
\r\n" + "
Asterisk Server
\r\n" + "\r\n", + (extra_header ? extra_header : ""), status, title, title, text); + return c; +} + +int ast_http_uri_link(struct ast_http_uri *urih) +{ + struct ast_http_uri *prev=uris; + if (!uris || strlen(uris->uri) <= strlen(urih->uri)) { + urih->next = uris; + uris = urih; + } else { + while (prev->next && (strlen(prev->next->uri) > strlen(urih->uri))) + prev = prev->next; + /* Insert it here */ + urih->next = prev->next; + prev->next = urih; + } + return 0; +} + +void ast_http_uri_unlink(struct ast_http_uri *urih) +{ + struct ast_http_uri *prev = uris; + if (!uris) + return; + if (uris == urih) { + uris = uris->next; + } + while(prev->next) { + if (prev->next == urih) { + prev->next = urih->next; + break; + } + prev = prev->next; + } +} + +static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **title, int *contentlength, struct ast_variable **cookies) +{ + char *c; + char *turi; + char *params; + char *var; + char *val; + struct ast_http_uri *urih=NULL; + int len; + struct ast_variable *vars=NULL, *v, *prev = NULL; + + + params = strchr(uri, '?'); + if (params) { + *params = '\0'; + params++; + while ((var = strsep(¶ms, "&"))) { + val = strchr(var, '='); + if (val) { + *val = '\0'; + val++; + ast_uri_decode(val); + } else + val = ""; + ast_uri_decode(var); + if ((v = ast_variable_new(var, val))) { + if (vars) + prev->next = v; + else + vars = v; + prev = v; + } + } + } + if (prev) + prev->next = *cookies; + else + vars = *cookies; + *cookies = NULL; + ast_uri_decode(uri); + if (!strncasecmp(uri, prefix, prefix_len)) { + uri += prefix_len; + if (!*uri || (*uri == '/')) { + if (*uri == '/') + uri++; + urih = uris; + while(urih) { + len = strlen(urih->uri); + if (!strncasecmp(urih->uri, uri, len)) { + if (!uri[len] || uri[len] == '/') { + turi = uri + len; + if (*turi == '/') + turi++; + if (!*turi || urih->has_subtree) { + uri = turi; + break; + } + } + } + urih = urih->next; + } + } + } + if (urih) { + c = urih->callback(sin, uri, vars, status, title, contentlength); + ast_variables_destroy(vars); + } else if (ast_strlen_zero(uri) && ast_strlen_zero(prefix)) { + /* Special case: If no prefix, and no URI, send to /static/index.html */ + c = ast_http_error(302, "Moved Temporarily", "Location: /static/index.html\r\n", "This is not the page you are looking for..."); + *status = 302; + *title = strdup("Moved Temporarily"); + } else { + c = ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this serer."); + *status = 404; + *title = strdup("Not Found"); + } + return c; +} + +static void *ast_httpd_helper_thread(void *data) +{ + char buf[4096]; + char cookie[4096]; + char timebuf[256]; + struct ast_http_server_instance *ser = data; + struct ast_variable *var, *prev=NULL, *vars=NULL; + char *uri, *c, *title=NULL; + char *vname, *vval; + int status = 200, contentlength = 0; + time_t t; + + if (fgets(buf, sizeof(buf), ser->f)) { + /* Skip method */ + uri = buf; + while(*uri && (*uri > 32)) + uri++; + if (*uri) { + *uri = '\0'; + uri++; + } + + /* Skip white space */ + while (*uri && (*uri < 33)) + uri++; + + if (*uri) { + c = uri; + while (*c && (*c > 32)) + c++; + if (*c) { + *c = '\0'; + } + } + + while (fgets(cookie, sizeof(cookie), ser->f)) { + /* Trim trailing characters */ + while(!ast_strlen_zero(cookie) && (cookie[strlen(cookie) - 1] < 33)) { + cookie[strlen(cookie) - 1] = '\0'; + } + if (ast_strlen_zero(cookie)) + break; + if (!strncasecmp(cookie, "Cookie: ", 8)) { + + /* TODO - The cookie parsing code below seems to work + in IE6 and FireFox 1.5. However, it is not entirely + correct, and therefore may not work in all + circumstances. + For more details see RFC 2109 and RFC 2965 */ + + /* FireFox cookie strings look like: + Cookie: mansession_id="********" + InternetExplorer's look like: + Cookie: $Version="1"; mansession_id="********" */ + + /* If we got a FireFox cookie string, the name's right + after "Cookie: " */ + vname = cookie + 8; + + /* If we got an IE cookie string, we need to skip to + past the version to get to the name */ + if (*vname == '$') { + vname = strchr(vname, ';'); + if (vname) { + vname++; + if (*vname == ' ') + vname++; + } + } + + if (vname) { + vval = strchr(vname, '='); + if (vval) { + /* Ditch the = and the quotes */ + *vval++ = '\0'; + if (*vval) + vval++; + if (strlen(vval)) + vval[strlen(vval) - 1] = '\0'; + var = ast_variable_new(vname, vval); + if (var) { + if (prev) + prev->next = var; + else + vars = var; + prev = var; + } + } + } + } + } + + if (*uri) { + if (!strcasecmp(buf, "get")) + c = handle_uri(&ser->requestor, uri, &status, &title, &contentlength, &vars); + else + c = ast_http_error(501, "Not Implemented", NULL, "Attempt to use unimplemented / unsupported method");\ + } else + c = ast_http_error(400, "Bad Request", NULL, "Invalid Request"); + + /* If they aren't mopped up already, clean up the cookies */ + if (vars) + ast_variables_destroy(vars); + + if (!c) + c = ast_http_error(500, "Internal Error", NULL, "Internal Server Error"); + if (c) { + time(&t); + strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t)); + ast_cli(ser->fd, "HTTP/1.1 %d %s\r\n", status, title ? title : "OK"); + ast_cli(ser->fd, "Server: Asterisk\r\n"); + ast_cli(ser->fd, "Date: %s\r\n", timebuf); + ast_cli(ser->fd, "Connection: close\r\n"); + if (contentlength) { + char *tmp; + tmp = strstr(c, "\r\n\r\n"); + if (tmp) { + ast_cli(ser->fd, "Content-length: %d\r\n", contentlength); + write(ser->fd, c, (tmp + 4 - c)); + write(ser->fd, tmp + 4, contentlength); + } + } else + ast_cli(ser->fd, "%s", c); + free(c); + } + if (title) + free(title); + } + fclose(ser->f); + free(ser); + return NULL; +} + +static void *http_root(void *data) +{ + int fd; + struct sockaddr_in sin; + socklen_t sinlen; + struct ast_http_server_instance *ser; + pthread_t launched; + pthread_attr_t attr; + + for (;;) { + ast_wait_for_input(httpfd, -1); + sinlen = sizeof(sin); + fd = accept(httpfd, (struct sockaddr *)&sin, &sinlen); + if (fd < 0) { + if ((errno != EAGAIN) && (errno != EINTR)) + ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno)); + continue; + } + ser = ast_calloc(1, sizeof(*ser)); + if (ser) { + ser->fd = fd; + memcpy(&ser->requestor, &sin, sizeof(ser->requestor)); + if ((ser->f = fdopen(ser->fd, "w+"))) { + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + if (ast_pthread_create(&launched, &attr, ast_httpd_helper_thread, ser)) { + ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno)); + fclose(ser->f); + free(ser); + } + } else { + ast_log(LOG_WARNING, "fdopen failed!\n"); + close(ser->fd); + free(ser); + } + } + } + return NULL; +} + +char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, size_t buflen) +{ + char *c; + c = buf; + ast_build_string(&c, &buflen, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var, val); + if (expires) + ast_build_string(&c, &buflen, "; Max-Age=%d", expires); + ast_build_string(&c, &buflen, "\r\n"); + return buf; +} + + +static void http_server_start(struct sockaddr_in *sin) +{ + int flags; + int x = 1; + + /* Do nothing if nothing has changed */ + if (!memcmp(&oldsin, sin, sizeof(oldsin))) { + ast_log(LOG_DEBUG, "Nothing changed in http\n"); + return; + } + + memcpy(&oldsin, sin, sizeof(oldsin)); + + /* Shutdown a running server if there is one */ + if (master != AST_PTHREADT_NULL) { + pthread_cancel(master); + pthread_kill(master, SIGURG); + pthread_join(master, NULL); + } + + if (httpfd != -1) + close(httpfd); + + /* If there's no new server, stop here */ + if (!sin->sin_family) + return; + + + httpfd = socket(AF_INET, SOCK_STREAM, 0); + if (httpfd < 0) { + ast_log(LOG_WARNING, "Unable to allocate socket: %s\n", strerror(errno)); + return; + } + + setsockopt(httpfd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x)); + if (bind(httpfd, (struct sockaddr *)sin, sizeof(*sin))) { + ast_log(LOG_NOTICE, "Unable to bind http server to %s:%d: %s\n", + ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port), + strerror(errno)); + close(httpfd); + httpfd = -1; + return; + } + if (listen(httpfd, 10)) { + ast_log(LOG_NOTICE, "Unable to listen!\n"); + close(httpfd); + httpfd = -1; + return; + } + flags = fcntl(httpfd, F_GETFL); + fcntl(httpfd, F_SETFL, flags | O_NONBLOCK); + if (ast_pthread_create(&master, NULL, http_root, NULL)) { + ast_log(LOG_NOTICE, "Unable to launch http server on %s:%d: %s\n", + ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port), + strerror(errno)); + close(httpfd); + httpfd = -1; + } +} + +static int __ast_http_load(int reload) +{ + struct ast_config *cfg; + struct ast_variable *v; + int enabled=0; + int newenablestatic=0; + struct sockaddr_in sin; + struct hostent *hp; + struct ast_hostent ahp; + char newprefix[MAX_PREFIX]; + + memset(&sin, 0, sizeof(sin)); + sin.sin_port = 8088; + strcpy(newprefix, DEFAULT_PREFIX); + cfg = ast_config_load("http.conf"); + if (cfg) { + v = ast_variable_browse(cfg, "general"); + while(v) { + if (!strcasecmp(v->name, "enabled")) + enabled = ast_true(v->value); + else if (!strcasecmp(v->name, "enablestatic")) + newenablestatic = ast_true(v->value); + else if (!strcasecmp(v->name, "bindport")) + sin.sin_port = ntohs(atoi(v->value)); + else if (!strcasecmp(v->name, "bindaddr")) { + if ((hp = ast_gethostbyname(v->value, &ahp))) { + memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr)); + } else { + ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value); + } + } else if (!strcasecmp(v->name, "prefix")) { + if (!ast_strlen_zero(v->value)) { + newprefix[0] = '/'; + ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1); + } else { + newprefix[0] = '\0'; + } + + } + v = v->next; + } + ast_config_destroy(cfg); + } + if (enabled) + sin.sin_family = AF_INET; + if (strcmp(prefix, newprefix)) { + ast_copy_string(prefix, newprefix, sizeof(prefix)); + prefix_len = strlen(prefix); + } + enablestatic = newenablestatic; + http_server_start(&sin); + return 0; +} + +static int handle_show_http(int fd, int argc, char *argv[]) +{ + struct ast_http_uri *urih; + if (argc != 3) + return RESULT_SHOWUSAGE; + ast_cli(fd, "HTTP Server Status:\n"); + ast_cli(fd, "Prefix: %s\n", prefix); + if (oldsin.sin_family) + ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n", + ast_inet_ntoa(oldsin.sin_addr), + ntohs(oldsin.sin_port)); + else + ast_cli(fd, "Server Disabled\n\n"); + ast_cli(fd, "Enabled URI's:\n"); + urih = uris; + while(urih){ + ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description); + urih = urih->next; + } + if (!uris) + ast_cli(fd, "None.\n"); + return RESULT_SUCCESS; +} + +int ast_http_reload(void) +{ + return __ast_http_load(1); +} + +static char show_http_help[] = +"Usage: http show status\n" +" Shows status of internal HTTP engine\n"; + +static struct ast_cli_entry http_cli[] = { + { { "http", "show", "status", NULL }, handle_show_http, + "Display HTTP server status", show_http_help }, +}; + +int ast_http_init(void) +{ + ast_http_uri_link(&statusuri); + ast_http_uri_link(&staticuri); + ast_cli_register_multiple(http_cli, sizeof(http_cli) / sizeof(http_cli[0])); + return __ast_http_load(0); +} diff --git a/main/image.c b/main/image.c new file mode 100644 index 000000000..ff67c0340 --- /dev/null +++ b/main/image.c @@ -0,0 +1,210 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Image Management + * + * \author Mark Spencer + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/sched.h" +#include "asterisk/options.h" +#include "asterisk/channel.h" +#include "asterisk/logger.h" +#include "asterisk/file.h" +#include "asterisk/image.h" +#include "asterisk/translate.h" +#include "asterisk/cli.h" +#include "asterisk/lock.h" + +static AST_LIST_HEAD_STATIC(imagers, ast_imager); + +int ast_image_register(struct ast_imager *img) +{ + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Registered format '%s' (%s)\n", img->name, img->desc); + AST_LIST_LOCK(&imagers); + AST_LIST_INSERT_HEAD(&imagers, img, list); + AST_LIST_UNLOCK(&imagers); + return 0; +} + +void ast_image_unregister(struct ast_imager *img) +{ + struct ast_imager *i; + + AST_LIST_LOCK(&imagers); + AST_LIST_TRAVERSE_SAFE_BEGIN(&imagers, i, list) { + if (i == img) { + AST_LIST_REMOVE_CURRENT(&imagers, list); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + AST_LIST_UNLOCK(&imagers); + if (i && (option_verbose > 1)) + ast_verbose(VERBOSE_PREFIX_2 "Unregistered format '%s' (%s)\n", img->name, img->desc); +} + +int ast_supports_images(struct ast_channel *chan) +{ + if (!chan || !chan->tech) + return 0; + if (!chan->tech->send_image) + return 0; + return 1; +} + +static int file_exists(char *filename) +{ + int res; + struct stat st; + res = stat(filename, &st); + if (!res) + return st.st_size; + return 0; +} + +static void make_filename(char *buf, int len, char *filename, const char *preflang, char *ext) +{ + if (filename[0] == '/') { + if (!ast_strlen_zero(preflang)) + snprintf(buf, len, "%s-%s.%s", filename, preflang, ext); + else + snprintf(buf, len, "%s.%s", filename, ext); + } else { + if (!ast_strlen_zero(preflang)) + snprintf(buf, len, "%s/%s/%s-%s.%s", ast_config_AST_DATA_DIR, "images", filename, preflang, ext); + else + snprintf(buf, len, "%s/%s/%s.%s", ast_config_AST_DATA_DIR, "images", filename, ext); + } +} + +struct ast_frame *ast_read_image(char *filename, const char *preflang, int format) +{ + struct ast_imager *i; + char buf[256]; + char tmp[80]; + char *e; + struct ast_imager *found = NULL; + int fd; + int len=0; + struct ast_frame *f = NULL; + + AST_LIST_LOCK(&imagers); + AST_LIST_TRAVERSE(&imagers, i, list) { + if (i->format & format) { + char *stringp=NULL; + strncpy(tmp, i->exts, sizeof(tmp)-1); + stringp=tmp; + e = strsep(&stringp, "|"); + while(e) { + make_filename(buf, sizeof(buf), filename, preflang, e); + if ((len = file_exists(buf))) { + found = i; + break; + } + make_filename(buf, sizeof(buf), filename, NULL, e); + if ((len = file_exists(buf))) { + found = i; + break; + } + e = strsep(&stringp, "|"); + } + } + if (found) + break; + } + + if (found) { + fd = open(buf, O_RDONLY); + if (fd > -1) { + if (!found->identify || found->identify(fd)) { + /* Reset file pointer */ + lseek(fd, 0, SEEK_SET); + f = found->read_image(fd,len); + } else + ast_log(LOG_WARNING, "%s does not appear to be a %s file\n", buf, found->name); + close(fd); + } else + ast_log(LOG_WARNING, "Unable to open '%s': %s\n", buf, strerror(errno)); + } else + ast_log(LOG_WARNING, "Image file '%s' not found\n", filename); + + AST_LIST_UNLOCK(&imagers); + + return f; +} + + +int ast_send_image(struct ast_channel *chan, char *filename) +{ + struct ast_frame *f; + int res = -1; + if (chan->tech->send_image) { + f = ast_read_image(filename, chan->language, -1); + if (f) { + res = chan->tech->send_image(chan, f); + ast_frfree(f); + } + } + return res; +} + +static int show_image_formats(int fd, int argc, char *argv[]) +{ +#define FORMAT "%10s %10s %50s %10s\n" +#define FORMAT2 "%10s %10s %50s %10s\n" + struct ast_imager *i; + if (argc != 3) + return RESULT_SHOWUSAGE; + ast_cli(fd, FORMAT, "Name", "Extensions", "Description", "Format"); + AST_LIST_TRAVERSE(&imagers, i, list) + ast_cli(fd, FORMAT2, i->name, i->exts, i->desc, ast_getformatname(i->format)); + return RESULT_SUCCESS; +} + +struct ast_cli_entry show_images = +{ + { "show", "image", "formats" }, + show_image_formats, + "Displays image formats", +"Usage: show image formats\n" +" displays currently registered image formats (if any)\n" +}; + + +int ast_image_init(void) +{ + return ast_cli_register(&show_images); +} + diff --git a/main/indications.c b/main/indications.c new file mode 100644 index 000000000..359a5900f --- /dev/null +++ b/main/indications.c @@ -0,0 +1,600 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2002, Pauline Middelink + * + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Tone Management + * + * \author Pauline Middelink + * + * This set of function allow us to play a list of tones on a channel. + * Each element has two frequencies, which are mixed together and a + * duration. For silence both frequencies can be set to 0. + * The playtones can be given as a comma separated string. + * + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include + +#include "asterisk/indications.h" +#include "asterisk/frame.h" +#include "asterisk/options.h" +#include "asterisk/channel.h" +#include "asterisk/logger.h" +#include "asterisk/lock.h" +#include "asterisk/utils.h" + +static int midi_tohz[128] = { + 8,8,9,9,10,10,11,12,12,13,14, + 15,16,17,18,19,20,21,23,24,25, + 27,29,30,32,34,36,38,41,43,46, + 48,51,55,58,61,65,69,73,77,82, + 87,92,97,103,110,116,123,130,138,146, + 155,164,174,184,195,207,220,233,246,261, + 277,293,311,329,349,369,391,415,440,466, + 493,523,554,587,622,659,698,739,783,830, + 880,932,987,1046,1108,1174,1244,1318,1396,1479, + 1567,1661,1760,1864,1975,2093,2217,2349,2489,2637, + 2793,2959,3135,3322,3520,3729,3951,4186,4434,4698, + 4978,5274,5587,5919,6271,6644,7040,7458,7902,8372, + 8869,9397,9956,10548,11175,11839,12543 + }; + +struct playtones_item { + int fac1; + int init_v2_1; + int init_v3_1; + int fac2; + int init_v2_2; + int init_v3_2; + int modulate; + int duration; +}; + +struct playtones_def { + int vol; + int reppos; + int nitems; + int interruptible; + struct playtones_item *items; +}; + +struct playtones_state { + int vol; + int v1_1; + int v2_1; + int v3_1; + int v1_2; + int v2_2; + int v3_2; + int reppos; + int nitems; + struct playtones_item *items; + int npos; + int oldnpos; + int pos; + int origwfmt; + struct ast_frame f; + unsigned char offset[AST_FRIENDLY_OFFSET]; + short data[4000]; +}; + +static void playtones_release(struct ast_channel *chan, void *params) +{ + struct playtones_state *ps = params; + if (chan) { + ast_set_write_format(chan, ps->origwfmt); + } + if (ps->items) free(ps->items); + free(ps); +} + +static void * playtones_alloc(struct ast_channel *chan, void *params) +{ + struct playtones_def *pd = params; + struct playtones_state *ps; + if (!(ps = ast_calloc(1, sizeof(*ps)))) + return NULL; + ps->origwfmt = chan->writeformat; + if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) { + ast_log(LOG_WARNING, "Unable to set '%s' to signed linear format (write)\n", chan->name); + playtones_release(NULL, ps); + ps = NULL; + } else { + ps->vol = pd->vol; + ps->reppos = pd->reppos; + ps->nitems = pd->nitems; + ps->items = pd->items; + ps->oldnpos = -1; + } + /* Let interrupts interrupt :) */ + if (pd->interruptible) + ast_set_flag(chan, AST_FLAG_WRITE_INT); + else + ast_clear_flag(chan, AST_FLAG_WRITE_INT); + return ps; +} + +static int playtones_generator(struct ast_channel *chan, void *data, int len, int samples) +{ + struct playtones_state *ps = data; + struct playtones_item *pi; + int x; + /* we need to prepare a frame with 16 * timelen samples as we're + * generating SLIN audio + */ + len = samples * 2; + if (len > sizeof(ps->data) / 2 - 1) { + ast_log(LOG_WARNING, "Can't generate that much data!\n"); + return -1; + } + memset(&ps->f, 0, sizeof(ps->f)); + + pi = &ps->items[ps->npos]; + if (ps->oldnpos != ps->npos) { + /* Load new parameters */ + ps->v1_1 = 0; + ps->v2_1 = pi->init_v2_1; + ps->v3_1 = pi->init_v3_1; + ps->v1_2 = 0; + ps->v2_2 = pi->init_v2_2; + ps->v3_2 = pi->init_v3_2; + ps->oldnpos = ps->npos; + } + for (x=0;xv1_1 = ps->v2_1; + ps->v2_1 = ps->v3_1; + ps->v3_1 = (pi->fac1 * ps->v2_1 >> 15) - ps->v1_1; + + ps->v1_2 = ps->v2_2; + ps->v2_2 = ps->v3_2; + ps->v3_2 = (pi->fac2 * ps->v2_2 >> 15) - ps->v1_2; + if (pi->modulate) { + int p; + p = ps->v3_2 - 32768; + if (p < 0) p = -p; + p = ((p * 9) / 10) + 1; + ps->data[x] = (ps->v3_1 * p) >> 15; + } else + ps->data[x] = ps->v3_1 + ps->v3_2; + } + + ps->f.frametype = AST_FRAME_VOICE; + ps->f.subclass = AST_FORMAT_SLINEAR; + ps->f.datalen = len; + ps->f.samples = samples; + ps->f.offset = AST_FRIENDLY_OFFSET; + ps->f.data = ps->data; + ps->f.delivery.tv_sec = 0; + ps->f.delivery.tv_usec = 0; + ast_write(chan, &ps->f); + + ps->pos += x; + if (pi->duration && ps->pos >= pi->duration * 8) { /* item finished? */ + ps->pos = 0; /* start new item */ + ps->npos++; + if (ps->npos >= ps->nitems) { /* last item? */ + if (ps->reppos == -1) /* repeat set? */ + return -1; + ps->npos = ps->reppos; /* redo from top */ + } + } + return 0; +} + +static struct ast_generator playtones = { + alloc: playtones_alloc, + release: playtones_release, + generate: playtones_generator, +}; + +int ast_playtones_start(struct ast_channel *chan, int vol, const char *playlst, int interruptible) +{ + char *s, *data = ast_strdupa(playlst); /* cute */ + struct playtones_def d = { vol, -1, 0, 1, NULL}; + char *stringp; + char *separator; + + if (vol < 1) + d.vol = 7219; /* Default to -8db */ + + d.interruptible = interruptible; + + stringp=data; + /* the stringp/data is not null here */ + /* check if the data is separated with '|' or with ',' by default */ + if (strchr(stringp,'|')) + separator = "|"; + else + separator = ","; + s = strsep(&stringp,separator); + while (s && *s) { + int freq1, freq2, time, modulate=0, midinote=0; + + if (s[0]=='!') + s++; + else if (d.reppos == -1) + d.reppos = d.nitems; + if (sscanf(s, "%d+%d/%d", &freq1, &freq2, &time) == 3) { + /* f1+f2/time format */ + } else if (sscanf(s, "%d+%d", &freq1, &freq2) == 2) { + /* f1+f2 format */ + time = 0; + } else if (sscanf(s, "%d*%d/%d", &freq1, &freq2, &time) == 3) { + /* f1*f2/time format */ + modulate = 1; + } else if (sscanf(s, "%d*%d", &freq1, &freq2) == 2) { + /* f1*f2 format */ + time = 0; + modulate = 1; + } else if (sscanf(s, "%d/%d", &freq1, &time) == 2) { + /* f1/time format */ + freq2 = 0; + } else if (sscanf(s, "%d", &freq1) == 1) { + /* f1 format */ + freq2 = 0; + time = 0; + } else if (sscanf(s, "M%d+M%d/%d", &freq1, &freq2, &time) == 3) { + /* Mf1+Mf2/time format */ + midinote = 1; + } else if (sscanf(s, "M%d+M%d", &freq1, &freq2) == 2) { + /* Mf1+Mf2 format */ + time = 0; + midinote = 1; + } else if (sscanf(s, "M%d*M%d/%d", &freq1, &freq2, &time) == 3) { + /* Mf1*Mf2/time format */ + modulate = 1; + midinote = 1; + } else if (sscanf(s, "M%d*M%d", &freq1, &freq2) == 2) { + /* Mf1*Mf2 format */ + time = 0; + modulate = 1; + midinote = 1; + } else if (sscanf(s, "M%d/%d", &freq1, &time) == 2) { + /* Mf1/time format */ + freq2 = -1; + midinote = 1; + } else if (sscanf(s, "M%d", &freq1) == 1) { + /* Mf1 format */ + freq2 = -1; + time = 0; + midinote = 1; + } else { + ast_log(LOG_WARNING,"%s: tone component '%s' of '%s' is no good\n",chan->name,s,playlst); + return -1; + } + + if (midinote) { + /* midi notes must be between 0 and 127 */ + if ((freq1 >= 0) && (freq1 <= 127)) + freq1 = midi_tohz[freq1]; + else + freq1 = 0; + + if ((freq2 >= 0) && (freq2 <= 127)) + freq2 = midi_tohz[freq2]; + else + freq2 = 0; + } + + if (!(d.items = ast_realloc(d.items, (d.nitems + 1) * sizeof(*d.items)))) { + return -1; + } + d.items[d.nitems].fac1 = 2.0 * cos(2.0 * M_PI * (freq1 / 8000.0)) * 32768.0; + d.items[d.nitems].init_v2_1 = sin(-4.0 * M_PI * (freq1 / 8000.0)) * d.vol; + d.items[d.nitems].init_v3_1 = sin(-2.0 * M_PI * (freq1 / 8000.0)) * d.vol; + + d.items[d.nitems].fac2 = 2.0 * cos(2.0 * M_PI * (freq2 / 8000.0)) * 32768.0; + d.items[d.nitems].init_v2_2 = sin(-4.0 * M_PI * (freq2 / 8000.0)) * d.vol; + d.items[d.nitems].init_v3_2 = sin(-2.0 * M_PI * (freq2 / 8000.0)) * d.vol; + d.items[d.nitems].duration = time; + d.items[d.nitems].modulate = modulate; + d.nitems++; + + s = strsep(&stringp,separator); + } + + if (ast_activate_generator(chan, &playtones, &d)) { + free(d.items); + return -1; + } + return 0; +} + +void ast_playtones_stop(struct ast_channel *chan) +{ + ast_deactivate_generator(chan); +} + +/*--------------------------------------------*/ + +static struct tone_zone *tone_zones; +static struct tone_zone *current_tonezone; + +/* Protect the tone_zones list (highly unlikely that two things would change + * it at the same time, but still! */ +AST_MUTEX_DEFINE_STATIC(tzlock); + +struct tone_zone *ast_walk_indications(const struct tone_zone *cur) +{ + struct tone_zone *tz; + + if (cur == NULL) + return tone_zones; + ast_mutex_lock(&tzlock); + for (tz = tone_zones; tz; tz = tz->next) + if (tz == cur) + break; + if (tz) + tz = tz->next; + ast_mutex_unlock(&tzlock); + return tz; +} + +/* Set global indication country */ +int ast_set_indication_country(const char *country) +{ + if (country) { + struct tone_zone *z = ast_get_indication_zone(country); + if (z) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Setting default indication country to '%s'\n",country); + current_tonezone = z; + return 0; + } + } + return 1; /* not found */ +} + +/* locate tone_zone, given the country. if country == NULL, use the default country */ +struct tone_zone *ast_get_indication_zone(const char *country) +{ + struct tone_zone *tz; + int alias_loop = 0; + + /* we need some tonezone, pick the first */ + if (country == NULL && current_tonezone) + return current_tonezone; /* default country? */ + if (country == NULL && tone_zones) + return tone_zones; /* any country? */ + if (country == NULL) + return 0; /* not a single country insight */ + + ast_mutex_lock(&tzlock); + do { + for (tz=tone_zones; tz; tz=tz->next) { + if (strcasecmp(country,tz->country)==0) { + /* tone_zone found */ + if (tz->alias && tz->alias[0]) { + country = tz->alias; + break; + } + ast_mutex_unlock(&tzlock); + return tz; + } + } + } while (++alias_loop<20 && tz); + ast_mutex_unlock(&tzlock); + if (alias_loop==20) + ast_log(LOG_NOTICE,"Alias loop for '%s' forcefull broken\n",country); + /* nothing found, sorry */ + return 0; +} + +/* locate a tone_zone_sound, given the tone_zone. if tone_zone == NULL, use the default tone_zone */ +struct tone_zone_sound *ast_get_indication_tone(const struct tone_zone *zone, const char *indication) +{ + struct tone_zone_sound *ts; + + /* we need some tonezone, pick the first */ + if (zone == NULL && current_tonezone) + zone = current_tonezone; /* default country? */ + if (zone == NULL && tone_zones) + zone = tone_zones; /* any country? */ + if (zone == NULL) + return 0; /* not a single country insight */ + + ast_mutex_lock(&tzlock); + for (ts=zone->tones; ts; ts=ts->next) { + if (strcasecmp(indication,ts->name)==0) { + /* found indication! */ + ast_mutex_unlock(&tzlock); + return ts; + } + } + /* nothing found, sorry */ + ast_mutex_unlock(&tzlock); + return 0; +} + +/* helper function to delete a tone_zone in its entirety */ +static inline void free_zone(struct tone_zone* zone) +{ + while (zone->tones) { + struct tone_zone_sound *tmp = zone->tones->next; + free((void*)zone->tones->name); + free((void*)zone->tones->data); + free(zone->tones); + zone->tones = tmp; + } + if (zone->ringcadence) + free(zone->ringcadence); + free(zone); +} + +/*--------------------------------------------*/ + +/* add a new country, if country exists, it will be replaced. */ +int ast_register_indication_country(struct tone_zone *zone) +{ + struct tone_zone *tz,*pz; + + ast_mutex_lock(&tzlock); + for (pz=NULL,tz=tone_zones; tz; pz=tz,tz=tz->next) { + if (strcasecmp(zone->country,tz->country)==0) { + /* tone_zone already there, replace */ + zone->next = tz->next; + if (pz) + pz->next = zone; + else + tone_zones = zone; + /* if we are replacing the default zone, re-point it */ + if (tz == current_tonezone) + current_tonezone = zone; + /* now free the previous zone */ + free_zone(tz); + ast_mutex_unlock(&tzlock); + return 0; + } + } + /* country not there, add */ + zone->next = NULL; + if (pz) + pz->next = zone; + else + tone_zones = zone; + ast_mutex_unlock(&tzlock); + + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Registered indication country '%s'\n",zone->country); + return 0; +} + +/* remove an existing country and all its indications, country must exist. + * Also, all countries which are an alias for the specified country are removed. */ +int ast_unregister_indication_country(const char *country) +{ + struct tone_zone *tz, *pz = NULL, *tmp; + int res = -1; + + ast_mutex_lock(&tzlock); + tz = tone_zones; + while (tz) { + if (country==NULL || + (strcasecmp(country, tz->country)==0 || + strcasecmp(country, tz->alias)==0)) { + /* tone_zone found, remove */ + tmp = tz->next; + if (pz) + pz->next = tmp; + else + tone_zones = tmp; + /* if we are unregistering the default country, w'll notice */ + if (tz == current_tonezone) { + ast_log(LOG_NOTICE,"Removed default indication country '%s'\n",tz->country); + current_tonezone = NULL; + } + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Unregistered indication country '%s'\n",tz->country); + free_zone(tz); + if (tone_zones == tz) + tone_zones = tmp; + tz = tmp; + res = 0; + } + else { + /* next zone please */ + pz = tz; + tz = tz->next; + } + } + ast_mutex_unlock(&tzlock); + return res; +} + +/* add a new indication to a tone_zone. tone_zone must exist. if the indication already + * exists, it will be replaced. */ +int ast_register_indication(struct tone_zone *zone, const char *indication, const char *tonelist) +{ + struct tone_zone_sound *ts,*ps; + + /* is it an alias? stop */ + if (zone->alias[0]) + return -1; + + ast_mutex_lock(&tzlock); + for (ps=NULL,ts=zone->tones; ts; ps=ts,ts=ts->next) { + if (strcasecmp(indication,ts->name)==0) { + /* indication already there, replace */ + free((void*)ts->name); + free((void*)ts->data); + break; + } + } + if (!ts) { + /* not there, we have to add */ + if (!(ts = ast_malloc(sizeof(*ts)))) { + ast_mutex_unlock(&tzlock); + return -2; + } + ts->next = NULL; + } + if (!(ts->name = ast_strdup(indication)) || !(ts->data = ast_strdup(tonelist))) { + ast_mutex_unlock(&tzlock); + return -2; + } + if (ps) + ps->next = ts; + else + zone->tones = ts; + ast_mutex_unlock(&tzlock); + return 0; +} + +/* remove an existing country's indication. Both country and indication must exist */ +int ast_unregister_indication(struct tone_zone *zone, const char *indication) +{ + struct tone_zone_sound *ts,*ps = NULL, *tmp; + int res = -1; + + /* is it an alias? stop */ + if (zone->alias[0]) + return -1; + + ast_mutex_lock(&tzlock); + ts = zone->tones; + while (ts) { + if (strcasecmp(indication,ts->name)==0) { + /* indication found */ + tmp = ts->next; + if (ps) + ps->next = tmp; + else + zone->tones = tmp; + free((void*)ts->name); + free((void*)ts->data); + free(ts); + ts = tmp; + res = 0; + } + else { + /* next zone please */ + ps = ts; + ts = ts->next; + } + } + /* indication not found, goodbye */ + ast_mutex_unlock(&tzlock); + return res; +} diff --git a/main/io.c b/main/io.c new file mode 100644 index 000000000..220b29d50 --- /dev/null +++ b/main/io.c @@ -0,0 +1,371 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief I/O Managment (Derived from Cheops-NG) + * + * \author Mark Spencer + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include + +#include "asterisk/io.h" +#include "asterisk/logger.h" +#include "asterisk/utils.h" + +#ifdef DEBUG_IO +#define DEBUG DEBUG_M +#else +#define DEBUG(a) +#endif + +/* + * Kept for each file descriptor + */ +struct io_rec { + ast_io_cb callback; /* What is to be called */ + void *data; /* Data to be passed */ + int *id; /* ID number */ +}; + +/* These two arrays are keyed with + the same index. it's too bad that + pollfd doesn't have a callback field + or something like that. They grow as + needed, by GROW_SHRINK_AMOUNT structures + at once */ + +#define GROW_SHRINK_SIZE 512 + +/* Global variables are now in a struct in order to be + made threadsafe */ +struct io_context { + /* Poll structure */ + struct pollfd *fds; + /* Associated I/O records */ + struct io_rec *ior; + /* First available fd */ + unsigned int fdcnt; + /* Maximum available fd */ + unsigned int maxfdcnt; + /* Currently used io callback */ + int current_ioc; + /* Whether something has been deleted */ + int needshrink; +}; + +struct io_context *io_context_create(void) +{ + /* Create an I/O context */ + struct io_context *tmp; + if ((tmp = ast_malloc(sizeof(*tmp)))) { + tmp->needshrink = 0; + tmp->fdcnt = 0; + tmp->maxfdcnt = GROW_SHRINK_SIZE/2; + tmp->current_ioc = -1; + if (!(tmp->fds = ast_calloc(1, (GROW_SHRINK_SIZE / 2) * sizeof(*tmp->fds)))) { + free(tmp); + tmp = NULL; + } else { + if (!(tmp->ior = ast_calloc(1, (GROW_SHRINK_SIZE / 2) * sizeof(*tmp->ior)))) { + free(tmp->fds); + free(tmp); + tmp = NULL; + } + } + } + return tmp; +} + +void io_context_destroy(struct io_context *ioc) +{ + /* Free associated memory with an I/O context */ + if (ioc->fds) + free(ioc->fds); + if (ioc->ior) + free(ioc->ior); + free(ioc); +} + +static int io_grow(struct io_context *ioc) +{ + /* + * Grow the size of our arrays. Return 0 on success or + * -1 on failure + */ + void *tmp; + DEBUG(ast_log(LOG_DEBUG, "io_grow()\n")); + ioc->maxfdcnt += GROW_SHRINK_SIZE; + if ((tmp = ast_realloc(ioc->ior, (ioc->maxfdcnt + 1) * sizeof(*ioc->ior)))) { + ioc->ior = tmp; + if ((tmp = ast_realloc(ioc->fds, (ioc->maxfdcnt + 1) * sizeof(*ioc->fds)))) { + ioc->fds = tmp; + } else { + /* + * Failed to allocate enough memory for the pollfd. Not + * really any need to shrink back the iorec's as we'll + * probably want to grow them again soon when more memory + * is available, and then they'll already be the right size + */ + ioc->maxfdcnt -= GROW_SHRINK_SIZE; + return -1; + } + } else { + /* + * Memory allocation failure. We return to the old size, and + * return a failure + */ + ioc->maxfdcnt -= GROW_SHRINK_SIZE; + return -1; + } + return 0; +} + +int *ast_io_add(struct io_context *ioc, int fd, ast_io_cb callback, short events, void *data) +{ + /* + * Add a new I/O entry for this file descriptor + * with the given event mask, to call callback with + * data as an argument. Returns NULL on failure. + */ + int *ret; + DEBUG(ast_log(LOG_DEBUG, "ast_io_add()\n")); + if (ioc->fdcnt >= ioc->maxfdcnt) { + /* + * We don't have enough space for this entry. We need to + * reallocate maxfdcnt poll fd's and io_rec's, or back out now. + */ + if (io_grow(ioc)) + return NULL; + } + + /* + * At this point, we've got sufficiently large arrays going + * and we can make an entry for it in the pollfd and io_r + * structures. + */ + ioc->fds[ioc->fdcnt].fd = fd; + ioc->fds[ioc->fdcnt].events = events; + ioc->fds[ioc->fdcnt].revents = 0; + ioc->ior[ioc->fdcnt].callback = callback; + ioc->ior[ioc->fdcnt].data = data; + if (!(ioc->ior[ioc->fdcnt].id = ast_malloc(sizeof(*ioc->ior[ioc->fdcnt].id)))) { + /* Bonk if we couldn't allocate an int */ + return NULL; + } + *(ioc->ior[ioc->fdcnt].id) = ioc->fdcnt; + ret = ioc->ior[ioc->fdcnt].id; + ioc->fdcnt++; + return ret; +} + +int *ast_io_change(struct io_context *ioc, int *id, int fd, ast_io_cb callback, short events, void *data) +{ + if (*id < ioc->fdcnt) { + if (fd > -1) + ioc->fds[*id].fd = fd; + if (callback) + ioc->ior[*id].callback = callback; + if (events) + ioc->fds[*id].events = events; + if (data) + ioc->ior[*id].data = data; + return id; + } + return NULL; +} + +static int io_shrink(struct io_context *ioc) +{ + int getfrom; + int putto = 0; + /* + * Bring the fields from the very last entry to cover over + * the entry we are removing, then decrease the size of the + * arrays by one. + */ + for (getfrom = 0; getfrom < ioc->fdcnt; getfrom++) { + if (ioc->ior[getfrom].id) { + /* In use, save it */ + if (getfrom != putto) { + ioc->fds[putto] = ioc->fds[getfrom]; + ioc->ior[putto] = ioc->ior[getfrom]; + *(ioc->ior[putto].id) = putto; + } + putto++; + } + } + ioc->fdcnt = putto; + ioc->needshrink = 0; + /* FIXME: We should free some memory if we have lots of unused + io structs */ + return 0; +} + +int ast_io_remove(struct io_context *ioc, int *_id) +{ + int x; + if (!_id) { + ast_log(LOG_WARNING, "Asked to remove NULL?\n"); + return -1; + } + for (x = 0; x < ioc->fdcnt; x++) { + if (ioc->ior[x].id == _id) { + /* Free the int immediately and set to NULL so we know it's unused now */ + free(ioc->ior[x].id); + ioc->ior[x].id = NULL; + ioc->fds[x].events = 0; + ioc->fds[x].revents = 0; + ioc->needshrink = 1; + if (!ioc->current_ioc) + io_shrink(ioc); + return 0; + } + } + + ast_log(LOG_NOTICE, "Unable to remove unknown id %p\n", _id); + return -1; +} + +int ast_io_wait(struct io_context *ioc, int howlong) +{ + /* + * Make the poll call, and call + * the callbacks for anything that needs + * to be handled + */ + int res; + int x; + int origcnt; + DEBUG(ast_log(LOG_DEBUG, "ast_io_wait()\n")); + res = poll(ioc->fds, ioc->fdcnt, howlong); + if (res > 0) { + /* + * At least one event + */ + origcnt = ioc->fdcnt; + for(x = 0; x < origcnt; x++) { + /* Yes, it is possible for an entry to be deleted and still have an + event waiting if it occurs after the original calling id */ + if (ioc->fds[x].revents && ioc->ior[x].id) { + /* There's an event waiting */ + ioc->current_ioc = *ioc->ior[x].id; + if (ioc->ior[x].callback) { + if (!ioc->ior[x].callback(ioc->ior[x].id, ioc->fds[x].fd, ioc->fds[x].revents, ioc->ior[x].data)) { + /* Time to delete them since they returned a 0 */ + ast_io_remove(ioc, ioc->ior[x].id); + } + } + ioc->current_ioc = -1; + } + } + if (ioc->needshrink) + io_shrink(ioc); + } + return res; +} + +void ast_io_dump(struct io_context *ioc) +{ + /* + * Print some debugging information via + * the logger interface + */ + int x; + ast_log(LOG_DEBUG, "Asterisk IO Dump: %d entries, %d max entries\n", ioc->fdcnt, ioc->maxfdcnt); + ast_log(LOG_DEBUG, "================================================\n"); + ast_log(LOG_DEBUG, "| ID FD Callback Data Events |\n"); + ast_log(LOG_DEBUG, "+------+------+-----------+-----------+--------+\n"); + for (x = 0; x < ioc->fdcnt; x++) { + ast_log(LOG_DEBUG, "| %.4d | %.4d | %p | %p | %.6x |\n", + *ioc->ior[x].id, + ioc->fds[x].fd, + ioc->ior[x].callback, + ioc->ior[x].data, + ioc->fds[x].events); + } + ast_log(LOG_DEBUG, "================================================\n"); +} + +/* Unrelated I/O functions */ + +int ast_hide_password(int fd) +{ + struct termios tios; + int res; + int old; + if (!isatty(fd)) + return -1; + res = tcgetattr(fd, &tios); + if (res < 0) + return -1; + old = tios.c_lflag & (ECHO | ECHONL); + tios.c_lflag &= ~ECHO; + tios.c_lflag |= ECHONL; + res = tcsetattr(fd, TCSAFLUSH, &tios); + if (res < 0) + return -1; + return old; +} + +int ast_restore_tty(int fd, int oldstate) +{ + int res; + struct termios tios; + if (oldstate < 0) + return 0; + res = tcgetattr(fd, &tios); + if (res < 0) + return -1; + tios.c_lflag &= ~(ECHO | ECHONL); + tios.c_lflag |= oldstate; + res = tcsetattr(fd, TCSAFLUSH, &tios); + if (res < 0) + return -1; + return 0; +} + +int ast_get_termcols(int fd) +{ + struct winsize win; + int cols = 0; + + if (!isatty(fd)) + return -1; + + if ( ioctl(fd, TIOCGWINSZ, &win) != -1 ) { + if ( !cols && win.ws_col > 0 ) + cols = (int) win.ws_col; + } else { + /* assume 80 characters if the ioctl fails for some reason */ + cols = 80; + } + + return cols; +} + diff --git a/main/jitterbuf.c b/main/jitterbuf.c new file mode 100644 index 000000000..a541d67e5 --- /dev/null +++ b/main/jitterbuf.c @@ -0,0 +1,825 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2004-2005, Horizon Wimba, Inc. + * + * Contributors: + * Steve Kann + * + * A license has been granted to Digium (via disclaimer) for the use of + * this code. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief jitterbuf: an application-independent jitterbuffer + * \author Steve Kann + * + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include + +#include "jitterbuf.h" +#include "asterisk/utils.h" + +/*! define these here, just for ancient compiler systems */ +#define JB_LONGMAX 2147483647L +#define JB_LONGMIN (-JB_LONGMAX - 1L) + +#define jb_warn(...) (warnf ? warnf(__VA_ARGS__) : (void)0) +#define jb_err(...) (errf ? errf(__VA_ARGS__) : (void)0) +#define jb_dbg(...) (dbgf ? dbgf(__VA_ARGS__) : (void)0) + +#ifdef DEEP_DEBUG +#define jb_dbg2(...) (dbgf ? dbgf(__VA_ARGS__) : (void)0) +#else +#define jb_dbg2(...) ((void)0) +#endif + +static jb_output_function_t warnf, errf, dbgf; + +void jb_setoutput(jb_output_function_t err, jb_output_function_t warn, jb_output_function_t dbg) +{ + errf = err; + warnf = warn; + dbgf = dbg; +} + +static void increment_losspct(jitterbuf *jb) +{ + jb->info.losspct = (100000 + 499 * jb->info.losspct)/500; +} + +static void decrement_losspct(jitterbuf *jb) +{ + jb->info.losspct = (499 * jb->info.losspct)/500; +} + +void jb_reset(jitterbuf *jb) +{ + /* only save settings */ + jb_conf s = jb->info.conf; + memset(jb, 0, sizeof(*jb)); + jb->info.conf = s; + + /* initialize length */ + jb->info.current = jb->info.target = JB_TARGET_EXTRA; + jb->info.silence_begin_ts = -1; +} + +jitterbuf * jb_new() +{ + jitterbuf *jb; + + if (!(jb = ast_malloc(sizeof(*jb)))) + return NULL; + + jb_reset(jb); + + jb_dbg2("jb_new() = %x\n", jb); + return jb; +} + +void jb_destroy(jitterbuf *jb) +{ + jb_frame *frame; + jb_dbg2("jb_destroy(%x)\n", jb); + + /* free all the frames on the "free list" */ + frame = jb->free; + while (frame != NULL) { + jb_frame *next = frame->next; + free(frame); + frame = next; + } + + /* free ourselves! */ + free(jb); +} + + + +#if 0 +static int longcmp(const void *a, const void *b) +{ + return *(long *)a - *(long *)b; +} +#endif + +/*! \brief simple history manipulation + \note maybe later we can make the history buckets variable size, or something? */ +/* drop parameter determines whether we will drop outliers to minimize + * delay */ +static int history_put(jitterbuf *jb, long ts, long now, long ms) +{ + long delay = now - (ts - jb->info.resync_offset); + long threshold = 2 * jb->info.jitter + jb->info.conf.resync_threshold; + long kicked; + + /* don't add special/negative times to history */ + if (ts <= 0) + return 0; + + /* check for drastic change in delay */ + if (jb->info.conf.resync_threshold != -1) { + if (abs(delay - jb->info.last_delay) > threshold) { + jb->info.cnt_delay_discont++; + if (jb->info.cnt_delay_discont > 3) { + /* resync the jitterbuffer */ + jb->info.cnt_delay_discont = 0; + jb->hist_ptr = 0; + jb->hist_maxbuf_valid = 0; + + jb_warn("Resyncing the jb. last_delay %ld, this delay %ld, threshold %ld, new offset %ld\n", jb->info.last_delay, delay, threshold, ts - now); + jb->info.resync_offset = ts - now; + jb->info.last_delay = delay = 0; /* after resync, frame is right on time */ + } else { + return -1; + } + } else { + jb->info.last_delay = delay; + jb->info.cnt_delay_discont = 0; + } + } + + kicked = jb->history[jb->hist_ptr % JB_HISTORY_SZ]; + + jb->history[(jb->hist_ptr++) % JB_HISTORY_SZ] = delay; + + /* optimization; the max/min buffers don't need to be recalculated, if this packet's + * entry doesn't change them. This happens if this packet is not involved, _and_ any packet + * that got kicked out of the history is also not involved + * We do a number of comparisons, but it's probably still worthwhile, because it will usually + * succeed, and should be a lot faster than going through all 500 packets in history */ + if (!jb->hist_maxbuf_valid) + return 0; + + /* don't do this until we've filled history + * (reduces some edge cases below) */ + if (jb->hist_ptr < JB_HISTORY_SZ) + goto invalidate; + + /* if the new delay would go into min */ + if (delay < jb->hist_minbuf[JB_HISTORY_MAXBUF_SZ-1]) + goto invalidate; + + /* or max.. */ + if (delay > jb->hist_maxbuf[JB_HISTORY_MAXBUF_SZ-1]) + goto invalidate; + + /* or the kicked delay would be in min */ + if (kicked <= jb->hist_minbuf[JB_HISTORY_MAXBUF_SZ-1]) + goto invalidate; + + if (kicked >= jb->hist_maxbuf[JB_HISTORY_MAXBUF_SZ-1]) + goto invalidate; + + /* if we got here, we don't need to invalidate, 'cause this delay didn't + * affect things */ + return 0; + /* end optimization */ + + +invalidate: + jb->hist_maxbuf_valid = 0; + return 0; +} + +static void history_calc_maxbuf(jitterbuf *jb) +{ + int i,j; + + if (jb->hist_ptr == 0) + return; + + + /* initialize maxbuf/minbuf to the latest value */ + for (i=0;ihist_maxbuf[i] = jb->history[(jb->hist_ptr-1) % JB_HISTORY_SZ]; + * jb->hist_minbuf[i] = jb->history[(jb->hist_ptr-1) % JB_HISTORY_SZ]; + */ + jb->hist_maxbuf[i] = JB_LONGMIN; + jb->hist_minbuf[i] = JB_LONGMAX; + } + + /* use insertion sort to populate maxbuf */ + /* we want it to be the top "n" values, in order */ + + /* start at the beginning, or JB_HISTORY_SZ frames ago */ + i = (jb->hist_ptr > JB_HISTORY_SZ) ? (jb->hist_ptr - JB_HISTORY_SZ) : 0; + + for (;ihist_ptr;i++) { + long toins = jb->history[i % JB_HISTORY_SZ]; + + /* if the maxbuf should get this */ + if (toins > jb->hist_maxbuf[JB_HISTORY_MAXBUF_SZ-1]) { + + /* insertion-sort it into the maxbuf */ + for (j=0;j jb->hist_maxbuf[j]) { + /* move over */ + memmove(jb->hist_maxbuf + j + 1, jb->hist_maxbuf + j, (JB_HISTORY_MAXBUF_SZ - (j + 1)) * sizeof(jb->hist_maxbuf[0])); + /* insert */ + jb->hist_maxbuf[j] = toins; + + break; + } + } + } + + /* if the minbuf should get this */ + if (toins < jb->hist_minbuf[JB_HISTORY_MAXBUF_SZ-1]) { + + /* insertion-sort it into the maxbuf */ + for (j=0;jhist_minbuf[j]) { + /* move over */ + memmove(jb->hist_minbuf + j + 1, jb->hist_minbuf + j, (JB_HISTORY_MAXBUF_SZ - (j + 1)) * sizeof(jb->hist_minbuf[0])); + /* insert */ + jb->hist_minbuf[j] = toins; + + break; + } + } + } + + if (0) { + int k; + fprintf(stderr, "toins = %ld\n", toins); + fprintf(stderr, "maxbuf ="); + for (k=0;khist_maxbuf[k]); + fprintf(stderr, "\nminbuf ="); + for (k=0;khist_minbuf[k]); + fprintf(stderr, "\n"); + } + } + + jb->hist_maxbuf_valid = 1; +} + +static void history_get(jitterbuf *jb) +{ + long max, min, jitter; + int index; + int count; + + if (!jb->hist_maxbuf_valid) + history_calc_maxbuf(jb); + + /* count is how many items in history we're examining */ + count = (jb->hist_ptr < JB_HISTORY_SZ) ? jb->hist_ptr : JB_HISTORY_SZ; + + /* index is the "n"ths highest/lowest that we'll look for */ + index = count * JB_HISTORY_DROPPCT / 100; + + /* sanity checks for index */ + if (index > (JB_HISTORY_MAXBUF_SZ - 1)) + index = JB_HISTORY_MAXBUF_SZ - 1; + + + if (index < 0) { + jb->info.min = 0; + jb->info.jitter = 0; + return; + } + + max = jb->hist_maxbuf[index]; + min = jb->hist_minbuf[index]; + + jitter = max - min; + + /* these debug stmts compare the difference between looking at the absolute jitter, and the + * values we get by throwing away the outliers */ + /* + fprintf(stderr, "[%d] min=%d, max=%d, jitter=%d\n", index, min, max, jitter); + fprintf(stderr, "[%d] min=%d, max=%d, jitter=%d\n", 0, jb->hist_minbuf[0], jb->hist_maxbuf[0], jb->hist_maxbuf[0]-jb->hist_minbuf[0]); + */ + + jb->info.min = min; + jb->info.jitter = jitter; +} + +/* returns 1 if frame was inserted into head of queue, 0 otherwise */ +static int queue_put(jitterbuf *jb, void *data, const enum jb_frame_type type, long ms, long ts) +{ + jb_frame *frame; + jb_frame *p; + int head = 0; + long resync_ts = ts - jb->info.resync_offset; + + if ((frame = jb->free)) { + jb->free = frame->next; + } else if (!(frame = ast_malloc(sizeof(*frame)))) { + jb_err("cannot allocate frame\n"); + return 0; + } + + jb->info.frames_cur++; + + frame->data = data; + frame->ts = resync_ts; + frame->ms = ms; + frame->type = type; + + /* + * frames are a circular list, jb-frames points to to the lowest ts, + * jb->frames->prev points to the highest ts + */ + + if (!jb->frames) { /* queue is empty */ + jb->frames = frame; + frame->next = frame; + frame->prev = frame; + head = 1; + } else if (resync_ts < jb->frames->ts) { + frame->next = jb->frames; + frame->prev = jb->frames->prev; + + frame->next->prev = frame; + frame->prev->next = frame; + + /* frame is out of order */ + jb->info.frames_ooo++; + + jb->frames = frame; + head = 1; + } else { + p = jb->frames; + + /* frame is out of order */ + if (resync_ts < p->prev->ts) jb->info.frames_ooo++; + + while (resync_ts < p->prev->ts && p->prev != jb->frames) + p = p->prev; + + frame->next = p; + frame->prev = p->prev; + + frame->next->prev = frame; + frame->prev->next = frame; + } + return head; +} + +static long queue_next(jitterbuf *jb) +{ + if (jb->frames) + return jb->frames->ts; + else + return -1; +} + +static long queue_last(jitterbuf *jb) +{ + if (jb->frames) + return jb->frames->prev->ts; + else + return -1; +} + +static jb_frame *_queue_get(jitterbuf *jb, long ts, int all) +{ + jb_frame *frame; + frame = jb->frames; + + if (!frame) + return NULL; + + /*jb_warn("queue_get: ASK %ld FIRST %ld\n", ts, frame->ts); */ + + if (all || ts >= frame->ts) { + /* remove this frame */ + frame->prev->next = frame->next; + frame->next->prev = frame->prev; + + if (frame->next == frame) + jb->frames = NULL; + else + jb->frames = frame->next; + + + /* insert onto "free" single-linked list */ + frame->next = jb->free; + jb->free = frame; + + jb->info.frames_cur--; + + /* we return the frame pointer, even though it's on free list, + * but caller must copy data */ + return frame; + } + + return NULL; +} + +static jb_frame *queue_get(jitterbuf *jb, long ts) +{ + return _queue_get(jb,ts,0); +} + +static jb_frame *queue_getall(jitterbuf *jb) +{ + return _queue_get(jb,0,1); +} + +#if 0 +/* some diagnostics */ +static void jb_dbginfo(jitterbuf *jb) +{ + if (dbgf == NULL) + return; + + jb_dbg("\njb info: fin=%ld fout=%ld flate=%ld flost=%ld fdrop=%ld fcur=%ld\n", + jb->info.frames_in, jb->info.frames_out, jb->info.frames_late, jb->info.frames_lost, jb->info.frames_dropped, jb->info.frames_cur); + + jb_dbg("jitter=%ld current=%ld target=%ld min=%ld sil=%d len=%d len/fcur=%ld\n", + jb->info.jitter, jb->info.current, jb->info.target, jb->info.min, jb->info.silence_begin_ts, jb->info.current - jb->info.min, + jb->info.frames_cur ? (jb->info.current - jb->info.min)/jb->info.frames_cur : -8); + if (jb->info.frames_in > 0) + jb_dbg("jb info: Loss PCT = %ld%%, Late PCT = %ld%%\n", + jb->info.frames_lost * 100/(jb->info.frames_in + jb->info.frames_lost), + jb->info.frames_late * 100/jb->info.frames_in); + jb_dbg("jb info: queue %d -> %d. last_ts %d (queue len: %d) last_ms %d\n", + queue_next(jb), + queue_last(jb), + jb->info.next_voice_ts, + queue_last(jb) - queue_next(jb), + jb->info.last_voice_ms); +} +#endif + +#ifdef DEEP_DEBUG +static void jb_chkqueue(jitterbuf *jb) +{ + int i=0; + jb_frame *p = jb->frames; + + if (!p) { + return; + } + + do { + if (p->next == NULL) { + jb_err("Queue is BROKEN at item [%d]", i); + } + i++; + p=p->next; + } while (p->next != jb->frames); +} + +static void jb_dbgqueue(jitterbuf *jb) +{ + int i=0; + jb_frame *p = jb->frames; + + jb_dbg("queue: "); + + if (!p) { + jb_dbg("EMPTY\n"); + return; + } + + do { + jb_dbg("[%d]=%ld ", i++, p->ts); + p=p->next; + } while (p->next != jb->frames); + + jb_dbg("\n"); +} +#endif + +enum jb_return_code jb_put(jitterbuf *jb, void *data, const enum jb_frame_type type, long ms, long ts, long now) +{ + jb_dbg2("jb_put(%x,%x,%ld,%ld,%ld)\n", jb, data, ms, ts, now); + + jb->info.frames_in++; + + if (type == JB_TYPE_VOICE) { + /* presently, I'm only adding VOICE frames to history and drift calculations; mostly because with the + * IAX integrations, I'm sending retransmitted control frames with their awkward timestamps through */ + if (history_put(jb,ts,now,ms)) + return JB_DROP; + } + + /* if put into head of queue, caller needs to reschedule */ + if (queue_put(jb,data,type,ms,ts)) { + return JB_SCHED; + } + return JB_OK; +} + + +static enum jb_return_code _jb_get(jitterbuf *jb, jb_frame *frameout, long now, long interpl) +{ + jb_frame *frame; + long diff; + static int dbg_cnt = 0; + + /*if ((now - jb_next(jb)) > 2 * jb->info.last_voice_ms) jb_warn("SCHED: %ld", (now - jb_next(jb))); */ + /* get jitter info */ + history_get(jb); + + if (dbg_cnt && dbg_cnt % 50 == 0) { + jb_dbg("\n"); + } + dbg_cnt++; + + /* target */ + jb->info.target = jb->info.jitter + jb->info.min + JB_TARGET_EXTRA; + + /* if a hard clamp was requested, use it */ + if ((jb->info.conf.max_jitterbuf) && ((jb->info.target - jb->info.min) > jb->info.conf.max_jitterbuf)) { + jb_dbg("clamping target from %d to %d\n", (jb->info.target - jb->info.min), jb->info.conf.max_jitterbuf); + jb->info.target = jb->info.min + jb->info.conf.max_jitterbuf; + } + + diff = jb->info.target - jb->info.current; + + /* jb_warn("diff = %d lms=%d last = %d now = %d\n", diff, */ + /* jb->info.last_voice_ms, jb->info.last_adjustment, now); */ + + /* let's work on non-silent case first */ + if (!jb->info.silence_begin_ts) { + /* we want to grow */ + if ((diff > 0) && + /* we haven't grown in the delay length */ + (((jb->info.last_adjustment + JB_ADJUST_DELAY) < now) || + /* we need to grow more than the "length" we have left */ + (diff > queue_last(jb) - queue_next(jb)) ) ) { + /* grow by interp frame length */ + jb->info.current += interpl; + jb->info.next_voice_ts += interpl; + jb->info.last_voice_ms = interpl; + jb->info.last_adjustment = now; + jb->info.cnt_contig_interp++; + if (jb->info.conf.max_contig_interp && jb->info.cnt_contig_interp >= jb->info.conf.max_contig_interp) { + jb->info.silence_begin_ts = jb->info.next_voice_ts - jb->info.current; + } + jb_dbg("G"); + return JB_INTERP; + } + + frame = queue_get(jb, jb->info.next_voice_ts - jb->info.current); + + /* not a voice frame; just return it. */ + if (frame && frame->type != JB_TYPE_VOICE) { + if (frame->type == JB_TYPE_SILENCE) { + jb->info.silence_begin_ts = frame->ts; + jb->info.cnt_contig_interp = 0; + } + + *frameout = *frame; + jb->info.frames_out++; + jb_dbg("o"); + return JB_OK; + } + + + /* voice frame is later than expected */ + if (frame && frame->ts + jb->info.current < jb->info.next_voice_ts) { + if (frame->ts + jb->info.current > jb->info.next_voice_ts - jb->info.last_voice_ms) { + /* either we interpolated past this frame in the last jb_get */ + /* or the frame is still in order, but came a little too quick */ + *frameout = *frame; + /* reset expectation for next frame */ + jb->info.next_voice_ts = frame->ts + jb->info.current + frame->ms; + jb->info.frames_out++; + decrement_losspct(jb); + jb->info.cnt_contig_interp = 0; + jb_dbg("v"); + return JB_OK; + } else { + /* voice frame is late */ + *frameout = *frame; + jb->info.frames_out++; + decrement_losspct(jb); + jb->info.frames_late++; + jb->info.frames_lost--; + jb_dbg("l"); + /*jb_warn("\nlate: wanted=%ld, this=%ld, next=%ld\n", jb->info.next_voice_ts - jb->info.current, frame->ts, queue_next(jb)); + jb_warninfo(jb); */ + return JB_DROP; + } + } + + /* keep track of frame sizes, to allow for variable sized-frames */ + if (frame && frame->ms > 0) { + jb->info.last_voice_ms = frame->ms; + } + + /* we want to shrink; shrink at 1 frame / 500ms */ + /* unless we don't have a frame, then shrink 1 frame */ + /* every 80ms (though perhaps we can shrink even faster */ + /* in this case) */ + if (diff < -JB_TARGET_EXTRA && + ((!frame && jb->info.last_adjustment + 80 < now) || + (jb->info.last_adjustment + 500 < now))) { + + jb->info.last_adjustment = now; + jb->info.cnt_contig_interp = 0; + + if (frame) { + *frameout = *frame; + /* shrink by frame size we're throwing out */ + jb->info.current -= frame->ms; + jb->info.frames_out++; + decrement_losspct(jb); + jb->info.frames_dropped++; + jb_dbg("s"); + return JB_DROP; + } else { + /* shrink by last_voice_ms */ + jb->info.current -= jb->info.last_voice_ms; + jb->info.frames_lost++; + increment_losspct(jb); + jb_dbg("S"); + return JB_NOFRAME; + } + } + + /* lost frame */ + if (!frame) { + /* this is a bit of a hack for now, but if we're close to + * target, and we find a missing frame, it makes sense to + * grow, because the frame might just be a bit late; + * otherwise, we presently get into a pattern where we return + * INTERP for the lost frame, then it shows up next, and we + * throw it away because it's late */ + /* I've recently only been able to replicate this using + * iaxclient talking to app_echo on asterisk. In this case, + * my outgoing packets go through asterisk's (old) + * jitterbuffer, and then might get an unusual increasing delay + * there if it decides to grow?? */ + /* Update: that might have been a different bug, that has been fixed.. + * But, this still seemed like a good idea, except that it ended up making a single actual + * lost frame get interpolated two or more times, when there was "room" to grow, so it might + * be a bit of a bad idea overall */ + /*if (diff > -1 * jb->info.last_voice_ms) { + jb->info.current += jb->info.last_voice_ms; + jb->info.last_adjustment = now; + jb_warn("g"); + return JB_INTERP; + } */ + jb->info.frames_lost++; + increment_losspct(jb); + jb->info.next_voice_ts += interpl; + jb->info.last_voice_ms = interpl; + jb->info.cnt_contig_interp++; + if (jb->info.conf.max_contig_interp && jb->info.cnt_contig_interp >= jb->info.conf.max_contig_interp) { + jb->info.silence_begin_ts = jb->info.next_voice_ts - jb->info.current; + } + jb_dbg("L"); + return JB_INTERP; + } + + /* normal case; return the frame, increment stuff */ + *frameout = *frame; + jb->info.next_voice_ts += frame->ms; + jb->info.frames_out++; + jb->info.cnt_contig_interp = 0; + decrement_losspct(jb); + jb_dbg("v"); + return JB_OK; + } else { + /* TODO: after we get the non-silent case down, we'll make the + * silent case -- basically, we'll just grow and shrink faster + * here, plus handle next_voice_ts a bit differently */ + + /* to disable silent special case altogether, just uncomment this: */ + /* jb->info.silence_begin_ts = 0; */ + + /* shrink interpl len every 10ms during silence */ + if (diff < -JB_TARGET_EXTRA && + jb->info.last_adjustment + 10 <= now) { + jb->info.current -= interpl; + jb->info.last_adjustment = now; + } + + frame = queue_get(jb, now - jb->info.current); + if (!frame) { + return JB_NOFRAME; + } else if (frame->type != JB_TYPE_VOICE) { + /* normal case; in silent mode, got a non-voice frame */ + *frameout = *frame; + jb->info.frames_out++; + return JB_OK; + } + if (frame->ts < jb->info.silence_begin_ts) { + /* voice frame is late */ + *frameout = *frame; + jb->info.frames_out++; + decrement_losspct(jb); + jb->info.frames_late++; + jb->info.frames_lost--; + jb_dbg("l"); + /*jb_warn("\nlate: wanted=%ld, this=%ld, next=%ld\n", jb->info.next_voice_ts - jb->info.current, frame->ts, queue_next(jb)); + jb_warninfo(jb); */ + return JB_DROP; + } else { + /* voice frame */ + /* try setting current to target right away here */ + jb->info.current = jb->info.target; + jb->info.silence_begin_ts = 0; + jb->info.next_voice_ts = frame->ts + jb->info.current + frame->ms; + jb->info.last_voice_ms = frame->ms; + jb->info.frames_out++; + decrement_losspct(jb); + *frameout = *frame; + jb_dbg("V"); + return JB_OK; + } + } +} + +long jb_next(jitterbuf *jb) +{ + if (jb->info.silence_begin_ts) { + long next = queue_next(jb); + if (next > 0) { + history_get(jb); + /* shrink during silence */ + if (jb->info.target - jb->info.current < -JB_TARGET_EXTRA) + return jb->info.last_adjustment + 10; + return next + jb->info.target; + } + else + return JB_LONGMAX; + } else { + return jb->info.next_voice_ts; + } +} + +enum jb_return_code jb_get(jitterbuf *jb, jb_frame *frameout, long now, long interpl) +{ + enum jb_return_code ret = _jb_get(jb, frameout, now, interpl); +#if 0 + static int lastts=0; + int thists = ((ret == JB_OK) || (ret == JB_DROP)) ? frameout->ts : 0; + jb_warn("jb_get(%x,%x,%ld) = %d (%d)\n", jb, frameout, now, ret, thists); + if (thists && thists < lastts) jb_warn("XXXX timestamp roll-back!!!\n"); + lastts = thists; +#endif + if(ret == JB_INTERP) + frameout->ms = jb->info.last_voice_ms; + + return ret; +} + +enum jb_return_code jb_getall(jitterbuf *jb, jb_frame *frameout) +{ + jb_frame *frame; + frame = queue_getall(jb); + + if (!frame) { + return JB_NOFRAME; + } + + *frameout = *frame; + return JB_OK; +} + + +enum jb_return_code jb_getinfo(jitterbuf *jb, jb_info *stats) +{ + + history_get(jb); + + *stats = jb->info; + + return JB_OK; +} + +enum jb_return_code jb_setconf(jitterbuf *jb, jb_conf *conf) +{ + /* take selected settings from the struct */ + + jb->info.conf.max_jitterbuf = conf->max_jitterbuf; + jb->info.conf.resync_threshold = conf->resync_threshold; + jb->info.conf.max_contig_interp = conf->max_contig_interp; + + return JB_OK; +} + + diff --git a/main/jitterbuf.h b/main/jitterbuf.h new file mode 100644 index 000000000..3213534d0 --- /dev/null +++ b/main/jitterbuf.h @@ -0,0 +1,162 @@ +/* + * jitterbuf: an application-independent jitterbuffer + * + * Copyrights: + * Copyright (C) 2004-2005, Horizon Wimba, Inc. + * + * Contributors: + * Steve Kann + * + * This program is free software, distributed under the terms of + * the GNU Lesser (Library) General Public License + * + * Copyright on this file is disclaimed to Digium for inclusion in Asterisk + */ + +#ifndef _JITTERBUF_H_ +#define _JITTERBUF_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* configuration constants */ + /* Number of historical timestamps to use in calculating jitter and drift */ +#define JB_HISTORY_SZ 500 + /* what percentage of timestamps should we drop from the history when we examine it; + * this might eventually be something made configurable */ +#define JB_HISTORY_DROPPCT 3 + /* the maximum droppct we can handle (say it was configurable). */ +#define JB_HISTORY_DROPPCT_MAX 4 + /* the size of the buffer we use to keep the top and botton timestamps for dropping */ +#define JB_HISTORY_MAXBUF_SZ JB_HISTORY_SZ * JB_HISTORY_DROPPCT_MAX / 100 + /* amount of additional jitterbuffer adjustment */ +#define JB_TARGET_EXTRA 40 + /* ms between growing and shrinking; may not be honored if jitterbuffer runs out of space */ +#define JB_ADJUST_DELAY 40 + +enum jb_return_code { + /* return codes */ + JB_OK, /* 0 */ + JB_EMPTY, /* 1 */ + JB_NOFRAME, /* 2 */ + JB_INTERP, /* 3 */ + JB_DROP, /* 4 */ + JB_SCHED /* 5 */ +}; + +enum jb_frame_type { + /* frame types */ + JB_TYPE_CONTROL, /* 0 */ + JB_TYPE_VOICE, /* 1 */ + JB_TYPE_VIDEO, /* 2 - reserved */ + JB_TYPE_SILENCE /* 3 */ +}; + +typedef struct jb_conf { + /* settings */ + long max_jitterbuf; /* defines a hard clamp to use in setting the jitter buffer delay */ + long resync_threshold; /* the jb will resync when delay increases to (2 * jitter) + this param */ + long max_contig_interp; /* the max interp frames to return in a row */ +} jb_conf; + +typedef struct jb_info { + jb_conf conf; + + /* statistics */ + long frames_in; /* number of frames input to the jitterbuffer.*/ + long frames_out; /* number of frames output from the jitterbuffer.*/ + long frames_late; /* number of frames which were too late, and dropped.*/ + long frames_lost; /* number of missing frames.*/ + long frames_dropped; /* number of frames dropped (shrinkage) */ + long frames_ooo; /* number of frames received out-of-order */ + long frames_cur; /* number of frames presently in jb, awaiting delivery.*/ + long jitter; /* jitter measured within current history interval*/ + long min; /* minimum lateness within current history interval */ + long current; /* the present jitterbuffer adjustment */ + long target; /* the target jitterbuffer adjustment */ + long losspct; /* recent lost frame percentage (* 1000) */ + long next_voice_ts; /* the ts of the next frame to be read from the jb - in receiver's time */ + long last_voice_ms; /* the duration of the last voice frame */ + long silence_begin_ts; /* the time of the last CNG frame, when in silence */ + long last_adjustment; /* the time of the last adjustment */ + long last_delay; /* the last now added to history */ + long cnt_delay_discont; /* the count of discontinuous delays */ + long resync_offset; /* the amount to offset ts to support resyncs */ + long cnt_contig_interp; /* the number of contiguous interp frames returned */ +} jb_info; + +typedef struct jb_frame { + void *data; /* the frame data */ + long ts; /* the relative delivery time expected */ + long ms; /* the time covered by this frame, in sec/8000 */ + enum jb_frame_type type; /* the type of frame */ + struct jb_frame *next, *prev; +} jb_frame; + +typedef struct jitterbuf { + jb_info info; + + /* history */ + long history[JB_HISTORY_SZ]; /* history */ + int hist_ptr; /* points to index in history for next entry */ + long hist_maxbuf[JB_HISTORY_MAXBUF_SZ]; /* a sorted buffer of the max delays (highest first) */ + long hist_minbuf[JB_HISTORY_MAXBUF_SZ]; /* a sorted buffer of the min delays (lowest first) */ + int hist_maxbuf_valid; /* are the "maxbuf"/minbuf valid? */ + + + jb_frame *frames; /* queued frames */ + jb_frame *free; /* free frames (avoid malloc?) */ +} jitterbuf; + + +/* new jitterbuf */ +jitterbuf * jb_new(void); + +/* destroy jitterbuf */ +void jb_destroy(jitterbuf *jb); + +/* reset jitterbuf */ +/* NOTE: The jitterbuffer should be empty before you call this, otherwise + * you will leak queued frames, and some internal structures */ +void jb_reset(jitterbuf *jb); + +/* queue a frame data=frame data, timings (in ms): ms=length of frame (for voice), ts=ts (sender's time) + * now=now (in receiver's time) return value is one of + * JB_OK: Frame added. Last call to jb_next() still valid + * JB_DROP: Drop this frame immediately + * JB_SCHED: Frame added. Call jb_next() to get a new time for the next frame + */ +enum jb_return_code jb_put(jitterbuf *jb, void *data, const enum jb_frame_type type, long ms, long ts, long now); + +/* get a frame for time now (receiver's time) return value is one of + * JB_OK: You've got frame! + * JB_DROP: Here's an audio frame you should just drop. Ask me again for this time.. + * JB_NOFRAME: There's no frame scheduled for this time. + * JB_INTERP: Please interpolate an interpl-length frame for this time (either we need to grow, or there was a lost frame) + * JB_EMPTY: The jb is empty. + */ +enum jb_return_code jb_get(jitterbuf *jb, jb_frame *frame, long now, long interpl); + +/* unconditionally get frames from jitterbuf until empty */ +enum jb_return_code jb_getall(jitterbuf *jb, jb_frame *frameout); + +/* when is the next frame due out, in receiver's time (0=EMPTY) + * This value may change as frames are added (esp non-audio frames) */ +long jb_next(jitterbuf *jb); + +/* get jitterbuf info: only "statistics" may be valid */ +enum jb_return_code jb_getinfo(jitterbuf *jb, jb_info *stats); + +/* set jitterbuf conf */ +enum jb_return_code jb_setconf(jitterbuf *jb, jb_conf *conf); + +typedef void (*jb_output_function_t)(const char *fmt, ...); +void jb_setoutput(jb_output_function_t err, jb_output_function_t warn, jb_output_function_t dbg); + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/main/loader.c b/main/loader.c new file mode 100644 index 000000000..709a943d7 --- /dev/null +++ b/main/loader.c @@ -0,0 +1,883 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * Kevin P. Fleming + * Luigi Rizzo + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Module Loader + * \author Mark Spencer + * \author Kevin P. Fleming + * \author Luigi Rizzo + * - See ModMngMnt + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include + +#include "asterisk/linkedlists.h" +#include "asterisk/module.h" +#include "asterisk/options.h" +#include "asterisk/config.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/term.h" +#include "asterisk/manager.h" +#include "asterisk/cdr.h" +#include "asterisk/enum.h" +#include "asterisk/rtp.h" +#include "asterisk/http.h" +#include "asterisk/lock.h" + +#ifdef DLFCNCOMPAT +#include "asterisk/dlfcn-compat.h" +#else +#include +#endif + +#include "asterisk/md5.h" +#include "asterisk/utils.h" + +#ifndef RTLD_NOW +#define RTLD_NOW 0 +#endif + +struct ast_module_user { + struct ast_channel *chan; + AST_LIST_ENTRY(ast_module_user) entry; +}; + +AST_LIST_HEAD(module_user_list, ast_module_user); + +static unsigned char expected_key[] = +{ 0x87, 0x76, 0x79, 0x35, 0x23, 0xea, 0x3a, 0xd3, + 0x25, 0x2a, 0xbb, 0x35, 0x87, 0xe4, 0x22, 0x24 }; + +static unsigned int embedding = 1; /* we always start out by registering embedded modules, + since they are here before we dlopen() any + */ + +enum flags { + FLAG_RUNNING = (1 << 1), /* module successfully initialized */ + FLAG_DECLINED = (1 << 2), /* module declined to initialize */ +}; + +struct ast_module { + const struct ast_module_info *info; + void *lib; /* the shared lib, or NULL if embedded */ + int usecount; /* the number of 'users' currently in this module */ + struct module_user_list users; /* the list of users in the module */ + unsigned int flags; /* flags for this module */ + AST_LIST_ENTRY(ast_module) entry; + char resource[0]; +}; + +static AST_LIST_HEAD_STATIC(module_list, ast_module); + +struct loadupdate { + int (*updater)(void); + AST_LIST_ENTRY(loadupdate) entry; +}; + +static AST_LIST_HEAD_STATIC(updaters, loadupdate); + +AST_MUTEX_DEFINE_STATIC(reloadlock); + +/* when dynamic modules are being loaded, ast_module_register() will + need to know what filename the module was loaded from while it + is being registered +*/ +struct ast_module *resource_being_loaded; + +/* XXX: should we check for duplicate resource names here? */ + +void ast_module_register(const struct ast_module_info *info) +{ + struct ast_module *mod; + + if (embedding) { + if (!(mod = ast_calloc(1, sizeof(*mod) + strlen(info->name) + 1))) + return; + strcpy(mod->resource, info->name); + } else { + mod = resource_being_loaded; + } + + mod->info = info; + AST_LIST_HEAD_INIT(&mod->users); + + /* during startup, before the loader has been initialized, + there are no threads, so there is no need to take the lock + on this list to manipulate it. it is also possible that it + might be unsafe to use the list lock at that point... so + let's avoid it altogether + */ + if (!embedding) + AST_LIST_LOCK(&module_list); + + /* it is paramount that the new entry be placed at the tail of + the list, otherwise the code that uses dlopen() to load + dynamic modules won't be able to find out if the module it + just opened was registered or failed to load + */ + AST_LIST_INSERT_TAIL(&module_list, mod, entry); + + if (!embedding) + AST_LIST_UNLOCK(&module_list); + + /* give the module a copy of its own handle, for later use in registrations and the like */ + *((struct ast_module **) &(info->self)) = mod; +} + +void ast_module_unregister(const struct ast_module_info *info) +{ + struct ast_module *mod = NULL; + + /* it is assumed that the users list in the module structure + will already be empty, or we cannot have gotten to this + point + */ + AST_LIST_LOCK(&module_list); + AST_LIST_TRAVERSE_SAFE_BEGIN(&module_list, mod, entry) { + if (mod->info == info) { + AST_LIST_REMOVE_CURRENT(&module_list, entry); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&module_list); + + if (mod) { + AST_LIST_HEAD_DESTROY(&mod->users); + free(mod); + } +} + +struct ast_module_user *__ast_module_user_add(struct ast_module *mod, + struct ast_channel *chan) +{ + struct ast_module_user *u = ast_calloc(1, sizeof(*u)); + + if (!u) + return NULL; + + u->chan = chan; + + AST_LIST_LOCK(&mod->users); + AST_LIST_INSERT_HEAD(&mod->users, u, entry); + AST_LIST_UNLOCK(&mod->users); + + ast_atomic_fetchadd_int(&mod->usecount, +1); + + ast_update_use_count(); + + return u; +} + +void __ast_module_user_remove(struct ast_module *mod, struct ast_module_user *u) +{ + AST_LIST_LOCK(&mod->users); + AST_LIST_REMOVE(&mod->users, u, entry); + AST_LIST_UNLOCK(&mod->users); + ast_atomic_fetchadd_int(&mod->usecount, -1); + free(u); + + ast_update_use_count(); +} + +void __ast_module_user_hangup_all(struct ast_module *mod) +{ + struct ast_module_user *u; + + AST_LIST_LOCK(&mod->users); + while ((u = AST_LIST_REMOVE_HEAD(&mod->users, entry))) { + ast_softhangup(u->chan, AST_SOFTHANGUP_APPUNLOAD); + ast_atomic_fetchadd_int(&mod->usecount, -1); + free(u); + } + AST_LIST_UNLOCK(&mod->users); + + ast_update_use_count(); +} + +/*! \note + * In addition to modules, the reload command handles some extra keywords + * which are listed here together with the corresponding handlers. + * This table is also used by the command completion code. + */ +static struct reload_classes { + const char *name; + int (*reload_fn)(void); +} reload_classes[] = { /* list in alpha order, longest match first for cli completion */ + { "cdr", ast_cdr_engine_reload }, + { "dnsmgr", dnsmgr_reload }, + { "extconfig", read_config_maps }, + { "enum", ast_enum_reload }, + { "manager", reload_manager }, + { "rtp", ast_rtp_reload }, + { "http", ast_http_reload }, + { NULL, NULL } +}; + +static int printdigest(const unsigned char *d) +{ + int x, pos; + char buf[256]; /* large enough so we don't have to worry */ + + for (pos = 0, x = 0; x < 16; x++) + pos += sprintf(buf + pos, " %02x", *d++); + + ast_log(LOG_DEBUG, "Unexpected signature:%s\n", buf); + + return 0; +} + +static int key_matches(const unsigned char *key1, const unsigned char *key2) +{ + int x; + + for (x = 0; x < 16; x++) { + if (key1[x] != key2[x]) + return 0; + } + + return 1; +} + +static int verify_key(const unsigned char *key) +{ + struct MD5Context c; + unsigned char digest[16]; + + MD5Init(&c); + MD5Update(&c, key, strlen((char *)key)); + MD5Final(digest, &c); + + if (key_matches(expected_key, digest)) + return 0; + + printdigest(digest); + + return -1; +} + +static int resource_name_match(const char *name1_in, const char *name2_in) +{ + char *name1 = (char *) name1_in; + char *name2 = (char *) name2_in; + + /* trim off any .so extensions */ + if (!strcasecmp(name1 + strlen(name1) - 3, ".so")) { + name1 = ast_strdupa(name1); + name1[strlen(name1) - 3] = '\0'; + } + if (!strcasecmp(name2 + strlen(name2) - 3, ".so")) { + name2 = ast_strdupa(name2); + name2[strlen(name2) - 3] = '\0'; + } + + return strcasecmp(name1, name2); +} + +static struct ast_module *find_resource(const char *resource, int do_lock) +{ + struct ast_module *cur; + + if (do_lock) + AST_LIST_LOCK(&module_list); + + AST_LIST_TRAVERSE(&module_list, cur, entry) { + if (!resource_name_match(resource, cur->resource)) + break; + } + + if (do_lock) + AST_LIST_UNLOCK(&module_list); + + return cur; +} + +#if LOADABLE_MODULES +static void unload_dynamic_module(struct ast_module *mod) +{ + if (mod->lib) + dlclose(mod->lib); + /* WARNING: the structure pointed to by mod is now gone! */ +} + +static struct ast_module *load_dynamic_module(const char *resource_in, unsigned int global_symbols_only) +{ + char fn[256]; + void *lib; + struct ast_module *mod; + unsigned int load_global = global_symbols_only; + char *resource = (char *) resource_in; + + if (strcasecmp(resource + strlen(resource) - 3, ".so")) { + resource = alloca(strlen(resource_in) + 3); + strcpy(resource, resource_in); + strcat(resource, ".so"); + } + + snprintf(fn, sizeof(fn), "%s/%s", ast_config_AST_MODULE_DIR, resource); + +tryload: + if (!(resource_being_loaded = ast_calloc(1, sizeof(*resource_being_loaded) + strlen(resource) + 1))) + return NULL; + + strcpy(resource_being_loaded->resource, resource); + + if (load_global) + lib = dlopen(fn, RTLD_LAZY | RTLD_GLOBAL); + else + lib = dlopen(fn, RTLD_NOW | RTLD_LOCAL); + + if (!lib) { + ast_log(LOG_WARNING, "%s\n", dlerror()); + free(resource_being_loaded); + return NULL; + } + + /* the dlopen() succeeded, let's find out if the module + registered itself */ + /* note that this will only work properly as long as + ast_module_register() (which is called by the module's + constructor) places the new module at the tail of the + module_list + */ + if (resource_being_loaded != (mod = AST_LIST_LAST(&module_list))) { + /* no, it did not, so close it and return */ + dlclose(lib); + free(resource_being_loaded); + return NULL; + } + + resource_being_loaded = NULL; + mod->lib = lib; + + /* if we are being asked only to load modules that provide global symbols, + and this one does not, then close it and return */ + if (load_global && !ast_test_flag(mod->info, AST_MODFLAG_GLOBAL_SYMBOLS)) { + unload_dynamic_module(mod); + return NULL; + } + + /* if we were not asked to load _only_ modules with global symbols, but + this module wants to provide some, then we have to close and re-open + in global mode + */ + if (!load_global && ast_test_flag(mod->info, AST_MODFLAG_GLOBAL_SYMBOLS)) { + unload_dynamic_module(mod); + load_global = 1; + goto tryload; + } + + return mod; +} +#endif + +int ast_unload_resource(const char *resource_name, enum ast_module_unload_mode force) +{ + struct ast_module *mod; + int res = -1; + int error = 0; + + AST_LIST_LOCK(&module_list); + + mod = find_resource(resource_name, 0); + + if (!ast_test_flag(mod, FLAG_RUNNING | FLAG_DECLINED)) + error = 1; + + if (!error && (mod->usecount > 0)) { + if (force) + ast_log(LOG_WARNING, "Warning: Forcing removal of module '%s' with use count %d\n", + resource_name, mod->usecount); + else { + ast_log(LOG_WARNING, "Soft unload failed, '%s' has use count %d\n", resource_name, + mod->usecount); + error = 1; + } + } + + if (!error) { + __ast_module_user_hangup_all(mod); + res = mod->info->unload(); + + if (res) { + ast_log(LOG_WARNING, "Firm unload failed for %s\n", resource_name); + if (force <= AST_FORCE_FIRM) + error = 1; + else + ast_log(LOG_WARNING, "** Dangerous **: Unloading resource anyway, at user request\n"); + } + } + + if (!error) + ast_clear_flag(mod, FLAG_RUNNING | FLAG_DECLINED); + + AST_LIST_UNLOCK(&module_list); + +#if LOADABLE_MODULES + if (!error) + unload_dynamic_module(mod); +#endif + + if (!error) + ast_update_use_count(); + + return res; +} + +char *ast_module_helper(const char *line, const char *word, int pos, int state, int rpos, int needsreload) +{ + struct ast_module *cur; + int i, which=0, l = strlen(word); + char *ret = NULL; + + if (pos != rpos) + return NULL; + + AST_LIST_LOCK(&module_list); + AST_LIST_TRAVERSE(&module_list, cur, entry) { + if (!strncasecmp(word, cur->resource, l) && + (cur->info->reload || !needsreload) && + ++which > state) { + ret = strdup(cur->resource); + break; + } + } + AST_LIST_UNLOCK(&module_list); + + if (!ret) { + for (i=0; !ret && reload_classes[i].name; i++) { + if (!strncasecmp(word, reload_classes[i].name, l) && ++which > state) + ret = strdup(reload_classes[i].name); + } + } + + return ret; +} + +int ast_module_reload(const char *name) +{ + struct ast_module *cur; + int res = 0; /* return value. 0 = not found, others, see below */ + int i; + + if (ast_mutex_trylock(&reloadlock)) { + ast_verbose("The previous reload command didn't finish yet\n"); + return -1; /* reload already in progress */ + } + + /* Call "predefined" reload here first */ + for (i = 0; reload_classes[i].name; i++) { + if (!name || !strcasecmp(name, reload_classes[i].name)) { + reload_classes[i].reload_fn(); /* XXX should check error ? */ + res = 2; /* found and reloaded */ + } + } + ast_lastreloadtime = time(NULL); + + if (name && res) + return res; + + AST_LIST_LOCK(&module_list); + AST_LIST_TRAVERSE(&module_list, cur, entry) { + const struct ast_module_info *info = cur->info; + + if (name && resource_name_match(name, cur->resource)) + continue; + + if (!ast_test_flag(cur, FLAG_RUNNING | FLAG_DECLINED)) + continue; + + if (!info->reload) { /* cannot be reloaded */ + if (res < 1) /* store result if possible */ + res = 1; /* 1 = no reload() method */ + continue; + } + + res = 2; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Reloading module '%s' (%s)\n", cur->resource, info->description); + info->reload(); + } + AST_LIST_UNLOCK(&module_list); + + ast_mutex_unlock(&reloadlock); + + return res; +} + +static unsigned int inspect_module(const struct ast_module *mod) +{ + if (!mod->info->description) { + ast_log(LOG_WARNING, "Module '%s' does not provide a description.\n", mod->resource); + return 1; + } + + if (!mod->info->key) { + ast_log(LOG_WARNING, "Module '%s' does not provide a license key.\n", mod->resource); + return 1; + } + + if (verify_key((unsigned char *) mod->info->key)) { + ast_log(LOG_WARNING, "Module '%s' did not provide a valid license key.\n", mod->resource); + return 1; + } + + return 0; +} + +static enum ast_module_load_result load_resource(const char *resource_name, unsigned int global_symbols_only) +{ + struct ast_module *mod; + enum ast_module_load_result res = AST_MODULE_LOAD_SUCCESS; + char tmp[256]; + + if ((mod = find_resource(resource_name, 0))) { + if (ast_test_flag(mod, FLAG_RUNNING)) { + ast_log(LOG_WARNING, "Module '%s' already exists.\n", resource_name); + return AST_MODULE_LOAD_DECLINE; + } + if (global_symbols_only && !ast_test_flag(mod->info, AST_MODFLAG_GLOBAL_SYMBOLS)) + return AST_MODULE_LOAD_SKIP; +#if LOADABLE_MODULES + } else { + if (!(mod = load_dynamic_module(resource_name, global_symbols_only))) { + /* don't generate a warning message during load_modules() */ + if (!global_symbols_only) { + ast_log(LOG_WARNING, "Module '%s' could not be loaded.\n", resource_name); + return AST_MODULE_LOAD_DECLINE; + } else { + return AST_MODULE_LOAD_SKIP; + } + } +#endif + } + + if (inspect_module(mod)) { + ast_log(LOG_WARNING, "Module '%s' could not be loaded.\n", resource_name); +#if LOADABLE_MODULES + unload_dynamic_module(mod); +#endif + return AST_MODULE_LOAD_DECLINE; + } + + ast_clear_flag(mod, FLAG_DECLINED); + + if (mod->info->load) + res = mod->info->load(); + + switch (res) { + case AST_MODULE_LOAD_SUCCESS: + if (!ast_fully_booted) { + if (option_verbose) + ast_verbose( " => (%s)\n", term_color(tmp, mod->info->description, COLOR_BROWN, COLOR_BLACK, sizeof(tmp))); + if (ast_opt_console && !option_verbose) + ast_verbose( "."); + } else { + if (option_verbose) + ast_verbose(VERBOSE_PREFIX_1 "Loaded %s => (%s)\n", resource_name, mod->info->description); + } + + ast_set_flag(mod, FLAG_RUNNING); + + ast_update_use_count(); + break; + case AST_MODULE_LOAD_DECLINE: + ast_set_flag(mod, FLAG_DECLINED); + break; + case AST_MODULE_LOAD_FAILURE: + break; + case AST_MODULE_LOAD_SKIP: + /* modules should never return this value */ + break; + } + + return res; +} + +int ast_load_resource(const char *resource_name) +{ + AST_LIST_LOCK(&module_list); + load_resource(resource_name, 0); + AST_LIST_UNLOCK(&module_list); + + return 0; +} + +struct load_order_entry { + char *resource; + unsigned int embedded; + AST_LIST_ENTRY(load_order_entry) entry; +}; + +AST_LIST_HEAD_NOLOCK(load_order, load_order_entry); + +static struct load_order_entry *add_to_load_order(const char *resource, struct load_order *load_order) +{ + struct load_order_entry *order; + + AST_LIST_TRAVERSE(load_order, order, entry) { + if (!resource_name_match(order->resource, resource)) + return NULL; + } + + if (!(order = ast_calloc(1, sizeof(*order)))) + return NULL; + + order->resource = ast_strdup(resource); + AST_LIST_INSERT_TAIL(load_order, order, entry); + + return order; +} + +int load_modules(void) +{ + struct ast_config *cfg; + struct dirent *dirent; + DIR *dir; + struct ast_module *mod; + struct load_order_entry *order; + struct ast_variable *v; + unsigned int load_count; + struct load_order load_order; + int res = 0; + + /* all embedded modules have registered themselves by now */ + embedding = 0; + + if (option_verbose) + ast_verbose("Asterisk Dynamic Loader Starting:\n"); + + AST_LIST_TRAVERSE(&module_list, mod, entry) { + if (option_debug > 1) + ast_log(LOG_DEBUG, "Embedded module found: %s\n", mod->resource); + } + + if (!(cfg = ast_config_load(AST_MODULE_CONFIG))) { + ast_log(LOG_WARNING, "No '%s' found, no modules will be loaded.\n", AST_MODULE_CONFIG); + return 0; + } + + AST_LIST_HEAD_INIT_NOLOCK(&load_order); + + /* first, find all the modules we have been explicitly requested to load */ + for (v = ast_variable_browse(cfg, "modules"); v; v = v->next) { + if (!strcasecmp(v->name, "load")) + add_to_load_order(v->value, &load_order); + } + + /* check if 'autoload' is on */ + if (ast_true(ast_variable_retrieve(cfg, "modules", "autoload"))) { + /* if so, first add all the embedded modules to the load order */ + AST_LIST_TRAVERSE(&module_list, mod, entry) { + order = add_to_load_order(mod->resource, &load_order); + + if (order) + order->embedded = 1; + } + + /* if we are allowed to load dynamic modules, scan the directory for + for all available modules and add them as well */ +#if LOADABLE_MODULES + if ((dir = opendir(ast_config_AST_MODULE_DIR))) { + while ((dirent = readdir(dir))) { + int ld = strlen(dirent->d_name); + + /* Must end in .so to load it. */ + + if (ld < 4) + continue; + + if (strcasecmp(dirent->d_name + ld - 3, ".so")) + continue; + + add_to_load_order(dirent->d_name, &load_order); + + } + + closedir(dir); + } else { + if (!ast_opt_quiet) + ast_log(LOG_WARNING, "Unable to open modules directory '%s'.\n", + ast_config_AST_MODULE_DIR); + } +#endif + } + + /* now scan the config for any modules we are prohibited from loading and + remove them from the load order */ + for (v = ast_variable_browse(cfg, "modules"); v; v = v->next) { + if (strcasecmp(v->name, "noload")) + continue; + + AST_LIST_TRAVERSE_SAFE_BEGIN(&load_order, order, entry) { + if (!resource_name_match(order->resource, v->value)) { + AST_LIST_REMOVE_CURRENT(&load_order, entry); + free(order->resource); + free(order); + } + } + AST_LIST_TRAVERSE_SAFE_END; + } + + /* we are done with the config now, all the information we need is in the + load_order list */ + ast_config_destroy(cfg); + + load_count = 0; + AST_LIST_TRAVERSE(&load_order, order, entry) + load_count++; + + ast_log(LOG_NOTICE, "%d modules will be loaded.\n", load_count); + + /* first, load only modules that provide global symbols */ + AST_LIST_TRAVERSE_SAFE_BEGIN(&load_order, order, entry) { + switch (load_resource(order->resource, 1)) { + case AST_MODULE_LOAD_SUCCESS: + case AST_MODULE_LOAD_DECLINE: + AST_LIST_REMOVE_CURRENT(&load_order, entry); + free(order->resource); + free(order); + break; + case AST_MODULE_LOAD_FAILURE: + res = -1; + goto done; + case AST_MODULE_LOAD_SKIP: + /* try again later */ + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + + /* now load everything else */ + AST_LIST_TRAVERSE_SAFE_BEGIN(&load_order, order, entry) { + switch (load_resource(order->resource, 0)) { + case AST_MODULE_LOAD_SUCCESS: + case AST_MODULE_LOAD_DECLINE: + AST_LIST_REMOVE_CURRENT(&load_order, entry); + free(order->resource); + free(order); + break; + case AST_MODULE_LOAD_FAILURE: + res = -1; + goto done; + case AST_MODULE_LOAD_SKIP: + /* should not happen */ + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + +done: + while ((order = AST_LIST_REMOVE_HEAD(&load_order, entry))) { + free(order->resource); + free(order); + } + + return res; +} + +void ast_update_use_count(void) +{ + /* Notify any module monitors that the use count for a + resource has changed */ + struct loadupdate *m; + + AST_LIST_LOCK(&module_list); + AST_LIST_TRAVERSE(&updaters, m, entry) + m->updater(); + AST_LIST_UNLOCK(&module_list); +} + +int ast_update_module_list(int (*modentry)(const char *module, const char *description, int usecnt, const char *like), + const char *like) +{ + struct ast_module *cur; + int unlock = -1; + int total_mod_loaded = 0; + + if (AST_LIST_TRYLOCK(&module_list)) + unlock = 0; + + AST_LIST_TRAVERSE(&module_list, cur, entry) { + total_mod_loaded += modentry(cur->resource, cur->info->description, cur->usecount, like); + } + + if (unlock) + AST_LIST_UNLOCK(&module_list); + + return total_mod_loaded; +} + +int ast_loader_register(int (*v)(void)) +{ + struct loadupdate *tmp; + + if (!(tmp = ast_malloc(sizeof(*tmp)))) + return -1; + + tmp->updater = v; + AST_LIST_LOCK(&module_list); + AST_LIST_INSERT_HEAD(&updaters, tmp, entry); + AST_LIST_UNLOCK(&module_list); + + return 0; +} + +int ast_loader_unregister(int (*v)(void)) +{ + struct loadupdate *cur; + + AST_LIST_LOCK(&module_list); + AST_LIST_TRAVERSE_SAFE_BEGIN(&updaters, cur, entry) { + if (cur->updater == v) { + AST_LIST_REMOVE_CURRENT(&updaters, entry); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&module_list); + + return cur ? 0 : -1; +} + +struct ast_module *ast_module_ref(struct ast_module *mod) +{ + ast_atomic_fetchadd_int(&mod->usecount, +1); + ast_update_use_count(); + + return mod; +} + +void ast_module_unref(struct ast_module *mod) +{ + ast_atomic_fetchadd_int(&mod->usecount, -1); + ast_update_use_count(); +} diff --git a/main/logger.c b/main/logger.c new file mode 100644 index 000000000..532023b21 --- /dev/null +++ b/main/logger.c @@ -0,0 +1,914 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Asterisk Logger + * + * Logging routines + * + * \author Mark Spencer + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef STACK_BACKTRACES +#include +#define MAX_BACKTRACE_FRAMES 20 +#endif + +#define SYSLOG_NAMES /* so we can map syslog facilities names to their numeric values, + from which is included by logger.h */ +#include + +static int syslog_level_map[] = { + LOG_DEBUG, + LOG_INFO, /* arbitrary equivalent of LOG_EVENT */ + LOG_NOTICE, + LOG_WARNING, + LOG_ERR, + LOG_DEBUG, + LOG_DEBUG +}; + +#define SYSLOG_NLEVELS sizeof(syslog_level_map) / sizeof(int) + +#include "asterisk/logger.h" +#include "asterisk/lock.h" +#include "asterisk/options.h" +#include "asterisk/channel.h" +#include "asterisk/config.h" +#include "asterisk/term.h" +#include "asterisk/cli.h" +#include "asterisk/utils.h" +#include "asterisk/manager.h" +#include "asterisk/threadstorage.h" + +#if defined(__linux__) && !defined(__NR_gettid) +#include +#endif + +#if defined(__linux__) && defined(__NR_gettid) +#define GETTID() syscall(__NR_gettid) +#else +#define GETTID() getpid() +#endif + + +static char dateformat[256] = "%b %e %T"; /* Original Asterisk Format */ + +static int filesize_reload_needed = 0; +static int global_logmask = -1; + +static struct { + unsigned int queue_log:1; + unsigned int event_log:1; +} logfiles = { 1, 1 }; + +static char hostname[MAXHOSTNAMELEN]; + +enum logtypes { + LOGTYPE_SYSLOG, + LOGTYPE_FILE, + LOGTYPE_CONSOLE, +}; + +struct logchannel { + int logmask; /* What to log to this channel */ + int disabled; /* If this channel is disabled or not */ + int facility; /* syslog facility */ + enum logtypes type; /* Type of log channel */ + FILE *fileptr; /* logfile logging file pointer */ + char filename[256]; /* Filename */ + AST_LIST_ENTRY(logchannel) list; +}; + +static AST_LIST_HEAD_STATIC(logchannels, logchannel); + +static FILE *eventlog = NULL; +static FILE *qlog = NULL; + +static char *levels[] = { + "DEBUG", + "EVENT", + "NOTICE", + "WARNING", + "ERROR", + "VERBOSE", + "DTMF" +}; + +static int colors[] = { + COLOR_BRGREEN, + COLOR_BRBLUE, + COLOR_YELLOW, + COLOR_BRRED, + COLOR_RED, + COLOR_GREEN, + COLOR_BRGREEN +}; + +AST_THREADSTORAGE(verbose_buf, verbose_buf_init); +#define VERBOSE_BUF_INIT_SIZE 128 + +AST_THREADSTORAGE(log_buf, log_buf_init); +#define LOG_BUF_INIT_SIZE 128 + +static int make_components(char *s, int lineno) +{ + char *w; + int res = 0; + char *stringp = s; + + while ((w = strsep(&stringp, ","))) { + w = ast_skip_blanks(w); + if (!strcasecmp(w, "error")) + res |= (1 << __LOG_ERROR); + else if (!strcasecmp(w, "warning")) + res |= (1 << __LOG_WARNING); + else if (!strcasecmp(w, "notice")) + res |= (1 << __LOG_NOTICE); + else if (!strcasecmp(w, "event")) + res |= (1 << __LOG_EVENT); + else if (!strcasecmp(w, "debug")) + res |= (1 << __LOG_DEBUG); + else if (!strcasecmp(w, "verbose")) + res |= (1 << __LOG_VERBOSE); + else if (!strcasecmp(w, "dtmf")) + res |= (1 << __LOG_DTMF); + else { + fprintf(stderr, "Logfile Warning: Unknown keyword '%s' at line %d of logger.conf\n", w, lineno); + } + } + + return res; +} + +static struct logchannel *make_logchannel(char *channel, char *components, int lineno) +{ + struct logchannel *chan; + char *facility; +#ifndef SOLARIS + CODE *cptr; +#endif + + if (ast_strlen_zero(channel) || !(chan = ast_calloc(1, sizeof(*chan)))) + return NULL; + + if (!strcasecmp(channel, "console")) { + chan->type = LOGTYPE_CONSOLE; + } else if (!strncasecmp(channel, "syslog", 6)) { + /* + * syntax is: + * syslog.facility => level,level,level + */ + facility = strchr(channel, '.'); + if(!facility++ || !facility) { + facility = "local0"; + } + +#ifndef SOLARIS + /* + * Walk through the list of facilitynames (defined in sys/syslog.h) + * to see if we can find the one we have been given + */ + chan->facility = -1; + cptr = facilitynames; + while (cptr->c_name) { + if (!strcasecmp(facility, cptr->c_name)) { + chan->facility = cptr->c_val; + break; + } + cptr++; + } +#else + chan->facility = -1; + if (!strcasecmp(facility, "kern")) + chan->facility = LOG_KERN; + else if (!strcasecmp(facility, "USER")) + chan->facility = LOG_USER; + else if (!strcasecmp(facility, "MAIL")) + chan->facility = LOG_MAIL; + else if (!strcasecmp(facility, "DAEMON")) + chan->facility = LOG_DAEMON; + else if (!strcasecmp(facility, "AUTH")) + chan->facility = LOG_AUTH; + else if (!strcasecmp(facility, "SYSLOG")) + chan->facility = LOG_SYSLOG; + else if (!strcasecmp(facility, "LPR")) + chan->facility = LOG_LPR; + else if (!strcasecmp(facility, "NEWS")) + chan->facility = LOG_NEWS; + else if (!strcasecmp(facility, "UUCP")) + chan->facility = LOG_UUCP; + else if (!strcasecmp(facility, "CRON")) + chan->facility = LOG_CRON; + else if (!strcasecmp(facility, "LOCAL0")) + chan->facility = LOG_LOCAL0; + else if (!strcasecmp(facility, "LOCAL1")) + chan->facility = LOG_LOCAL1; + else if (!strcasecmp(facility, "LOCAL2")) + chan->facility = LOG_LOCAL2; + else if (!strcasecmp(facility, "LOCAL3")) + chan->facility = LOG_LOCAL3; + else if (!strcasecmp(facility, "LOCAL4")) + chan->facility = LOG_LOCAL4; + else if (!strcasecmp(facility, "LOCAL5")) + chan->facility = LOG_LOCAL5; + else if (!strcasecmp(facility, "LOCAL6")) + chan->facility = LOG_LOCAL6; + else if (!strcasecmp(facility, "LOCAL7")) + chan->facility = LOG_LOCAL7; +#endif /* Solaris */ + + if (0 > chan->facility) { + fprintf(stderr, "Logger Warning: bad syslog facility in logger.conf\n"); + free(chan); + return NULL; + } + + chan->type = LOGTYPE_SYSLOG; + snprintf(chan->filename, sizeof(chan->filename), "%s", channel); + openlog("asterisk", LOG_PID, chan->facility); + } else { + if (channel[0] == '/') { + if(!ast_strlen_zero(hostname)) { + snprintf(chan->filename, sizeof(chan->filename) - 1,"%s.%s", channel, hostname); + } else { + ast_copy_string(chan->filename, channel, sizeof(chan->filename)); + } + } + + if(!ast_strlen_zero(hostname)) { + snprintf(chan->filename, sizeof(chan->filename), "%s/%s.%s",(char *)ast_config_AST_LOG_DIR, channel, hostname); + } else { + snprintf(chan->filename, sizeof(chan->filename), "%s/%s", (char *)ast_config_AST_LOG_DIR, channel); + } + chan->fileptr = fopen(chan->filename, "a"); + if (!chan->fileptr) { + /* Can't log here, since we're called with a lock */ + fprintf(stderr, "Logger Warning: Unable to open log file '%s': %s\n", chan->filename, strerror(errno)); + } + chan->type = LOGTYPE_FILE; + } + chan->logmask = make_components(components, lineno); + return chan; +} + +static void init_logger_chain(void) +{ + struct logchannel *chan; + struct ast_config *cfg; + struct ast_variable *var; + char *s; + + /* delete our list of log channels */ + AST_LIST_LOCK(&logchannels); + while ((chan = AST_LIST_REMOVE_HEAD(&logchannels, list))) + free(chan); + AST_LIST_UNLOCK(&logchannels); + + global_logmask = 0; + /* close syslog */ + closelog(); + + cfg = ast_config_load("logger.conf"); + + /* If no config file, we're fine, set default options. */ + if (!cfg) { + fprintf(stderr, "Unable to open logger.conf: %s\n", strerror(errno)); + if (!(chan = ast_calloc(1, sizeof(*chan)))) + return; + chan->type = LOGTYPE_CONSOLE; + chan->logmask = 28; /*warning,notice,error */ + AST_LIST_LOCK(&logchannels); + AST_LIST_INSERT_HEAD(&logchannels, chan, list); + AST_LIST_UNLOCK(&logchannels); + global_logmask |= chan->logmask; + return; + } + + if ((s = ast_variable_retrieve(cfg, "general", "appendhostname"))) { + if (ast_true(s)) { + if (gethostname(hostname, sizeof(hostname) - 1)) { + ast_copy_string(hostname, "unknown", sizeof(hostname)); + ast_log(LOG_WARNING, "What box has no hostname???\n"); + } + } else + hostname[0] = '\0'; + } else + hostname[0] = '\0'; + if ((s = ast_variable_retrieve(cfg, "general", "dateformat"))) + ast_copy_string(dateformat, s, sizeof(dateformat)); + else + ast_copy_string(dateformat, "%b %e %T", sizeof(dateformat)); + if ((s = ast_variable_retrieve(cfg, "general", "queue_log"))) + logfiles.queue_log = ast_true(s); + if ((s = ast_variable_retrieve(cfg, "general", "event_log"))) + logfiles.event_log = ast_true(s); + + AST_LIST_LOCK(&logchannels); + var = ast_variable_browse(cfg, "logfiles"); + for (; var; var = var->next) { + if (!(chan = make_logchannel(var->name, var->value, var->lineno))) + continue; + AST_LIST_INSERT_HEAD(&logchannels, chan, list); + global_logmask |= chan->logmask; + } + AST_LIST_UNLOCK(&logchannels); + + ast_config_destroy(cfg); +} + +void ast_queue_log(const char *queuename, const char *callid, const char *agent, const char *event, const char *fmt, ...) +{ + va_list ap; + AST_LIST_LOCK(&logchannels); + if (qlog) { + va_start(ap, fmt); + fprintf(qlog, "%ld|%s|%s|%s|%s|", (long)time(NULL), callid, queuename, agent, event); + vfprintf(qlog, fmt, ap); + fprintf(qlog, "\n"); + va_end(ap); + fflush(qlog); + } + AST_LIST_UNLOCK(&logchannels); +} + +int reload_logger(int rotate) +{ + char old[PATH_MAX] = ""; + char new[PATH_MAX]; + int event_rotate = rotate, queue_rotate = rotate; + struct logchannel *f; + FILE *myf; + int x, res = 0; + + AST_LIST_LOCK(&logchannels); + + if (eventlog) + fclose(eventlog); + else + event_rotate = 0; + eventlog = NULL; + + if (qlog) + fclose(qlog); + else + queue_rotate = 0; + qlog = NULL; + + mkdir((char *)ast_config_AST_LOG_DIR, 0755); + + AST_LIST_TRAVERSE(&logchannels, f, list) { + if (f->disabled) { + f->disabled = 0; /* Re-enable logging at reload */ + manager_event(EVENT_FLAG_SYSTEM, "LogChannel", "Channel: %s\r\nEnabled: Yes\r\n", f->filename); + } + if (f->fileptr && (f->fileptr != stdout) && (f->fileptr != stderr)) { + fclose(f->fileptr); /* Close file */ + f->fileptr = NULL; + if (rotate) { + ast_copy_string(old, f->filename, sizeof(old)); + + for (x = 0; ; x++) { + snprintf(new, sizeof(new), "%s.%d", f->filename, x); + myf = fopen((char *)new, "r"); + if (myf) + fclose(myf); + else + break; + } + + /* do it */ + if (rename(old,new)) + fprintf(stderr, "Unable to rename file '%s' to '%s'\n", old, new); + } + } + } + + filesize_reload_needed = 0; + + init_logger_chain(); + + if (logfiles.event_log) { + snprintf(old, sizeof(old), "%s/%s", (char *)ast_config_AST_LOG_DIR, EVENTLOG); + if (event_rotate) { + for (x=0;;x++) { + snprintf(new, sizeof(new), "%s/%s.%d", (char *)ast_config_AST_LOG_DIR, EVENTLOG,x); + myf = fopen((char *)new, "r"); + if (myf) /* File exists */ + fclose(myf); + else + break; + } + + /* do it */ + if (rename(old,new)) + ast_log(LOG_ERROR, "Unable to rename file '%s' to '%s'\n", old, new); + } + + eventlog = fopen(old, "a"); + if (eventlog) { + ast_log(LOG_EVENT, "Restarted Asterisk Event Logger\n"); + if (option_verbose) + ast_verbose("Asterisk Event Logger restarted\n"); + } else { + ast_log(LOG_ERROR, "Unable to create event log: %s\n", strerror(errno)); + res = -1; + } + } + + if (logfiles.queue_log) { + snprintf(old, sizeof(old), "%s/%s", (char *)ast_config_AST_LOG_DIR, QUEUELOG); + if (queue_rotate) { + for (x = 0; ; x++) { + snprintf(new, sizeof(new), "%s/%s.%d", (char *)ast_config_AST_LOG_DIR, QUEUELOG, x); + myf = fopen((char *)new, "r"); + if (myf) /* File exists */ + fclose(myf); + else + break; + } + + /* do it */ + if (rename(old, new)) + ast_log(LOG_ERROR, "Unable to rename file '%s' to '%s'\n", old, new); + } + + qlog = fopen(old, "a"); + if (qlog) { + ast_queue_log("NONE", "NONE", "NONE", "CONFIGRELOAD", "%s", ""); + ast_log(LOG_EVENT, "Restarted Asterisk Queue Logger\n"); + if (option_verbose) + ast_verbose("Asterisk Queue Logger restarted\n"); + } else { + ast_log(LOG_ERROR, "Unable to create queue log: %s\n", strerror(errno)); + res = -1; + } + } + + AST_LIST_UNLOCK(&logchannels); + + return res; +} + +static int handle_logger_reload(int fd, int argc, char *argv[]) +{ + if(reload_logger(0)) { + ast_cli(fd, "Failed to reload the logger\n"); + return RESULT_FAILURE; + } else + return RESULT_SUCCESS; +} + +static int handle_logger_rotate(int fd, int argc, char *argv[]) +{ + if(reload_logger(1)) { + ast_cli(fd, "Failed to reload the logger and rotate log files\n"); + return RESULT_FAILURE; + } else + return RESULT_SUCCESS; +} + +/*! \brief CLI command to show logging system configuration */ +static int handle_logger_show_channels(int fd, int argc, char *argv[]) +{ +#define FORMATL "%-35.35s %-8.8s %-9.9s " + struct logchannel *chan; + + ast_cli(fd,FORMATL, "Channel", "Type", "Status"); + ast_cli(fd, "Configuration\n"); + ast_cli(fd,FORMATL, "-------", "----", "------"); + ast_cli(fd, "-------------\n"); + AST_LIST_LOCK(&logchannels); + AST_LIST_TRAVERSE(&logchannels, chan, list) { + ast_cli(fd, FORMATL, chan->filename, chan->type==LOGTYPE_CONSOLE ? "Console" : (chan->type==LOGTYPE_SYSLOG ? "Syslog" : "File"), + chan->disabled ? "Disabled" : "Enabled"); + ast_cli(fd, " - "); + if (chan->logmask & (1 << __LOG_DEBUG)) + ast_cli(fd, "Debug "); + if (chan->logmask & (1 << __LOG_DTMF)) + ast_cli(fd, "DTMF "); + if (chan->logmask & (1 << __LOG_VERBOSE)) + ast_cli(fd, "Verbose "); + if (chan->logmask & (1 << __LOG_WARNING)) + ast_cli(fd, "Warning "); + if (chan->logmask & (1 << __LOG_NOTICE)) + ast_cli(fd, "Notice "); + if (chan->logmask & (1 << __LOG_ERROR)) + ast_cli(fd, "Error "); + if (chan->logmask & (1 << __LOG_EVENT)) + ast_cli(fd, "Event "); + ast_cli(fd, "\n"); + } + AST_LIST_UNLOCK(&logchannels); + ast_cli(fd, "\n"); + + return RESULT_SUCCESS; +} + +struct verb { + void (*verboser)(const char *string); + AST_LIST_ENTRY(verb) list; +}; + +static AST_LIST_HEAD_STATIC(verbosers, verb); + +static char logger_reload_help[] = +"Usage: logger reload\n" +" Reloads the logger subsystem state. Use after restarting syslogd(8) if you are using syslog logging.\n"; + +static char logger_rotate_help[] = +"Usage: logger rotate\n" +" Rotates and Reopens the log files.\n"; + +static char logger_show_channels_help[] = +"Usage: logger show channels\n" +" Show configured logger channels.\n"; + +static struct ast_cli_entry logger_show_channels_cli = + { { "logger", "show", "channels", NULL }, + handle_logger_show_channels, "List configured log channels", + logger_show_channels_help }; + +static struct ast_cli_entry reload_logger_cli = + { { "logger", "reload", NULL }, + handle_logger_reload, "Reopens the log files", + logger_reload_help }; + +static struct ast_cli_entry rotate_logger_cli = + { { "logger", "rotate", NULL }, + handle_logger_rotate, "Rotates and reopens the log files", + logger_rotate_help }; + +static int handle_SIGXFSZ(int sig) +{ + /* Indicate need to reload */ + filesize_reload_needed = 1; + return 0; +} + +int init_logger(void) +{ + char tmp[256]; + int res = 0; + + /* auto rotate if sig SIGXFSZ comes a-knockin */ + (void) signal(SIGXFSZ,(void *) handle_SIGXFSZ); + + /* register the relaod logger cli command */ + ast_cli_register(&reload_logger_cli); + ast_cli_register(&rotate_logger_cli); + ast_cli_register(&logger_show_channels_cli); + + mkdir((char *)ast_config_AST_LOG_DIR, 0755); + + /* create log channels */ + init_logger_chain(); + + /* create the eventlog */ + if (logfiles.event_log) { + mkdir((char *)ast_config_AST_LOG_DIR, 0755); + snprintf(tmp, sizeof(tmp), "%s/%s", (char *)ast_config_AST_LOG_DIR, EVENTLOG); + eventlog = fopen((char *)tmp, "a"); + if (eventlog) { + ast_log(LOG_EVENT, "Started Asterisk Event Logger\n"); + if (option_verbose) + ast_verbose("Asterisk Event Logger Started %s\n",(char *)tmp); + } else { + ast_log(LOG_ERROR, "Unable to create event log: %s\n", strerror(errno)); + res = -1; + } + } + + if (logfiles.queue_log) { + snprintf(tmp, sizeof(tmp), "%s/%s", (char *)ast_config_AST_LOG_DIR, QUEUELOG); + qlog = fopen(tmp, "a"); + ast_queue_log("NONE", "NONE", "NONE", "QUEUESTART", "%s", ""); + } + return res; +} + +void close_logger(void) +{ + struct logchannel *f; + + AST_LIST_LOCK(&logchannels); + + if (eventlog) { + fclose(eventlog); + eventlog = NULL; + } + + if (qlog) { + fclose(qlog); + qlog = NULL; + } + + AST_LIST_TRAVERSE(&logchannels, f, list) { + if (f->fileptr && (f->fileptr != stdout) && (f->fileptr != stderr)) { + fclose(f->fileptr); + f->fileptr = NULL; + } + } + + closelog(); /* syslog */ + + AST_LIST_UNLOCK(&logchannels); + + return; +} + +static void ast_log_vsyslog(int level, const char *file, int line, const char *function, const char *fmt, va_list args) +{ + char buf[BUFSIZ]; + char *s; + + if (level >= SYSLOG_NLEVELS) { + /* we are locked here, so cannot ast_log() */ + fprintf(stderr, "ast_log_vsyslog called with bogus level: %d\n", level); + return; + } + if (level == __LOG_VERBOSE) { + snprintf(buf, sizeof(buf), "VERBOSE[%ld]: ", (long)GETTID()); + level = __LOG_DEBUG; + } else if (level == __LOG_DTMF) { + snprintf(buf, sizeof(buf), "DTMF[%ld]: ", (long)GETTID()); + level = __LOG_DEBUG; + } else { + snprintf(buf, sizeof(buf), "%s[%ld]: %s:%d in %s: ", + levels[level], (long)GETTID(), file, line, function); + } + s = buf + strlen(buf); + vsnprintf(s, sizeof(buf) - strlen(buf), fmt, args); + term_strip(s, s, strlen(s) + 1); + syslog(syslog_level_map[level], "%s", buf); +} + +/*! + * \brief send log messages to syslog and/or the console + */ +void ast_log(int level, const char *file, int line, const char *function, const char *fmt, ...) +{ + struct logchannel *chan; + struct ast_dynamic_str *buf; + time_t t; + struct tm tm; + char date[256]; + + va_list ap; + + if (!(buf = ast_dynamic_str_thread_get(&log_buf, LOG_BUF_INIT_SIZE))) + return; + + /* don't display LOG_DEBUG messages unless option_verbose _or_ option_debug + are non-zero; LOG_DEBUG messages can still be displayed if option_debug + is zero, if option_verbose is non-zero (this allows for 'level zero' + LOG_DEBUG messages to be displayed, if the logmask on any channel + allows it) + */ + if (!option_verbose && !option_debug && (level == __LOG_DEBUG)) + return; + + /* Ignore anything that never gets logged anywhere */ + if (!(global_logmask & (1 << level))) + return; + + /* Ignore anything other than the currently debugged file if there is one */ + if ((level == __LOG_DEBUG) && !ast_strlen_zero(debug_filename) && strcasecmp(debug_filename, file)) + return; + + time(&t); + localtime_r(&t, &tm); + strftime(date, sizeof(date), dateformat, &tm); + + AST_LIST_LOCK(&logchannels); + + if (logfiles.event_log && level == __LOG_EVENT) { + va_start(ap, fmt); + + fprintf(eventlog, "%s asterisk[%ld]: ", date, (long)getpid()); + vfprintf(eventlog, fmt, ap); + fflush(eventlog); + + va_end(ap); + AST_LIST_UNLOCK(&logchannels); + return; + } + + if (!AST_LIST_EMPTY(&logchannels)) { + AST_LIST_TRAVERSE(&logchannels, chan, list) { + if (chan->disabled) + break; + /* Check syslog channels */ + if (chan->type == LOGTYPE_SYSLOG && (chan->logmask & (1 << level))) { + va_start(ap, fmt); + ast_log_vsyslog(level, file, line, function, fmt, ap); + va_end(ap); + /* Console channels */ + } else if ((chan->logmask & (1 << level)) && (chan->type == LOGTYPE_CONSOLE)) { + char linestr[128]; + char tmp1[80], tmp2[80], tmp3[80], tmp4[80]; + + if (level != __LOG_VERBOSE) { + int res; + sprintf(linestr, "%d", line); + ast_dynamic_str_thread_set(&buf, BUFSIZ, &log_buf, + "[%s] %s[%ld]: %s:%s %s: ", + date, + term_color(tmp1, levels[level], colors[level], 0, sizeof(tmp1)), + (long)GETTID(), + term_color(tmp2, file, COLOR_BRWHITE, 0, sizeof(tmp2)), + term_color(tmp3, linestr, COLOR_BRWHITE, 0, sizeof(tmp3)), + term_color(tmp4, function, COLOR_BRWHITE, 0, sizeof(tmp4))); + + ast_console_puts_mutable(buf->str); + + va_start(ap, fmt); + res = ast_dynamic_str_thread_set_va(&buf, BUFSIZ, &log_buf, fmt, ap); + va_end(ap); + if (res != AST_DYNSTR_BUILD_FAILED) + ast_console_puts_mutable(buf->str); + } + /* File channels */ + } else if ((chan->logmask & (1 << level)) && (chan->fileptr)) { + int res; + ast_dynamic_str_thread_set(&buf, BUFSIZ, &log_buf, + "[%s] %s[%ld] %s: ", + date, levels[level], (long)GETTID(), file); + res = fprintf(chan->fileptr, "%s", buf->str); + if (res <= 0 && !ast_strlen_zero(buf->str)) { /* Error, no characters printed */ + fprintf(stderr,"**** Asterisk Logging Error: ***********\n"); + if (errno == ENOMEM || errno == ENOSPC) { + fprintf(stderr, "Asterisk logging error: Out of disk space, can't log to log file %s\n", chan->filename); + } else + fprintf(stderr, "Logger Warning: Unable to write to log file '%s': %s (disabled)\n", chan->filename, strerror(errno)); + manager_event(EVENT_FLAG_SYSTEM, "LogChannel", "Channel: %s\r\nEnabled: No\r\nReason: %d - %s\r\n", chan->filename, errno, strerror(errno)); + chan->disabled = 1; + } else { + int res; + /* No error message, continue printing */ + va_start(ap, fmt); + res = ast_dynamic_str_thread_set_va(&buf, BUFSIZ, &log_buf, fmt, ap); + va_end(ap); + if (res != AST_DYNSTR_BUILD_FAILED) { + term_strip(buf->str, buf->str, buf->len); + fputs(buf->str, chan->fileptr); + fflush(chan->fileptr); + } + } + } + } + } else { + /* + * we don't have the logger chain configured yet, + * so just log to stdout + */ + if (level != __LOG_VERBOSE) { + int res; + va_start(ap, fmt); + res = ast_dynamic_str_thread_set_va(&buf, BUFSIZ, &log_buf, fmt, ap); + va_end(ap); + if (res != AST_DYNSTR_BUILD_FAILED) + fputs(buf->str, stdout); + } + } + + AST_LIST_UNLOCK(&logchannels); + + if (filesize_reload_needed) { + reload_logger(1); + ast_log(LOG_EVENT,"Rotated Logs Per SIGXFSZ (Exceeded file size limit)\n"); + if (option_verbose) + ast_verbose("Rotated Logs Per SIGXFSZ (Exceeded file size limit)\n"); + } +} + +void ast_backtrace(void) +{ +#ifdef STACK_BACKTRACES + int count=0, i=0; + void **addresses; + char **strings; + + if ((addresses = ast_calloc(MAX_BACKTRACE_FRAMES, sizeof(*addresses)))) { + count = backtrace(addresses, MAX_BACKTRACE_FRAMES); + if ((strings = backtrace_symbols(addresses, count))) { + ast_log(LOG_DEBUG, "Got %d backtrace record%c\n", count, count != 1 ? 's' : ' '); + for (i=0; i < count ; i++) { + ast_log(LOG_DEBUG, "#%d: [%08X] %s\n", i, (unsigned int)addresses[i], strings[i]); + } + free(strings); + } else { + ast_log(LOG_DEBUG, "Could not allocate memory for backtrace\n"); + } + free(addresses); + } +#else +#ifdef Linux + ast_log(LOG_WARNING, "Must compile with 'make dont-optimize' for stack backtraces\n"); +#else + ast_log(LOG_WARNING, "Inline stack backtraces are only available on the Linux platform.\n"); +#endif +#endif +} + +void ast_verbose(const char *fmt, ...) +{ + struct verb *v; + struct ast_dynamic_str *buf; + int res; + va_list ap; + + if (ast_opt_timestamp) { + time_t t; + struct tm tm; + char date[40]; + char *datefmt; + + time(&t); + localtime_r(&t, &tm); + strftime(date, sizeof(date), dateformat, &tm); + datefmt = alloca(strlen(date) + 3 + strlen(fmt) + 1); + sprintf(datefmt, "[%s] %s", date, fmt); + fmt = datefmt; + } + + if (!(buf = ast_dynamic_str_thread_get(&verbose_buf, VERBOSE_BUF_INIT_SIZE))) + return; + + va_start(ap, fmt); + res = ast_dynamic_str_thread_set_va(&buf, 0, &verbose_buf, fmt, ap); + va_end(ap); + + if (res == AST_DYNSTR_BUILD_FAILED) + return; + + AST_LIST_LOCK(&verbosers); + AST_LIST_TRAVERSE(&verbosers, v, list) + v->verboser(buf->str); + AST_LIST_UNLOCK(&verbosers); + + ast_log(LOG_VERBOSE, "%s", buf->str); +} + +int ast_register_verbose(void (*v)(const char *string)) +{ + struct verb *verb; + + if (!(verb = ast_malloc(sizeof(*verb)))) + return -1; + + verb->verboser = v; + + AST_LIST_LOCK(&verbosers); + AST_LIST_INSERT_HEAD(&verbosers, verb, list); + AST_LIST_UNLOCK(&verbosers); + + return 0; +} + +int ast_unregister_verbose(void (*v)(const char *string)) +{ + struct verb *cur; + + AST_LIST_LOCK(&verbosers); + AST_LIST_TRAVERSE_SAFE_BEGIN(&verbosers, cur, list) { + if (cur->verboser == v) { + AST_LIST_REMOVE_CURRENT(&verbosers, list); + free(cur); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + AST_LIST_UNLOCK(&verbosers); + + return cur ? 0 : -1; +} diff --git a/main/manager.c b/main/manager.c new file mode 100644 index 000000000..03bfdbd27 --- /dev/null +++ b/main/manager.c @@ -0,0 +1,2630 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief The Asterisk Management Interface - AMI + * + * \author Mark Spencer + * + * Channel Management and more + * + * \ref amiconf + */ + +/*! \addtogroup Group_AMI AMI functions +*/ +/*! @{ + Doxygen group */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/channel.h" +#include "asterisk/file.h" +#include "asterisk/manager.h" +#include "asterisk/config.h" +#include "asterisk/callerid.h" +#include "asterisk/lock.h" +#include "asterisk/logger.h" +#include "asterisk/options.h" +#include "asterisk/cli.h" +#include "asterisk/app.h" +#include "asterisk/pbx.h" +#include "asterisk/md5.h" +#include "asterisk/acl.h" +#include "asterisk/utils.h" +#include "asterisk/http.h" +#include "asterisk/threadstorage.h" + +struct fast_originate_helper { + char tech[AST_MAX_MANHEADER_LEN]; + char data[AST_MAX_MANHEADER_LEN]; + int timeout; + char app[AST_MAX_APP]; + char appdata[AST_MAX_MANHEADER_LEN]; + char cid_name[AST_MAX_MANHEADER_LEN]; + char cid_num[AST_MAX_MANHEADER_LEN]; + char context[AST_MAX_CONTEXT]; + char exten[AST_MAX_EXTENSION]; + char idtext[AST_MAX_MANHEADER_LEN]; + char account[AST_MAX_ACCOUNT_CODE]; + int priority; + struct ast_variable *vars; +}; + +struct eventqent { + int usecount; + int category; + ast_mutex_t lock; + struct eventqent *next; + char eventdata[1]; +}; + +static int enabled = 0; +static int portno = DEFAULT_MANAGER_PORT; +static int asock = -1; +static int displayconnects = 1; +static int timestampevents = 0; +static int httptimeout = 60; + +static pthread_t t; +AST_MUTEX_DEFINE_STATIC(sessionlock); +static int block_sockets = 0; +static int num_sessions = 0; +struct eventqent *master_eventq = NULL; + +AST_THREADSTORAGE(manager_event_buf, manager_event_buf_init); +#define MANAGER_EVENT_BUF_INITSIZE 256 + +static struct permalias { + int num; + char *label; +} perms[] = { + { EVENT_FLAG_SYSTEM, "system" }, + { EVENT_FLAG_CALL, "call" }, + { EVENT_FLAG_LOG, "log" }, + { EVENT_FLAG_VERBOSE, "verbose" }, + { EVENT_FLAG_COMMAND, "command" }, + { EVENT_FLAG_AGENT, "agent" }, + { EVENT_FLAG_USER, "user" }, + { EVENT_FLAG_CONFIG, "config" }, + { -1, "all" }, + { 0, "none" }, +}; + +static struct mansession { + /*! Execution thread */ + pthread_t t; + /*! Thread lock -- don't use in action callbacks, it's already taken care of */ + ast_mutex_t __lock; + /*! socket address */ + struct sockaddr_in sin; + /*! TCP socket */ + int fd; + /*! Whether or not we're busy doing an action */ + int busy; + /*! Whether or not we're "dead" */ + int dead; + /*! Whether an HTTP manager is in use */ + int inuse; + /*! Whether an HTTP session should be destroyed */ + int needdestroy; + /*! Whether an HTTP session has someone waiting on events */ + pthread_t waiting_thread; + /*! Unique manager identifer */ + unsigned long managerid; + /*! Session timeout if HTTP */ + time_t sessiontimeout; + /*! Output from manager interface */ + char *outputstr; + /*! Logged in username */ + char username[80]; + /*! Authentication challenge */ + char challenge[10]; + /*! Authentication status */ + int authenticated; + /*! Authorization for reading */ + int readperm; + /*! Authorization for writing */ + int writeperm; + /*! Buffer */ + char inbuf[AST_MAX_MANHEADER_LEN]; + int inlen; + int send_events; + int displaysystemname; /*!< Add system name to manager responses and events */ + /* Queued events that we've not had the ability to send yet */ + struct eventqent *eventq; + /* Timeout for ast_carefulwrite() */ + int writetimeout; + struct mansession *next; +} *sessions = NULL; + +static struct manager_action *first_action = NULL; +AST_MUTEX_DEFINE_STATIC(actionlock); + +/*! \brief Convert authority code to string with serveral options */ +static char *authority_to_str(int authority, char *res, int reslen) +{ + int running_total = 0, i; + memset(res, 0, reslen); + for (i=0; i running_total) ? reslen - running_total : 0); + running_total++; + } + strncat(res, perms[i].label, (reslen > running_total) ? reslen - running_total : 0); + running_total += strlen(perms[i].label); + } + } + if (ast_strlen_zero(res)) { + ast_copy_string(res, "", reslen); + } + return res; +} + +static char *complete_show_mancmd(const char *line, const char *word, int pos, int state) +{ + struct manager_action *cur; + int which = 0; + char *ret = NULL; + + ast_mutex_lock(&actionlock); + for (cur = first_action; cur; cur = cur->next) { /* Walk the list of actions */ + if (!strncasecmp(word, cur->action, strlen(word)) && ++which > state) { + ret = ast_strdup(cur->action); + break; /* make sure we exit even if ast_strdup() returns NULL */ + } + } + ast_mutex_unlock(&actionlock); + return ret; +} + +static void xml_copy_escape(char **dst, size_t *maxlen, const char *src, int lower) +{ + while (*src && (*maxlen > 6)) { + switch (*src) { + case '<': + strcpy(*dst, "<"); + (*dst) += 4; + *maxlen -= 4; + break; + case '>': + strcpy(*dst, ">"); + (*dst) += 4; + *maxlen -= 4; + break; + case '\"': + strcpy(*dst, """); + (*dst) += 6; + *maxlen -= 6; + break; + case '\'': + strcpy(*dst, "'"); + (*dst) += 6; + *maxlen -= 6; + break; + case '&': + strcpy(*dst, "&"); + (*dst) += 5; + *maxlen -= 5; + break; + default: + *(*dst)++ = lower ? tolower(*src) : *src; + (*maxlen)--; + } + src++; + } +} + +static char *xml_translate(char *in, struct ast_variable *vars) +{ + struct ast_variable *v; + char *dest = NULL; + char *out, *tmp, *var, *val; + char *objtype = NULL; + int colons = 0; + int breaks = 0; + size_t len; + int count = 1; + int escaped = 0; + int inobj = 0; + int x; + v = vars; + + while (v) { + if (!dest && !strcasecmp(v->name, "ajaxdest")) + dest = v->value; + else if (!objtype && !strcasecmp(v->name, "ajaxobjtype")) + objtype = v->value; + v = v->next; + } + if (!dest) + dest = "unknown"; + if (!objtype) + objtype = "generic"; + for (x=0; in[x]; x++) { + if (in[x] == ':') + colons++; + else if (in[x] == '\n') + breaks++; + else if (strchr("&\"<>", in[x])) + escaped++; + } + len = (size_t) (strlen(in) + colons * 5 + breaks * (40 + strlen(dest) + strlen(objtype)) + escaped * 10); /* foo="bar", "= 32)) + in++; + if (*in) { + if ((count > 3) && inobj) { + ast_build_string(&tmp, &len, " />\n"); + inobj = 0; + } + count = 0; + while (*in && (*in < 32)) { + *in = '\0'; + in++; + count++; + } + val = strchr(var, ':'); + if (val) { + *val = '\0'; + val++; + if (*val == ' ') + val++; + if (!inobj) { + ast_build_string(&tmp, &len, "<%s", dest, objtype); + inobj = 1; + } + ast_build_string(&tmp, &len, " "); + xml_copy_escape(&tmp, &len, var, 1); + ast_build_string(&tmp, &len, "='"); + xml_copy_escape(&tmp, &len, val, 0); + ast_build_string(&tmp, &len, "'"); + } + } + } + if (inobj) + ast_build_string(&tmp, &len, " />\n"); + return out; +} + +static char *html_translate(char *in) +{ + int x; + int colons = 0; + int breaks = 0; + size_t len; + int count = 1; + char *tmp, *var, *val, *out; + + for (x=0; in[x]; x++) { + if (in[x] == ':') + colons++; + if (in[x] == '\n') + breaks++; + } + len = strlen(in) + colons * 40 + breaks * 40; /* , "
*/ + out = ast_malloc(len); + if (!out) + return 0; + tmp = out; + while (*in) { + var = in; + while (*in && (*in >= 32)) + in++; + if (*in) { + if ((count % 4) == 0){ + ast_build_string(&tmp, &len, "
\r\n"); + } + count = 0; + while (*in && (*in < 32)) { + *in = '\0'; + in++; + count++; + } + val = strchr(var, ':'); + if (val) { + *val = '\0'; + val++; + if (*val == ' ') + val++; + ast_build_string(&tmp, &len, "%s%s\r\n", var, val); + } + } + } + return out; +} + +void astman_append(struct mansession *s, const char *fmt, ...) +{ + char *stuff; + int res; + va_list ap; + char *tmp; + + va_start(ap, fmt); + res = vasprintf(&stuff, fmt, ap); + va_end(ap); + if (res == -1) { + ast_log(LOG_ERROR, "Memory allocation failure\n"); + return; + } + if (s->fd > -1) + ast_carefulwrite(s->fd, stuff, strlen(stuff), s->writetimeout); + else { + tmp = realloc(s->outputstr, (s->outputstr ? strlen(s->outputstr) : 0) + strlen(stuff) + 1); + if (tmp) { + if (!s->outputstr) + tmp[0] = '\0'; + s->outputstr = tmp; + strcat(s->outputstr, stuff); + } + } + free(stuff); +} + +static int handle_showmancmd(int fd, int argc, char *argv[]) +{ + struct manager_action *cur = first_action; + char authority[80]; + int num; + + if (argc != 4) + return RESULT_SHOWUSAGE; + ast_mutex_lock(&actionlock); + while (cur) { /* Walk the list of actions */ + for (num = 3; num < argc; num++) { + if (!strcasecmp(cur->action, argv[num])) { + ast_cli(fd, "Action: %s\nSynopsis: %s\nPrivilege: %s\n%s\n", cur->action, cur->synopsis, authority_to_str(cur->authority, authority, sizeof(authority) -1), cur->description ? cur->description : ""); + } + } + cur = cur->next; + } + + ast_mutex_unlock(&actionlock); + return RESULT_SUCCESS; +} + +/*! \brief CLI command + Should change to "manager show commands" */ +static int handle_showmancmds(int fd, int argc, char *argv[]) +{ + struct manager_action *cur = first_action; + char authority[80]; + char *format = " %-15.15s %-15.15s %-55.55s\n"; + + ast_mutex_lock(&actionlock); + ast_cli(fd, format, "Action", "Privilege", "Synopsis"); + ast_cli(fd, format, "------", "---------", "--------"); + while (cur) { /* Walk the list of actions */ + ast_cli(fd, format, cur->action, authority_to_str(cur->authority, authority, sizeof(authority) -1), cur->synopsis); + cur = cur->next; + } + + ast_mutex_unlock(&actionlock); + return RESULT_SUCCESS; +} + +/*! \brief CLI command show manager connected */ +/* Should change to "manager show connected" */ +static int handle_showmanconn(int fd, int argc, char *argv[]) +{ + struct mansession *s; + char *format = " %-15.15s %-15.15s\n"; + ast_mutex_lock(&sessionlock); + s = sessions; + ast_cli(fd, format, "Username", "IP Address"); + while (s) { + ast_cli(fd, format,s->username, ast_inet_ntoa(s->sin.sin_addr)); + s = s->next; + } + + ast_mutex_unlock(&sessionlock); + return RESULT_SUCCESS; +} + +/*! \brief CLI command show manager connected */ +/* Should change to "manager show connected" */ +static int handle_showmaneventq(int fd, int argc, char *argv[]) +{ + struct eventqent *s; + ast_mutex_lock(&sessionlock); + s = master_eventq; + while (s) { + ast_cli(fd, "Usecount: %d\n",s->usecount); + ast_cli(fd, "Category: %d\n", s->category); + ast_cli(fd, "Event:\n%s", s->eventdata); + s = s->next; + } + ast_mutex_unlock(&sessionlock); + return RESULT_SUCCESS; +} + +static char showmancmd_help[] = +"Usage: show manager command \n" +" Shows the detailed description for a specific Asterisk manager interface command.\n"; + +static char showmancmds_help[] = +"Usage: show manager commands\n" +" Prints a listing of all the available Asterisk manager interface commands.\n"; + +static char showmanconn_help[] = +"Usage: show manager connected\n" +" Prints a listing of the users that are currently connected to the\n" +"Asterisk manager interface.\n"; + +static char showmaneventq_help[] = +"Usage: show manager eventq\n" +" Prints a listing of all events pending in the Asterisk manger\n" +"event queue.\n"; + +static struct ast_cli_entry show_mancmd_cli = + { { "show", "manager", "command", NULL }, + handle_showmancmd, "Show a manager interface command", showmancmd_help, complete_show_mancmd }; + +static struct ast_cli_entry show_mancmds_cli = + { { "show", "manager", "commands", NULL }, + handle_showmancmds, "List manager interface commands", showmancmds_help }; + +static struct ast_cli_entry show_manconn_cli = + { { "show", "manager", "connected", NULL }, + handle_showmanconn, "Show connected manager interface users", showmanconn_help }; + +static struct ast_cli_entry show_maneventq_cli = + { { "show", "manager", "eventq", NULL }, + handle_showmaneventq, "Show manager interface queued events", showmaneventq_help }; + +static void unuse_eventqent(struct eventqent *e) +{ + /* XXX Need to atomically decrement the users. Change this to atomic_dec + one day when we have such a beast XXX */ + int val; + ast_mutex_lock(&e->lock); + e->usecount--; + val = !e->usecount && e->next; + ast_mutex_unlock(&e->lock); + /* Wake up sleeping beauty */ + if (val) + pthread_kill(t, SIGURG); +} + +static void free_session(struct mansession *s) +{ + struct eventqent *eqe; + if (s->fd > -1) + close(s->fd); + if (s->outputstr) + free(s->outputstr); + ast_mutex_destroy(&s->__lock); + while (s->eventq) { + eqe = s->eventq; + s->eventq = s->eventq->next; + unuse_eventqent(eqe); + } + free(s); +} + +static void destroy_session(struct mansession *s) +{ + struct mansession *cur, *prev = NULL; + ast_mutex_lock(&sessionlock); + cur = sessions; + while (cur) { + if (cur == s) + break; + prev = cur; + cur = cur->next; + } + if (cur) { + if (prev) + prev->next = cur->next; + else + sessions = cur->next; + free_session(s); + num_sessions--; + } else + ast_log(LOG_WARNING, "Trying to delete nonexistent session %p?\n", s); + ast_mutex_unlock(&sessionlock); +} + +char *astman_get_header(struct message *m, char *var) +{ + char cmp[80]; + int x; + snprintf(cmp, sizeof(cmp), "%s: ", var); + for (x=0; xhdrcount; x++) + if (!strncasecmp(cmp, m->headers[x], strlen(cmp))) + return m->headers[x] + strlen(cmp); + return ""; +} + +struct ast_variable *astman_get_variables(struct message *m) +{ + int varlen, x, y; + struct ast_variable *head = NULL, *cur; + char *var, *val; + + char *parse; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(vars)[32]; + ); + + varlen = strlen("Variable: "); + + for (x = 0; x < m->hdrcount; x++) { + if (strncasecmp("Variable: ", m->headers[x], varlen)) + continue; + + parse = ast_strdupa(m->headers[x] + varlen); + + AST_STANDARD_APP_ARGS(args, parse); + if (args.argc) { + for (y = 0; y < args.argc; y++) { + if (!args.vars[y]) + continue; + var = val = ast_strdupa(args.vars[y]); + strsep(&val, "="); + if (!val || ast_strlen_zero(var)) + continue; + cur = ast_variable_new(var, val); + if (head) { + cur->next = head; + head = cur; + } else + head = cur; + } + } + } + + return head; +} + +/*! \note NOTE: + Callers of astman_send_error(), astman_send_response() or astman_send_ack() must EITHER + hold the session lock _or_ be running in an action callback (in which case s->busy will + be non-zero). In either of these cases, there is no need to lock-protect the session's + fd, since no other output will be sent (events will be queued), and no input will + be read until either the current action finishes or get_input() obtains the session + lock. + */ +void astman_send_error(struct mansession *s, struct message *m, char *error) +{ + char *id = astman_get_header(m,"ActionID"); + + astman_append(s, "Response: Error\r\n"); + if (!ast_strlen_zero(id)) + astman_append(s, "ActionID: %s\r\n", id); + astman_append(s, "Message: %s\r\n\r\n", error); +} + +void astman_send_response(struct mansession *s, struct message *m, char *resp, char *msg) +{ + char *id = astman_get_header(m,"ActionID"); + + astman_append(s, "Response: %s\r\n", resp); + if (!ast_strlen_zero(id)) + astman_append(s, "ActionID: %s\r\n", id); + if (msg) + astman_append(s, "Message: %s\r\n\r\n", msg); + else + astman_append(s, "\r\n"); +} + +void astman_send_ack(struct mansession *s, struct message *m, char *msg) +{ + astman_send_response(s, m, "Success", msg); +} + +/*! Tells you if smallstr exists inside bigstr + which is delim by delim and uses no buf or stringsep + ast_instring("this|that|more","this",',') == 1; + + feel free to move this to app.c -anthm */ +static int ast_instring(char *bigstr, char *smallstr, char delim) +{ + char *val = bigstr, *next; + + do { + if ((next = strchr(val, delim))) { + if (!strncmp(val, smallstr, (next - val))) + return 1; + else + continue; + } else + return !strcmp(smallstr, val); + + } while (*(val = (next + 1))); + + return 0; +} + +static int get_perm(char *instr) +{ + int x = 0, ret = 0; + + if (!instr) + return 0; + + for (x=0; x= 48 && string[x] <= 57)) { + ret = 0; + break; + } + } + + return ret ? atoi(string) : 0; +} + +static int ast_strings_to_mask(char *string) +{ + int x, ret = -1; + + x = ast_is_number(string); + + if (x) { + ret = x; + } else if (ast_strlen_zero(string)) { + ret = -1; + } else if (ast_false(string)) { + ret = 0; + } else if (ast_true(string)) { + ret = 0; + for (x=0; x__lock); + if (maskint >= 0) + s->send_events = maskint; + ast_mutex_unlock(&s->__lock); + + return maskint; +} + +static int authenticate(struct mansession *s, struct message *m) +{ + struct ast_config *cfg; + char *cat; + char *user = astman_get_header(m, "Username"); + char *pass = astman_get_header(m, "Secret"); + char *authtype = astman_get_header(m, "AuthType"); + char *key = astman_get_header(m, "Key"); + char *events = astman_get_header(m, "Events"); + + cfg = ast_config_load("manager.conf"); + if (!cfg) + return -1; + cat = ast_category_browse(cfg, NULL); + while (cat) { + if (strcasecmp(cat, "general")) { + /* This is a user */ + if (!strcasecmp(cat, user)) { + struct ast_variable *v; + struct ast_ha *ha = NULL; + char *password = NULL; + v = ast_variable_browse(cfg, cat); + while (v) { + if (!strcasecmp(v->name, "secret")) { + password = v->value; + } else if (!strcasecmp(v->name, "displaysystemname")) { + if (ast_true(v->value)) { + if (ast_strlen_zero(ast_config_AST_SYSTEM_NAME)) { + s->displaysystemname = 1; + } else { + ast_log(LOG_ERROR, "Can't enable displaysystemname in manager.conf - no system name configured in asterisk.conf\n"); + } + } + } else if (!strcasecmp(v->name, "permit") || + !strcasecmp(v->name, "deny")) { + ha = ast_append_ha(v->name, v->value, ha); + } else if (!strcasecmp(v->name, "writetimeout")) { + int val = atoi(v->value); + + if (val < 100) + ast_log(LOG_WARNING, "Invalid writetimeout value '%s' at line %d\n", v->value, v->lineno); + else + s->writetimeout = val; + } + + v = v->next; + } + if (ha && !ast_apply_ha(ha, &(s->sin))) { + ast_log(LOG_NOTICE, "%s failed to pass IP ACL as '%s'\n", ast_inet_ntoa(s->sin.sin_addr), user); + ast_free_ha(ha); + ast_config_destroy(cfg); + return -1; + } else if (ha) + ast_free_ha(ha); + if (!strcasecmp(authtype, "MD5")) { + if (!ast_strlen_zero(key) && s->challenge) { + int x; + int len = 0; + char md5key[256] = ""; + struct MD5Context md5; + unsigned char digest[16]; + MD5Init(&md5); + MD5Update(&md5, (unsigned char *) s->challenge, strlen(s->challenge)); + MD5Update(&md5, (unsigned char *) password, strlen(password)); + MD5Final(digest, &md5); + for (x=0; x<16; x++) + len += sprintf(md5key + len, "%2.2x", digest[x]); + if (!strcmp(md5key, key)) + break; + else { + ast_config_destroy(cfg); + return -1; + } + } + } else if (password && !strcmp(password, pass)) { + break; + } else { + ast_log(LOG_NOTICE, "%s failed to authenticate as '%s'\n", ast_inet_ntoa(s->sin.sin_addr), user); + ast_config_destroy(cfg); + return -1; + } + } + } + cat = ast_category_browse(cfg, cat); + } + if (cat) { + ast_copy_string(s->username, cat, sizeof(s->username)); + s->readperm = get_perm(ast_variable_retrieve(cfg, cat, "read")); + s->writeperm = get_perm(ast_variable_retrieve(cfg, cat, "write")); + ast_config_destroy(cfg); + if (events) + set_eventmask(s, events); + return 0; + } + ast_log(LOG_NOTICE, "%s tried to authenticate with nonexistent user '%s'\n", ast_inet_ntoa(s->sin.sin_addr), user); + ast_config_destroy(cfg); + return -1; +} + +/*! \brief Manager PING */ +static char mandescr_ping[] = +"Description: A 'Ping' action will ellicit a 'Pong' response. Used to keep the\n" +" manager connection open.\n" +"Variables: NONE\n"; + +static int action_ping(struct mansession *s, struct message *m) +{ + astman_send_response(s, m, "Pong", NULL); + return 0; +} + +static char mandescr_getconfig[] = +"Description: A 'GetConfig' action will dump the contents of a configuration\n" +"file by category and contents.\n" +"Variables:\n" +" Filename: Configuration filename (e.g. foo.conf)\n"; + +static int action_getconfig(struct mansession *s, struct message *m) +{ + struct ast_config *cfg; + char *fn = astman_get_header(m, "Filename"); + int catcount = 0; + int lineno = 0; + char *category=NULL; + struct ast_variable *v; + char idText[256] = ""; + char *id = astman_get_header(m, "ActionID"); + + if (!ast_strlen_zero(id)) + snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); + + if (ast_strlen_zero(fn)) { + astman_send_error(s, m, "Filename not specified"); + return 0; + } + if (!(cfg = ast_config_load(fn))) { + astman_send_error(s, m, "Config file not found"); + return 0; + } + astman_append(s, "Response: Success\r\n%s", idText); + while ((category = ast_category_browse(cfg, category))) { + lineno = 0; + astman_append(s, "Category-%06d: %s\r\n", catcount, category); + v = ast_variable_browse(cfg, category); + while (v) { + astman_append(s, "Line-%06d-%06d: %s=%s\r\n", catcount, lineno++, v->name, v->value); + v = v->next; + } + catcount++; + } + ast_config_destroy(cfg); + astman_append(s, "\r\n"); + return 0; +} + + +static void handle_updates(struct mansession *s, struct message *m, struct ast_config *cfg) +{ + int x; + char hdr[40]; + char *action, *cat, *var, *value, *match; + struct ast_category *category; + struct ast_variable *v; + + for (x=0;x<100000;x++) { + snprintf(hdr, sizeof(hdr), "Action-%06d", x); + action = astman_get_header(m, hdr); + if (ast_strlen_zero(action)) + break; + snprintf(hdr, sizeof(hdr), "Cat-%06d", x); + cat = astman_get_header(m, hdr); + snprintf(hdr, sizeof(hdr), "Var-%06d", x); + var = astman_get_header(m, hdr); + snprintf(hdr, sizeof(hdr), "Value-%06d", x); + value = astman_get_header(m, hdr); + snprintf(hdr, sizeof(hdr), "Match-%06d", x); + match = astman_get_header(m, hdr); + if (!strcasecmp(action, "newcat")) { + if (!ast_strlen_zero(cat)) { + category = ast_category_new(cat); + if (category) { + ast_category_append(cfg, category); + } + } + } else if (!strcasecmp(action, "renamecat")) { + if (!ast_strlen_zero(cat) && !ast_strlen_zero(value)) { + category = ast_category_get(cfg, cat); + if (category) + ast_category_rename(category, value); + } + } else if (!strcasecmp(action, "delcat")) { + if (!ast_strlen_zero(cat)) + ast_category_delete(cfg, cat); + } else if (!strcasecmp(action, "update")) { + if (!ast_strlen_zero(cat) && !ast_strlen_zero(var) && (category = ast_category_get(cfg, cat))) + ast_variable_update(category, var, value, match); + } else if (!strcasecmp(action, "delete")) { + if (!ast_strlen_zero(cat) && !ast_strlen_zero(var) && (category = ast_category_get(cfg, cat))) + ast_variable_delete(category, var, match); + } else if (!strcasecmp(action, "append")) { + if (!ast_strlen_zero(cat) && !ast_strlen_zero(var) && + (category = ast_category_get(cfg, cat)) && + (v = ast_variable_new(var, value))){ + if (match && !strcasecmp(match, "object")) + v->object = 1; + ast_variable_append(category, v); + } + } + } +} + +static char mandescr_updateconfig[] = +"Description: A 'UpdateConfig' action will dump the contents of a configuration\n" +"file by category and contents.\n" +"Variables (X's represent 6 digit number beginning with 000000):\n" +" SrcFilename: Configuration filename to read(e.g. foo.conf)\n" +" DstFilename: Configuration filename to write(e.g. foo.conf)\n" +" Action-XXXXXX: Action to Take (NewCat,RenameCat,DelCat,Update,Delete,Append)\n" +" Cat-XXXXXX: Category to operate on\n" +" Var-XXXXXX: Variable to work on\n" +" Value-XXXXXX: Value to work on\n" +" Match-XXXXXX: Extra match required to match line\n"; + +static int action_updateconfig(struct mansession *s, struct message *m) +{ + struct ast_config *cfg; + char *sfn = astman_get_header(m, "SrcFilename"); + char *dfn = astman_get_header(m, "DstFilename"); + int res; + char idText[256] = ""; + char *id = astman_get_header(m, "ActionID"); + + if (!ast_strlen_zero(id)) + snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); + + if (ast_strlen_zero(sfn) || ast_strlen_zero(dfn)) { + astman_send_error(s, m, "Filename not specified"); + return 0; + } + if (!(cfg = ast_config_load(sfn))) { + astman_send_error(s, m, "Config file not found"); + return 0; + } + handle_updates(s, m, cfg); + res = config_text_file_save(dfn, cfg, "Manager"); + ast_config_destroy(cfg); + if (res) { + astman_send_error(s, m, "Save of config failed"); + return 0; + } + astman_append(s, "Response: Success\r\n%s\r\n", idText); + return 0; +} + +/*! \brief Manager WAITEVENT */ +static char mandescr_waitevent[] = +"Description: A 'WaitEvent' action will ellicit a 'Success' response. Whenever\n" +"a manager event is queued. Once WaitEvent has been called on an HTTP manager\n" +"session, events will be generated and queued.\n" +"Variables: \n" +" Timeout: Maximum time to wait for events\n"; + +static int action_waitevent(struct mansession *s, struct message *m) +{ + char *timeouts = astman_get_header(m, "Timeout"); + int timeout = -1, max; + int x; + int needexit = 0; + time_t now; + struct eventqent *eqe; + char *id = astman_get_header(m,"ActionID"); + char idText[256] = ""; + + if (!ast_strlen_zero(id)) + snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); + + if (!ast_strlen_zero(timeouts)) { + sscanf(timeouts, "%i", &timeout); + } + + ast_mutex_lock(&s->__lock); + if (s->waiting_thread != AST_PTHREADT_NULL) { + pthread_kill(s->waiting_thread, SIGURG); + } + if (s->sessiontimeout) { + time(&now); + max = s->sessiontimeout - now - 10; + if (max < 0) + max = 0; + if ((timeout < 0) || (timeout > max)) + timeout = max; + if (!s->send_events) + s->send_events = -1; + /* Once waitevent is called, always queue events from now on */ + if (s->busy == 1) + s->busy = 2; + } + ast_mutex_unlock(&s->__lock); + s->waiting_thread = pthread_self(); + if (option_debug) + ast_log(LOG_DEBUG, "Starting waiting for an event!\n"); + for (x=0; ((x < timeout) || (timeout < 0)); x++) { + ast_mutex_lock(&s->__lock); + if (s->eventq && s->eventq->next) + needexit = 1; + if (s->waiting_thread != pthread_self()) + needexit = 1; + if (s->needdestroy) + needexit = 1; + ast_mutex_unlock(&s->__lock); + if (needexit) + break; + if (s->fd > 0) { + if (ast_wait_for_input(s->fd, 1000)) + break; + } else { + sleep(1); + } + } + if (option_debug) + ast_log(LOG_DEBUG, "Finished waiting for an event!\n"); + ast_mutex_lock(&s->__lock); + if (s->waiting_thread == pthread_self()) { + astman_send_response(s, m, "Success", "Waiting for Event..."); + /* Only show events if we're the most recent waiter */ + while(s->eventq->next) { + eqe = s->eventq->next; + if (((s->readperm & eqe->category) == eqe->category) && + ((s->send_events & eqe->category) == eqe->category)) { + astman_append(s, "%s", eqe->eventdata); + } + unuse_eventqent(s->eventq); + s->eventq = eqe; + } + astman_append(s, + "Event: WaitEventComplete\r\n" + "%s" + "\r\n", idText); + s->waiting_thread = AST_PTHREADT_NULL; + } else { + ast_log(LOG_DEBUG, "Abandoning event request!\n"); + } + ast_mutex_unlock(&s->__lock); + return 0; +} + +static char mandescr_listcommands[] = +"Description: Returns the action name and synopsis for every\n" +" action that is available to the user\n" +"Variables: NONE\n"; + +static int action_listcommands(struct mansession *s, struct message *m) +{ + struct manager_action *cur = first_action; + char idText[256] = ""; + char temp[BUFSIZ]; + char *id = astman_get_header(m,"ActionID"); + + if (!ast_strlen_zero(id)) + snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); + astman_append(s, "Response: Success\r\n%s", idText); + ast_mutex_lock(&actionlock); + while (cur) { /* Walk the list of actions */ + if ((s->writeperm & cur->authority) == cur->authority) + astman_append(s, "%s: %s (Priv: %s)\r\n", cur->action, cur->synopsis, authority_to_str(cur->authority, temp, sizeof(temp))); + cur = cur->next; + } + ast_mutex_unlock(&actionlock); + astman_append(s, "\r\n"); + + return 0; +} + +static char mandescr_events[] = +"Description: Enable/Disable sending of events to this manager\n" +" client.\n" +"Variables:\n" +" EventMask: 'on' if all events should be sent,\n" +" 'off' if no events should be sent,\n" +" 'system,call,log' to select which flags events should have to be sent.\n"; + +static int action_events(struct mansession *s, struct message *m) +{ + char *mask = astman_get_header(m, "EventMask"); + int res; + + res = set_eventmask(s, mask); + if (res > 0) + astman_send_response(s, m, "Events On", NULL); + else if (res == 0) + astman_send_response(s, m, "Events Off", NULL); + + return 0; +} + +static char mandescr_logoff[] = +"Description: Logoff this manager session\n" +"Variables: NONE\n"; + +static int action_logoff(struct mansession *s, struct message *m) +{ + astman_send_response(s, m, "Goodbye", "Thanks for all the fish."); + return -1; +} + +static char mandescr_hangup[] = +"Description: Hangup a channel\n" +"Variables: \n" +" Channel: The channel name to be hungup\n"; + +static int action_hangup(struct mansession *s, struct message *m) +{ + struct ast_channel *c = NULL; + char *name = astman_get_header(m, "Channel"); + if (ast_strlen_zero(name)) { + astman_send_error(s, m, "No channel specified"); + return 0; + } + c = ast_get_channel_by_name_locked(name); + if (!c) { + astman_send_error(s, m, "No such channel"); + return 0; + } + ast_softhangup(c, AST_SOFTHANGUP_EXPLICIT); + ast_channel_unlock(c); + astman_send_ack(s, m, "Channel Hungup"); + return 0; +} + +static char mandescr_setvar[] = +"Description: Set a global or local channel variable.\n" +"Variables: (Names marked with * are required)\n" +" Channel: Channel to set variable for\n" +" *Variable: Variable name\n" +" *Value: Value\n"; + +static int action_setvar(struct mansession *s, struct message *m) +{ + struct ast_channel *c = NULL; + char *name = astman_get_header(m, "Channel"); + char *varname = astman_get_header(m, "Variable"); + char *varval = astman_get_header(m, "Value"); + + if (ast_strlen_zero(varname)) { + astman_send_error(s, m, "No variable specified"); + return 0; + } + + if (ast_strlen_zero(varval)) { + astman_send_error(s, m, "No value specified"); + return 0; + } + + if (!ast_strlen_zero(name)) { + c = ast_get_channel_by_name_locked(name); + if (!c) { + astman_send_error(s, m, "No such channel"); + return 0; + } + } + + pbx_builtin_setvar_helper(c, varname, varval); + + if (c) + ast_channel_unlock(c); + + astman_send_ack(s, m, "Variable Set"); + + return 0; +} + +static char mandescr_getvar[] = +"Description: Get the value of a global or local channel variable.\n" +"Variables: (Names marked with * are required)\n" +" Channel: Channel to read variable from\n" +" *Variable: Variable name\n" +" ActionID: Optional Action id for message matching.\n"; + +static int action_getvar(struct mansession *s, struct message *m) +{ + struct ast_channel *c = NULL; + char *name = astman_get_header(m, "Channel"); + char *varname = astman_get_header(m, "Variable"); + char *id = astman_get_header(m,"ActionID"); + char *varval; + char workspace[1024]; + + if (ast_strlen_zero(varname)) { + astman_send_error(s, m, "No variable specified"); + return 0; + } + + if (!ast_strlen_zero(name)) { + c = ast_get_channel_by_name_locked(name); + if (!c) { + astman_send_error(s, m, "No such channel"); + return 0; + } + } + + if (varname[strlen(varname) - 1] == ')') { + ast_func_read(c, varname, workspace, sizeof(workspace)); + } else { + pbx_retrieve_variable(c, varname, &varval, workspace, sizeof(workspace), NULL); + } + + if (c) + ast_channel_unlock(c); + astman_append(s, "Response: Success\r\n" + "Variable: %s\r\nValue: %s\r\n", varname, varval); + if (!ast_strlen_zero(id)) + astman_append(s, "ActionID: %s\r\n",id); + astman_append(s, "\r\n"); + + return 0; +} + + +/*! \brief Manager "status" command to show channels */ +/* Needs documentation... */ +static int action_status(struct mansession *s, struct message *m) +{ + char *id = astman_get_header(m,"ActionID"); + char *name = astman_get_header(m,"Channel"); + char idText[256] = ""; + struct ast_channel *c; + char bridge[256]; + struct timeval now = ast_tvnow(); + long elapsed_seconds = 0; + int all = ast_strlen_zero(name); /* set if we want all channels */ + + astman_send_ack(s, m, "Channel status will follow"); + if (!ast_strlen_zero(id)) + snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); + if (all) + c = ast_channel_walk_locked(NULL); + else { + c = ast_get_channel_by_name_locked(name); + if (!c) { + astman_send_error(s, m, "No such channel"); + return 0; + } + } + /* if we look by name, we break after the first iteration */ + while (c) { + if (c->_bridge) + snprintf(bridge, sizeof(bridge), "Link: %s\r\n", c->_bridge->name); + else + bridge[0] = '\0'; + if (c->pbx) { + if (c->cdr) { + elapsed_seconds = now.tv_sec - c->cdr->start.tv_sec; + } + astman_append(s, + "Event: Status\r\n" + "Privilege: Call\r\n" + "Channel: %s\r\n" + "CallerID: %s\r\n" /* This parameter is deprecated and will be removed post-1.4 */ + "CallerIDNum: %s\r\n" + "CallerIDName: %s\r\n" + "Account: %s\r\n" + "State: %s\r\n" + "Context: %s\r\n" + "Extension: %s\r\n" + "Priority: %d\r\n" + "Seconds: %ld\r\n" + "%s" + "Uniqueid: %s\r\n" + "%s" + "\r\n", + c->name, + S_OR(c->cid.cid_num, ""), + S_OR(c->cid.cid_num, ""), + S_OR(c->cid.cid_name, ""), + c->accountcode, + ast_state2str(c->_state), c->context, + c->exten, c->priority, (long)elapsed_seconds, bridge, c->uniqueid, idText); + } else { + astman_append(s, + "Event: Status\r\n" + "Privilege: Call\r\n" + "Channel: %s\r\n" + "CallerID: %s\r\n" /* This parameter is deprecated and will be removed post-1.4 */ + "CallerIDNum: %s\r\n" + "CallerIDName: %s\r\n" + "Account: %s\r\n" + "State: %s\r\n" + "%s" + "Uniqueid: %s\r\n" + "%s" + "\r\n", + c->name, + S_OR(c->cid.cid_num, ""), + S_OR(c->cid.cid_num, ""), + S_OR(c->cid.cid_name, ""), + c->accountcode, + ast_state2str(c->_state), bridge, c->uniqueid, idText); + } + ast_channel_unlock(c); + if (!all) + break; + c = ast_channel_walk_locked(c); + } + astman_append(s, + "Event: StatusComplete\r\n" + "%s" + "\r\n",idText); + return 0; +} + +static char mandescr_redirect[] = +"Description: Redirect (transfer) a call.\n" +"Variables: (Names marked with * are required)\n" +" *Channel: Channel to redirect\n" +" ExtraChannel: Second call leg to transfer (optional)\n" +" *Exten: Extension to transfer to\n" +" *Context: Context to transfer to\n" +" *Priority: Priority to transfer to\n" +" ActionID: Optional Action id for message matching.\n"; + +/*! \brief action_redirect: The redirect manager command */ +static int action_redirect(struct mansession *s, struct message *m) +{ + char *name = astman_get_header(m, "Channel"); + char *name2 = astman_get_header(m, "ExtraChannel"); + char *exten = astman_get_header(m, "Exten"); + char *context = astman_get_header(m, "Context"); + char *priority = astman_get_header(m, "Priority"); + struct ast_channel *chan, *chan2 = NULL; + int pi = 0; + int res; + + if (ast_strlen_zero(name)) { + astman_send_error(s, m, "Channel not specified"); + return 0; + } + if (!ast_strlen_zero(priority) && (sscanf(priority, "%d", &pi) != 1)) { + if ((pi = ast_findlabel_extension(NULL, context, exten, priority, NULL)) < 1) { + astman_send_error(s, m, "Invalid priority\n"); + return 0; + } + } + /* XXX watch out, possible deadlock!!! */ + chan = ast_get_channel_by_name_locked(name); + if (!chan) { + char buf[BUFSIZ]; + snprintf(buf, sizeof(buf), "Channel does not exist: %s", name); + astman_send_error(s, m, buf); + return 0; + } + if (!ast_strlen_zero(name2)) + chan2 = ast_get_channel_by_name_locked(name2); + res = ast_async_goto(chan, context, exten, pi); + if (!res) { + if (!ast_strlen_zero(name2)) { + if (chan2) + res = ast_async_goto(chan2, context, exten, pi); + else + res = -1; + if (!res) + astman_send_ack(s, m, "Dual Redirect successful"); + else + astman_send_error(s, m, "Secondary redirect failed"); + } else + astman_send_ack(s, m, "Redirect successful"); + } else + astman_send_error(s, m, "Redirect failed"); + if (chan) + ast_channel_unlock(chan); + if (chan2) + ast_channel_unlock(chan2); + return 0; +} + +static char mandescr_command[] = +"Description: Run a CLI command.\n" +"Variables: (Names marked with * are required)\n" +" *Command: Asterisk CLI command to run\n" +" ActionID: Optional Action id for message matching.\n"; + +/*! \brief action_command: Manager command "command" - execute CLI command */ +static int action_command(struct mansession *s, struct message *m) +{ + char *cmd = astman_get_header(m, "Command"); + char *id = astman_get_header(m, "ActionID"); + astman_append(s, "Response: Follows\r\nPrivilege: Command\r\n"); + if (!ast_strlen_zero(id)) + astman_append(s, "ActionID: %s\r\n", id); + /* FIXME: Wedge a ActionID response in here, waiting for later changes */ + ast_cli_command(s->fd, cmd); + astman_append(s, "--END COMMAND--\r\n\r\n"); + return 0; +} + +static void *fast_originate(void *data) +{ + struct fast_originate_helper *in = data; + int res; + int reason = 0; + struct ast_channel *chan = NULL; + + if (!ast_strlen_zero(in->app)) { + res = ast_pbx_outgoing_app(in->tech, AST_FORMAT_SLINEAR, in->data, in->timeout, in->app, in->appdata, &reason, 1, + S_OR(in->cid_num, NULL), + S_OR(in->cid_name, NULL), + in->vars, in->account, &chan); + } else { + res = ast_pbx_outgoing_exten(in->tech, AST_FORMAT_SLINEAR, in->data, in->timeout, in->context, in->exten, in->priority, &reason, 1, + S_OR(in->cid_num, NULL), + S_OR(in->cid_name, NULL), + in->vars, in->account, &chan); + } + + /* Tell the manager what happened with the channel */ + manager_event(EVENT_FLAG_CALL, + res ? "OriginateFailure" : "OriginateSuccess", + "%s" + "Channel: %s/%s\r\n" + "Context: %s\r\n" + "Exten: %s\r\n" + "Reason: %d\r\n" + "Uniqueid: %s\r\n" + "CallerID: %s\r\n" /* This parameter is deprecated and will be removed post-1.4 */ + "CallerIDNum: %s\r\n" + "CallerIDName: %s\r\n", + in->idtext, in->tech, in->data, in->context, in->exten, reason, + chan ? chan->uniqueid : "", + S_OR(in->cid_num, ""), + S_OR(in->cid_num, ""), + S_OR(in->cid_name, "") + ); + + /* Locked by ast_pbx_outgoing_exten or ast_pbx_outgoing_app */ + if (chan) + ast_channel_unlock(chan); + free(in); + return NULL; +} + +static char mandescr_originate[] = +"Description: Generates an outgoing call to a Extension/Context/Priority or\n" +" Application/Data\n" +"Variables: (Names marked with * are required)\n" +" *Channel: Channel name to call\n" +" Exten: Extension to use (requires 'Context' and 'Priority')\n" +" Context: Context to use (requires 'Exten' and 'Priority')\n" +" Priority: Priority to use (requires 'Exten' and 'Context')\n" +" Application: Application to use\n" +" Data: Data to use (requires 'Application')\n" +" Timeout: How long to wait for call to be answered (in ms)\n" +" CallerID: Caller ID to be set on the outgoing channel\n" +" Variable: Channel variable to set, multiple Variable: headers are allowed\n" +" Account: Account code\n" +" Async: Set to 'true' for fast origination\n"; + +static int action_originate(struct mansession *s, struct message *m) +{ + char *name = astman_get_header(m, "Channel"); + char *exten = astman_get_header(m, "Exten"); + char *context = astman_get_header(m, "Context"); + char *priority = astman_get_header(m, "Priority"); + char *timeout = astman_get_header(m, "Timeout"); + char *callerid = astman_get_header(m, "CallerID"); + char *account = astman_get_header(m, "Account"); + char *app = astman_get_header(m, "Application"); + char *appdata = astman_get_header(m, "Data"); + char *async = astman_get_header(m, "Async"); + char *id = astman_get_header(m, "ActionID"); + struct ast_variable *vars = astman_get_variables(m); + char *tech, *data; + char *l = NULL, *n = NULL; + int pi = 0; + int res; + int to = 30000; + int reason = 0; + char tmp[256]; + char tmp2[256]; + + pthread_t th; + pthread_attr_t attr; + if (!name) { + astman_send_error(s, m, "Channel not specified"); + return 0; + } + if (!ast_strlen_zero(priority) && (sscanf(priority, "%d", &pi) != 1)) { + if ((pi = ast_findlabel_extension(NULL, context, exten, priority, NULL)) < 1) { + astman_send_error(s, m, "Invalid priority\n"); + return 0; + } + } + if (!ast_strlen_zero(timeout) && (sscanf(timeout, "%d", &to) != 1)) { + astman_send_error(s, m, "Invalid timeout\n"); + return 0; + } + ast_copy_string(tmp, name, sizeof(tmp)); + tech = tmp; + data = strchr(tmp, '/'); + if (!data) { + astman_send_error(s, m, "Invalid channel\n"); + return 0; + } + *data++ = '\0'; + ast_copy_string(tmp2, callerid, sizeof(tmp2)); + ast_callerid_parse(tmp2, &n, &l); + if (n) { + if (ast_strlen_zero(n)) + n = NULL; + } + if (l) { + ast_shrink_phone_number(l); + if (ast_strlen_zero(l)) + l = NULL; + } + if (ast_true(async)) { + struct fast_originate_helper *fast = ast_calloc(1, sizeof(*fast)); + if (!fast) { + res = -1; + } else { + if (!ast_strlen_zero(id)) + snprintf(fast->idtext, sizeof(fast->idtext), "ActionID: %s\r\n", id); + ast_copy_string(fast->tech, tech, sizeof(fast->tech)); + ast_copy_string(fast->data, data, sizeof(fast->data)); + ast_copy_string(fast->app, app, sizeof(fast->app)); + ast_copy_string(fast->appdata, appdata, sizeof(fast->appdata)); + if (l) + ast_copy_string(fast->cid_num, l, sizeof(fast->cid_num)); + if (n) + ast_copy_string(fast->cid_name, n, sizeof(fast->cid_name)); + fast->vars = vars; + ast_copy_string(fast->context, context, sizeof(fast->context)); + ast_copy_string(fast->exten, exten, sizeof(fast->exten)); + ast_copy_string(fast->account, account, sizeof(fast->account)); + fast->timeout = to; + fast->priority = pi; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (ast_pthread_create(&th, &attr, fast_originate, fast)) { + res = -1; + } else { + res = 0; + } + } + } else if (!ast_strlen_zero(app)) { + res = ast_pbx_outgoing_app(tech, AST_FORMAT_SLINEAR, data, to, app, appdata, &reason, 1, l, n, vars, account, NULL); + } else { + if (exten && context && pi) + res = ast_pbx_outgoing_exten(tech, AST_FORMAT_SLINEAR, data, to, context, exten, pi, &reason, 1, l, n, vars, account, NULL); + else { + astman_send_error(s, m, "Originate with 'Exten' requires 'Context' and 'Priority'"); + return 0; + } + } + if (!res) + astman_send_ack(s, m, "Originate successfully queued"); + else + astman_send_error(s, m, "Originate failed"); + return 0; +} + +/*! \brief Help text for manager command mailboxstatus + */ +static char mandescr_mailboxstatus[] = +"Description: Checks a voicemail account for status.\n" +"Variables: (Names marked with * are required)\n" +" *Mailbox: Full mailbox ID @\n" +" ActionID: Optional ActionID for message matching.\n" +"Returns number of messages.\n" +" Message: Mailbox Status\n" +" Mailbox: \n" +" Waiting: \n" +"\n"; + +static int action_mailboxstatus(struct mansession *s, struct message *m) +{ + char *mailbox = astman_get_header(m, "Mailbox"); + char *id = astman_get_header(m,"ActionID"); + char idText[256] = ""; + int ret; + if (ast_strlen_zero(mailbox)) { + astman_send_error(s, m, "Mailbox not specified"); + return 0; + } + if (!ast_strlen_zero(id)) + snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); + ret = ast_app_has_voicemail(mailbox, NULL); + astman_append(s, "Response: Success\r\n" + "%s" + "Message: Mailbox Status\r\n" + "Mailbox: %s\r\n" + "Waiting: %d\r\n\r\n", idText, mailbox, ret); + return 0; +} + +static char mandescr_mailboxcount[] = +"Description: Checks a voicemail account for new messages.\n" +"Variables: (Names marked with * are required)\n" +" *Mailbox: Full mailbox ID @\n" +" ActionID: Optional ActionID for message matching.\n" +"Returns number of new and old messages.\n" +" Message: Mailbox Message Count\n" +" Mailbox: \n" +" NewMessages: \n" +" OldMessages: \n" +"\n"; +static int action_mailboxcount(struct mansession *s, struct message *m) +{ + char *mailbox = astman_get_header(m, "Mailbox"); + char *id = astman_get_header(m,"ActionID"); + char idText[256] = ""; + int newmsgs = 0, oldmsgs = 0; + if (ast_strlen_zero(mailbox)) { + astman_send_error(s, m, "Mailbox not specified"); + return 0; + } + ast_app_inboxcount(mailbox, &newmsgs, &oldmsgs); + if (!ast_strlen_zero(id)) { + snprintf(idText, sizeof(idText), "ActionID: %s\r\n",id); + } + astman_append(s, "Response: Success\r\n" + "%s" + "Message: Mailbox Message Count\r\n" + "Mailbox: %s\r\n" + "NewMessages: %d\r\n" + "OldMessages: %d\r\n" + "\r\n", + idText,mailbox, newmsgs, oldmsgs); + return 0; +} + +static char mandescr_extensionstate[] = +"Description: Report the extension state for given extension.\n" +" If the extension has a hint, will use devicestate to check\n" +" the status of the device connected to the extension.\n" +"Variables: (Names marked with * are required)\n" +" *Exten: Extension to check state on\n" +" *Context: Context for extension\n" +" ActionId: Optional ID for this transaction\n" +"Will return an \"Extension Status\" message.\n" +"The response will include the hint for the extension and the status.\n"; + +static int action_extensionstate(struct mansession *s, struct message *m) +{ + char *exten = astman_get_header(m, "Exten"); + char *context = astman_get_header(m, "Context"); + char *id = astman_get_header(m,"ActionID"); + char idText[256] = ""; + char hint[256] = ""; + int status; + if (ast_strlen_zero(exten)) { + astman_send_error(s, m, "Extension not specified"); + return 0; + } + if (ast_strlen_zero(context)) + context = "default"; + status = ast_extension_state(NULL, context, exten); + ast_get_hint(hint, sizeof(hint) - 1, NULL, 0, NULL, context, exten); + if (!ast_strlen_zero(id)) { + snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); + } + astman_append(s, "Response: Success\r\n" + "%s" + "Message: Extension Status\r\n" + "Exten: %s\r\n" + "Context: %s\r\n" + "Hint: %s\r\n" + "Status: %d\r\n\r\n", + idText,exten, context, hint, status); + return 0; +} + +static char mandescr_timeout[] = +"Description: Hangup a channel after a certain time.\n" +"Variables: (Names marked with * are required)\n" +" *Channel: Channel name to hangup\n" +" *Timeout: Maximum duration of the call (sec)\n" +"Acknowledges set time with 'Timeout Set' message\n"; + +static int action_timeout(struct mansession *s, struct message *m) +{ + struct ast_channel *c = NULL; + char *name = astman_get_header(m, "Channel"); + int timeout = atoi(astman_get_header(m, "Timeout")); + if (ast_strlen_zero(name)) { + astman_send_error(s, m, "No channel specified"); + return 0; + } + if (!timeout) { + astman_send_error(s, m, "No timeout specified"); + return 0; + } + c = ast_get_channel_by_name_locked(name); + if (!c) { + astman_send_error(s, m, "No such channel"); + return 0; + } + ast_channel_setwhentohangup(c, timeout); + ast_channel_unlock(c); + astman_send_ack(s, m, "Timeout Set"); + return 0; +} + +static int process_events(struct mansession *s) +{ + struct eventqent *eqe; + int ret = 0; + ast_mutex_lock(&s->__lock); + if (s->fd > -1) { + s->busy--; + if (!s->eventq) + s->eventq = master_eventq; + while(s->eventq->next) { + eqe = s->eventq->next; + if ((s->authenticated && (s->readperm & eqe->category) == eqe->category) && + ((s->send_events & eqe->category) == eqe->category)) { + if (!ret && ast_carefulwrite(s->fd, eqe->eventdata, strlen(eqe->eventdata), s->writetimeout) < 0) + ret = -1; + } + unuse_eventqent(s->eventq); + s->eventq = eqe; + } + } + ast_mutex_unlock(&s->__lock); + return ret; +} + +static char mandescr_userevent[] = +"Description: Send an event to manager sessions.\n" +"Variables: (Names marked with * are required)\n" +" *UserEvent: EventStringToSend\n" +" Header1: Content1\n" +" HeaderN: ContentN\n"; + +static int action_userevent(struct mansession *s, struct message *m) +{ + char *event = astman_get_header(m, "UserEvent"); + char body[2048] = ""; + int x, bodylen = 0; + for (x = 0; x < m->hdrcount; x++) { + if (strncasecmp("UserEvent:", m->headers[x], strlen("UserEvent:"))) { + ast_copy_string(body + bodylen, m->headers[x], sizeof(body) - bodylen - 3); + bodylen += strlen(m->headers[x]); + ast_copy_string(body + bodylen, "\r\n", 3); + bodylen += 2; + } + } + + manager_event(EVENT_FLAG_USER, "UserEvent", "UserEvent: %s\r\n%s", event, body); + return 0; +} + +static int process_message(struct mansession *s, struct message *m) +{ + char action[80] = ""; + struct manager_action *tmp = first_action; + char *id = astman_get_header(m,"ActionID"); + char idText[256] = ""; + int ret = 0; + + ast_copy_string(action, astman_get_header(m, "Action"), sizeof(action)); + ast_log( LOG_DEBUG, "Manager received command '%s'\n", action ); + + if (ast_strlen_zero(action)) { + astman_send_error(s, m, "Missing action in request"); + return 0; + } + if (!ast_strlen_zero(id)) { + snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); + } + if (!s->authenticated) { + if (!strcasecmp(action, "Challenge")) { + char *authtype; + authtype = astman_get_header(m, "AuthType"); + if (!strcasecmp(authtype, "MD5")) { + if (ast_strlen_zero(s->challenge)) + snprintf(s->challenge, sizeof(s->challenge), "%ld", ast_random()); + ast_mutex_lock(&s->__lock); + astman_append(s, "Response: Success\r\n" + "%s" + "Challenge: %s\r\n\r\n", + idText, s->challenge); + ast_mutex_unlock(&s->__lock); + return 0; + } else { + astman_send_error(s, m, "Must specify AuthType"); + return 0; + } + } else if (!strcasecmp(action, "Login")) { + if (authenticate(s, m)) { + sleep(1); + astman_send_error(s, m, "Authentication failed"); + return -1; + } else { + s->authenticated = 1; + if (option_verbose > 1) { + if (displayconnects) { + ast_verbose(VERBOSE_PREFIX_2 "%sManager '%s' logged on from %s\n", (s->sessiontimeout ? "HTTP " : ""), s->username, ast_inet_ntoa(s->sin.sin_addr)); + } + } + ast_log(LOG_EVENT, "%sManager '%s' logged on from %s\n", (s->sessiontimeout ? "HTTP " : ""), s->username, ast_inet_ntoa(s->sin.sin_addr)); + astman_send_ack(s, m, "Authentication accepted"); + } + } else if (!strcasecmp(action, "Logoff")) { + astman_send_ack(s, m, "See ya"); + return -1; + } else + astman_send_error(s, m, "Authentication Required"); + } else { + ast_mutex_lock(&s->__lock); + s->busy++; + ast_mutex_unlock(&s->__lock); + while (tmp) { + if (!strcasecmp(action, tmp->action)) { + if ((s->writeperm & tmp->authority) == tmp->authority) { + if (tmp->func(s, m)) + ret = -1; + } else { + astman_send_error(s, m, "Permission denied"); + } + break; + } + tmp = tmp->next; + } + if (!tmp) + astman_send_error(s, m, "Invalid/unknown command"); + } + if (ret) + return ret; + return process_events(s); +} + +static int get_input(struct mansession *s, char *output) +{ + /* output must have at least sizeof(s->inbuf) space */ + int res; + int x; + struct pollfd fds[1]; + for (x = 1; x < s->inlen; x++) { + if ((s->inbuf[x] == '\n') && (s->inbuf[x-1] == '\r')) { + /* Copy output data up to and including \r\n */ + memcpy(output, s->inbuf, x + 1); + /* Add trailing \0 */ + output[x+1] = '\0'; + /* Move remaining data back to the front */ + memmove(s->inbuf, s->inbuf + x + 1, s->inlen - x); + s->inlen -= (x + 1); + return 1; + } + } + if (s->inlen >= sizeof(s->inbuf) - 1) { + ast_log(LOG_WARNING, "Dumping long line with no return from %s: %s\n", ast_inet_ntoa(s->sin.sin_addr), s->inbuf); + s->inlen = 0; + } + fds[0].fd = s->fd; + fds[0].events = POLLIN; + do { + ast_mutex_lock(&s->__lock); + s->waiting_thread = pthread_self(); + ast_mutex_unlock(&s->__lock); + + res = poll(fds, 1, -1); + + ast_mutex_lock(&s->__lock); + s->waiting_thread = AST_PTHREADT_NULL; + ast_mutex_unlock(&s->__lock); + if (res < 0) { + if (errno == EINTR) { + if (s->dead) + return -1; + return 0; + } + ast_log(LOG_WARNING, "Select returned error: %s\n", strerror(errno)); + return -1; + } else if (res > 0) { + ast_mutex_lock(&s->__lock); + res = read(s->fd, s->inbuf + s->inlen, sizeof(s->inbuf) - 1 - s->inlen); + ast_mutex_unlock(&s->__lock); + if (res < 1) + return -1; + break; + } + } while(1); + s->inlen += res; + s->inbuf[s->inlen] = '\0'; + return 0; +} + +static void *session_do(void *data) +{ + struct mansession *s = data; + struct message m; + int res; + + ast_mutex_lock(&s->__lock); + astman_append(s, "Asterisk Call Manager/1.0\r\n"); + ast_mutex_unlock(&s->__lock); + memset(&m, 0, sizeof(m)); + for (;;) { + res = get_input(s, m.headers[m.hdrcount]); + if (res > 0) { + /* Strip trailing \r\n */ + if (strlen(m.headers[m.hdrcount]) < 2) + continue; + m.headers[m.hdrcount][strlen(m.headers[m.hdrcount]) - 2] = '\0'; + if (ast_strlen_zero(m.headers[m.hdrcount])) { + if (process_message(s, &m)) + break; + memset(&m, 0, sizeof(m)); + } else if (m.hdrcount < AST_MAX_MANHEADERS - 1) + m.hdrcount++; + } else if (res < 0) { + break; + } else if (s->eventq->next) { + if (process_events(s)) + break; + } + } + if (s->authenticated) { + if (option_verbose > 1) { + if (displayconnects) + ast_verbose(VERBOSE_PREFIX_2 "Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(s->sin.sin_addr)); + } + ast_log(LOG_EVENT, "Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(s->sin.sin_addr)); + } else { + if (option_verbose > 1) { + if (displayconnects) + ast_verbose(VERBOSE_PREFIX_2 "Connect attempt from '%s' unable to authenticate\n", ast_inet_ntoa(s->sin.sin_addr)); + } + ast_log(LOG_EVENT, "Failed attempt from %s\n", ast_inet_ntoa(s->sin.sin_addr)); + } + destroy_session(s); + return NULL; +} + +static void *accept_thread(void *ignore) +{ + int as; + struct sockaddr_in sin; + socklen_t sinlen; + struct eventqent *eqe; + struct mansession *s, *prev = NULL, *next; + struct protoent *p; + int arg = 1; + int flags; + pthread_attr_t attr; + time_t now; + struct pollfd pfds[1]; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + for (;;) { + time(&now); + ast_mutex_lock(&sessionlock); + prev = NULL; + s = sessions; + while (s) { + next = s->next; + if (s->sessiontimeout && (now > s->sessiontimeout) && !s->inuse) { + num_sessions--; + if (prev) + prev->next = next; + else + sessions = next; + if (s->authenticated && (option_verbose > 1) && displayconnects) { + ast_verbose(VERBOSE_PREFIX_2 "HTTP Manager '%s' timed out from %s\n", + s->username, ast_inet_ntoa(s->sin.sin_addr)); + } + free_session(s); + } else + prev = s; + s = next; + } + /* Purge master event queue of old, unused events, but make sure we + always keep at least one in the queue */ + eqe = master_eventq; + while (master_eventq->next && !master_eventq->usecount) { + eqe = master_eventq; + master_eventq = master_eventq->next; + free(eqe); + } + ast_mutex_unlock(&sessionlock); + + sinlen = sizeof(sin); + pfds[0].fd = asock; + pfds[0].events = POLLIN; + /* Wait for something to happen, but timeout every few seconds so + we can ditch any old manager sessions */ + if (poll(pfds, 1, 5000) < 1) + continue; + as = accept(asock, (struct sockaddr *)&sin, &sinlen); + if (as < 0) { + ast_log(LOG_NOTICE, "Accept returned -1: %s\n", strerror(errno)); + continue; + } + p = getprotobyname("tcp"); + if (p) { + if( setsockopt(as, p->p_proto, TCP_NODELAY, (char *)&arg, sizeof(arg) ) < 0 ) { + ast_log(LOG_WARNING, "Failed to set manager tcp connection to TCP_NODELAY mode: %s\n", strerror(errno)); + } + } + if (!(s = ast_calloc(1, sizeof(*s)))) + continue; + + memcpy(&s->sin, &sin, sizeof(sin)); + s->writetimeout = 100; + s->waiting_thread = AST_PTHREADT_NULL; + + if (!block_sockets) { + /* For safety, make sure socket is non-blocking */ + flags = fcntl(as, F_GETFL); + fcntl(as, F_SETFL, flags | O_NONBLOCK); + } + ast_mutex_init(&s->__lock); + s->fd = as; + s->send_events = -1; + ast_mutex_lock(&sessionlock); + num_sessions++; + s->next = sessions; + sessions = s; + /* Find the last place in the master event queue and hook ourselves + in there */ + s->eventq = master_eventq; + while(s->eventq->next) + s->eventq = s->eventq->next; + ast_mutex_lock(&s->eventq->lock); + s->eventq->usecount++; + ast_mutex_unlock(&s->eventq->lock); + ast_mutex_unlock(&sessionlock); + if (ast_pthread_create(&s->t, &attr, session_do, s)) + destroy_session(s); + } + pthread_attr_destroy(&attr); + return NULL; +} + +static int append_event(const char *str, int category) +{ + struct eventqent *tmp, *prev = NULL; + tmp = ast_malloc(sizeof(*tmp) + strlen(str)); + + if (!tmp) + return -1; + + ast_mutex_init(&tmp->lock); + tmp->next = NULL; + tmp->category = category; + strcpy(tmp->eventdata, str); + + if (master_eventq) { + prev = master_eventq; + while (prev->next) + prev = prev->next; + prev->next = tmp; + } else { + master_eventq = tmp; + } + + tmp->usecount = num_sessions; + + return 0; +} + +/*! \brief manager_event: Send AMI event to client */ +int manager_event(int category, const char *event, const char *fmt, ...) +{ + struct mansession *s; + char auth[80]; + va_list ap; + struct timeval now; + struct ast_dynamic_str *buf; + + /* Abort if there aren't any manager sessions */ + if (!num_sessions) + return 0; + + if (!(buf = ast_dynamic_str_thread_get(&manager_event_buf, MANAGER_EVENT_BUF_INITSIZE))) + return -1; + + ast_dynamic_str_thread_set(&buf, 0, &manager_event_buf, + "Event: %s\r\nPrivilege: %s\r\n", + event, authority_to_str(category, auth, sizeof(auth))); + + if (timestampevents) { + now = ast_tvnow(); + ast_dynamic_str_thread_append(&buf, 0, &manager_event_buf, + "Timestamp: %ld.%06lu\r\n", + now.tv_sec, (unsigned long) now.tv_usec); + } + + va_start(ap, fmt); + ast_dynamic_str_thread_append_va(&buf, 0, &manager_event_buf, fmt, ap); + va_end(ap); + + ast_dynamic_str_thread_append(&buf, 0, &manager_event_buf, "\r\n"); + + ast_mutex_lock(&sessionlock); + /* Append even to master list and wake up any sleeping sessions */ + append_event(buf->str, category); + for (s = sessions; s; s = s->next) { + ast_mutex_lock(&s->__lock); + if (s->waiting_thread != AST_PTHREADT_NULL) + pthread_kill(s->waiting_thread, SIGURG); + ast_mutex_unlock(&s->__lock); + } + ast_mutex_unlock(&sessionlock); + + return 0; +} + +int ast_manager_unregister( char *action ) +{ + struct manager_action *cur = first_action, *prev = first_action; + + ast_mutex_lock(&actionlock); + while (cur) { + if (!strcasecmp(action, cur->action)) { + prev->next = cur->next; + free(cur); + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Manager unregistered action %s\n", action); + ast_mutex_unlock(&actionlock); + return 0; + } + prev = cur; + cur = cur->next; + } + ast_mutex_unlock(&actionlock); + return 0; +} + +static int manager_state_cb(char *context, char *exten, int state, void *data) +{ + /* Notify managers of change */ + manager_event(EVENT_FLAG_CALL, "ExtensionStatus", "Exten: %s\r\nContext: %s\r\nStatus: %d\r\n", exten, context, state); + return 0; +} + +static int ast_manager_register_struct(struct manager_action *act) +{ + struct manager_action *cur = first_action, *prev = NULL; + int ret; + + ast_mutex_lock(&actionlock); + while (cur) { /* Walk the list of actions */ + ret = strcasecmp(cur->action, act->action); + if (ret == 0) { + ast_log(LOG_WARNING, "Manager: Action '%s' already registered\n", act->action); + ast_mutex_unlock(&actionlock); + return -1; + } else if (ret > 0) { + /* Insert these alphabetically */ + if (prev) { + act->next = prev->next; + prev->next = act; + } else { + act->next = first_action; + first_action = act; + } + break; + } + prev = cur; + cur = cur->next; + } + + if (!cur) { + if (prev) + prev->next = act; + else + first_action = act; + act->next = NULL; + } + + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Manager registered action %s\n", act->action); + ast_mutex_unlock(&actionlock); + return 0; +} + +/*! \brief register a new command with manager, including online help. This is + the preferred way to register a manager command */ +int ast_manager_register2(const char *action, int auth, int (*func)(struct mansession *s, struct message *m), const char *synopsis, const char *description) +{ + struct manager_action *cur; + + cur = ast_malloc(sizeof(*cur)); + if (!cur) + return -1; + + cur->action = action; + cur->authority = auth; + cur->func = func; + cur->synopsis = synopsis; + cur->description = description; + cur->next = NULL; + + ast_manager_register_struct(cur); + + return 0; +} +/*! @} + END Doxygen group */ + +static struct mansession *find_session(unsigned long ident) +{ + struct mansession *s; + ast_mutex_lock(&sessionlock); + s = sessions; + while (s) { + ast_mutex_lock(&s->__lock); + if (s->sessiontimeout && (s->managerid == ident) && !s->needdestroy) { + s->inuse++; + break; + } + ast_mutex_unlock(&s->__lock); + s = s->next; + } + ast_mutex_unlock(&sessionlock); + return s; +} + + +static void vars2msg(struct message *m, struct ast_variable *vars) +{ + int x; + for (x = 0; vars && (x < AST_MAX_MANHEADERS); x++, vars = vars->next) { + if (!vars) + break; + m->hdrcount = x + 1; + snprintf(m->headers[x], sizeof(m->headers[x]), "%s: %s", vars->name, vars->value); + } +} + +#define FORMAT_RAW 0 +#define FORMAT_HTML 1 +#define FORMAT_XML 2 + +static char *contenttype[] = { "plain", "html", "xml" }; + +static char *generic_http_callback(int format, struct sockaddr_in *requestor, const char *uri, struct ast_variable *params, int *status, char **title, int *contentlength) +{ + struct mansession *s = NULL; + unsigned long ident = 0; + char workspace[512]; + char cookie[128]; + size_t len = sizeof(workspace); + int blastaway = 0; + char *c = workspace; + char *retval = NULL; + struct message m; + struct ast_variable *v; + + v = params; + while (v) { + if (!strcasecmp(v->name, "mansession_id")) { + sscanf(v->value, "%lx", &ident); + break; + } + v = v->next; + } + s = find_session(ident); + + if (!s) { + /* Create new session */ + s = ast_calloc(1, sizeof(struct mansession)); + if (!s) { + *status = 500; + goto generic_callback_out; + } + memcpy(&s->sin, requestor, sizeof(s->sin)); + s->fd = -1; + s->waiting_thread = AST_PTHREADT_NULL; + s->send_events = 0; + ast_mutex_init(&s->__lock); + ast_mutex_lock(&s->__lock); + ast_mutex_lock(&sessionlock); + s->inuse = 1; + s->managerid = rand() | (unsigned long)s; + s->next = sessions; + sessions = s; + num_sessions++; + /* Hook into the last spot in the event queue */ + s->eventq = master_eventq; + while (s->eventq->next) + s->eventq = s->eventq->next; + ast_mutex_lock(&s->eventq->lock); + s->eventq->usecount++; + ast_mutex_unlock(&s->eventq->lock); + ast_mutex_unlock(&sessionlock); + } + + /* Reset HTTP timeout. If we're not yet authenticated, keep it extremely short */ + time(&s->sessiontimeout); + if (!s->authenticated && (httptimeout > 5)) + s->sessiontimeout += 5; + else + s->sessiontimeout += httptimeout; + ast_mutex_unlock(&s->__lock); + + memset(&m, 0, sizeof(m)); + if (s) { + char tmp[80]; + ast_build_string(&c, &len, "Content-type: text/%s\r\n", contenttype[format]); + sprintf(tmp, "%08lx", s->managerid); + ast_build_string(&c, &len, "%s\r\n", ast_http_setcookie("mansession_id", tmp, httptimeout, cookie, sizeof(cookie))); + if (format == FORMAT_HTML) + ast_build_string(&c, &len, "Asterisk™ Manager Test Interface"); + vars2msg(&m, params); + if (format == FORMAT_XML) { + ast_build_string(&c, &len, "\n"); + } else if (format == FORMAT_HTML) { + ast_build_string(&c, &len, "\r\n"); + ast_build_string(&c, &len, "\r\n"); + } + if (process_message(s, &m)) { + if (s->authenticated) { + if (option_verbose > 1) { + if (displayconnects) + ast_verbose(VERBOSE_PREFIX_2 "HTTP Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(s->sin.sin_addr)); + } + ast_log(LOG_EVENT, "HTTP Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(s->sin.sin_addr)); + } else { + if (option_verbose > 1) { + if (displayconnects) + ast_verbose(VERBOSE_PREFIX_2 "HTTP Connect attempt from '%s' unable to authenticate\n", ast_inet_ntoa(s->sin.sin_addr)); + } + ast_log(LOG_EVENT, "HTTP Failed attempt from %s\n", ast_inet_ntoa(s->sin.sin_addr)); + } + s->needdestroy = 1; + } + if (s->outputstr) { + char *tmp; + if (format == FORMAT_XML) + tmp = xml_translate(s->outputstr, params); + else if (format == FORMAT_HTML) + tmp = html_translate(s->outputstr); + else + tmp = s->outputstr; + if (tmp) { + retval = malloc(strlen(workspace) + strlen(tmp) + 128); + if (retval) { + strcpy(retval, workspace); + strcpy(retval + strlen(retval), tmp); + c = retval + strlen(retval); + len = 120; + } + free(tmp); + } + if (tmp != s->outputstr) + free(s->outputstr); + s->outputstr = NULL; + } + /* Still okay because c would safely be pointing to workspace even + if retval failed to allocate above */ + if (format == FORMAT_XML) { + ast_build_string(&c, &len, "\n"); + } else if (format == FORMAT_HTML) + ast_build_string(&c, &len, "

  Manager Tester

\r\n"); + } else { + *status = 500; + *title = strdup("Server Error"); + } + ast_mutex_lock(&s->__lock); + if (s->needdestroy) { + if (s->inuse == 1) { + ast_log(LOG_DEBUG, "Need destroy, doing it now!\n"); + blastaway = 1; + } else { + ast_log(LOG_DEBUG, "Need destroy, but can't do it yet!\n"); + if (s->waiting_thread != AST_PTHREADT_NULL) + pthread_kill(s->waiting_thread, SIGURG); + s->inuse--; + } + } else + s->inuse--; + ast_mutex_unlock(&s->__lock); + + if (blastaway) + destroy_session(s); +generic_callback_out: + if (*status != 200) + return ast_http_error(500, "Server Error", NULL, "Internal Server Error (out of memory)\n"); + return retval; +} + +static char *manager_http_callback(struct sockaddr_in *requestor, const char *uri, struct ast_variable *params, int *status, char **title, int *contentlength) +{ + return generic_http_callback(FORMAT_HTML, requestor, uri, params, status, title, contentlength); +} + +static char *mxml_http_callback(struct sockaddr_in *requestor, const char *uri, struct ast_variable *params, int *status, char **title, int *contentlength) +{ + return generic_http_callback(FORMAT_XML, requestor, uri, params, status, title, contentlength); +} + +static char *rawman_http_callback(struct sockaddr_in *requestor, const char *uri, struct ast_variable *params, int *status, char **title, int *contentlength) +{ + return generic_http_callback(FORMAT_RAW, requestor, uri, params, status, title, contentlength); +} + +struct ast_http_uri rawmanuri = { + .description = "Raw HTTP Manager Event Interface", + .uri = "rawman", + .has_subtree = 0, + .callback = rawman_http_callback, +}; + +struct ast_http_uri manageruri = { + .description = "HTML Manager Event Interface", + .uri = "manager", + .has_subtree = 0, + .callback = manager_http_callback, +}; + +struct ast_http_uri managerxmluri = { + .description = "XML Manager Event Interface", + .uri = "mxml", + .has_subtree = 0, + .callback = mxml_http_callback, +}; + +static int registered = 0; +static int webregged = 0; + +int init_manager(void) +{ + struct ast_config *cfg; + char *val; + int oldportno = portno; + static struct sockaddr_in ba; + int x = 1; + int flags; + int webenabled = 0; + int newhttptimeout = 60; + if (!registered) { + /* Register default actions */ + ast_manager_register2("Ping", 0, action_ping, "Keepalive command", mandescr_ping); + ast_manager_register2("Events", 0, action_events, "Control Event Flow", mandescr_events); + ast_manager_register2("Logoff", 0, action_logoff, "Logoff Manager", mandescr_logoff); + ast_manager_register2("Hangup", EVENT_FLAG_CALL, action_hangup, "Hangup Channel", mandescr_hangup); + ast_manager_register("Status", EVENT_FLAG_CALL, action_status, "Lists channel status" ); + ast_manager_register2("Setvar", EVENT_FLAG_CALL, action_setvar, "Set Channel Variable", mandescr_setvar ); + ast_manager_register2("Getvar", EVENT_FLAG_CALL, action_getvar, "Gets a Channel Variable", mandescr_getvar ); + ast_manager_register2("GetConfig", EVENT_FLAG_CONFIG, action_getconfig, "Retrieve configuration", mandescr_getconfig); + ast_manager_register2("UpdateConfig", EVENT_FLAG_CONFIG, action_updateconfig, "Update basic configuration", mandescr_updateconfig); + ast_manager_register2("Redirect", EVENT_FLAG_CALL, action_redirect, "Redirect (transfer) a call", mandescr_redirect ); + ast_manager_register2("Originate", EVENT_FLAG_CALL, action_originate, "Originate Call", mandescr_originate); + ast_manager_register2("Command", EVENT_FLAG_COMMAND, action_command, "Execute Asterisk CLI Command", mandescr_command ); + ast_manager_register2("ExtensionState", EVENT_FLAG_CALL, action_extensionstate, "Check Extension Status", mandescr_extensionstate ); + ast_manager_register2("AbsoluteTimeout", EVENT_FLAG_CALL, action_timeout, "Set Absolute Timeout", mandescr_timeout ); + ast_manager_register2("MailboxStatus", EVENT_FLAG_CALL, action_mailboxstatus, "Check Mailbox", mandescr_mailboxstatus ); + ast_manager_register2("MailboxCount", EVENT_FLAG_CALL, action_mailboxcount, "Check Mailbox Message Count", mandescr_mailboxcount ); + ast_manager_register2("ListCommands", 0, action_listcommands, "List available manager commands", mandescr_listcommands); + ast_manager_register2("UserEvent", EVENT_FLAG_USER, action_userevent, "Send an arbitrary event", mandescr_userevent); + ast_manager_register2("WaitEvent", 0, action_waitevent, "Wait for an event to occur", mandescr_waitevent); + + ast_cli_register(&show_mancmd_cli); + ast_cli_register(&show_mancmds_cli); + ast_cli_register(&show_manconn_cli); + ast_cli_register(&show_maneventq_cli); + ast_extension_state_add(NULL, NULL, manager_state_cb, NULL); + registered = 1; + /* Append placeholder event so master_eventq never runs dry */ + append_event("Event: Placeholder\r\n\r\n", 0); + } + portno = DEFAULT_MANAGER_PORT; + displayconnects = 1; + cfg = ast_config_load("manager.conf"); + if (!cfg) { + ast_log(LOG_NOTICE, "Unable to open management configuration manager.conf. Call management disabled.\n"); + return 0; + } + val = ast_variable_retrieve(cfg, "general", "enabled"); + if (val) + enabled = ast_true(val); + + val = ast_variable_retrieve(cfg, "general", "block-sockets"); + if (val) + block_sockets = ast_true(val); + + val = ast_variable_retrieve(cfg, "general", "webenabled"); + if (val) + webenabled = ast_true(val); + + if ((val = ast_variable_retrieve(cfg, "general", "port"))) { + if (sscanf(val, "%d", &portno) != 1) { + ast_log(LOG_WARNING, "Invalid port number '%s'\n", val); + portno = DEFAULT_MANAGER_PORT; + } + } + + if ((val = ast_variable_retrieve(cfg, "general", "displayconnects"))) + displayconnects = ast_true(val); + + if ((val = ast_variable_retrieve(cfg, "general", "timestampevents"))) + timestampevents = ast_true(val); + + if ((val = ast_variable_retrieve(cfg, "general", "httptimeout"))) + newhttptimeout = atoi(val); + + memset(&ba, 0, sizeof(ba)); + ba.sin_family = AF_INET; + ba.sin_port = htons(portno); + + if ((val = ast_variable_retrieve(cfg, "general", "bindaddr"))) { + if (!inet_aton(val, &ba.sin_addr)) { + ast_log(LOG_WARNING, "Invalid address '%s' specified, using 0.0.0.0\n", val); + memset(&ba.sin_addr, 0, sizeof(ba.sin_addr)); + } + } + + + if ((asock > -1) && ((portno != oldportno) || !enabled)) { +#if 0 + /* Can't be done yet */ + close(asock); + asock = -1; +#else + ast_log(LOG_WARNING, "Unable to change management port / enabled\n"); +#endif + } + ast_config_destroy(cfg); + + if (webenabled && enabled) { + if (!webregged) { + ast_http_uri_link(&rawmanuri); + ast_http_uri_link(&manageruri); + ast_http_uri_link(&managerxmluri); + webregged = 1; + } + } else { + if (webregged) { + ast_http_uri_unlink(&rawmanuri); + ast_http_uri_unlink(&manageruri); + ast_http_uri_unlink(&managerxmluri); + webregged = 0; + } + } + + if (newhttptimeout > 0) + httptimeout = newhttptimeout; + + /* If not enabled, do nothing */ + if (!enabled) + return 0; + + if (asock < 0) { + asock = socket(AF_INET, SOCK_STREAM, 0); + if (asock < 0) { + ast_log(LOG_WARNING, "Unable to create socket: %s\n", strerror(errno)); + return -1; + } + setsockopt(asock, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x)); + if (bind(asock, (struct sockaddr *)&ba, sizeof(ba))) { + ast_log(LOG_WARNING, "Unable to bind socket: %s\n", strerror(errno)); + close(asock); + asock = -1; + return -1; + } + if (listen(asock, 2)) { + ast_log(LOG_WARNING, "Unable to listen on socket: %s\n", strerror(errno)); + close(asock); + asock = -1; + return -1; + } + flags = fcntl(asock, F_GETFL); + fcntl(asock, F_SETFL, flags | O_NONBLOCK); + if (option_verbose) + ast_verbose("Asterisk Management interface listening on port %d\n", portno); + ast_pthread_create(&t, NULL, accept_thread, NULL); + } + return 0; +} + +int reload_manager(void) +{ + manager_event(EVENT_FLAG_SYSTEM, "Reload", "Message: Reload Requested\r\n"); + return init_manager(); +} diff --git a/main/md5.c b/main/md5.c new file mode 100644 index 000000000..949c408e2 --- /dev/null +++ b/main/md5.c @@ -0,0 +1,267 @@ + +/*!\file +\brief MD5 checksum routines used for authentication. Not covered by GPL, but + in the public domain as per the copyright below */ + +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include /* for memcpy() */ + +#include "asterisk/endian.h" +#include "asterisk/md5.h" + +# if __BYTE_ORDER == __BIG_ENDIAN +# define HIGHFIRST 1 +# endif +#ifndef HIGHFIRST +#define byteReverse(buf, len) /* Nothing */ +#else +void byteReverse(unsigned char *buf, unsigned longs); + +#ifndef ASM_MD5 +/* + * Note: this code is harmless on little-endian machines. + */ +void byteReverse(unsigned char *buf, unsigned longs) +{ + uint32_t t; + do { + t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(uint32_t *) buf = t; + buf += 4; + } while (--longs); +} +#endif +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void MD5Init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void MD5Update(struct MD5Context *ctx, unsigned char const *buf, unsigned len) +{ + uint32_t t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void MD5Final(unsigned char digest[16], struct MD5Context *ctx) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32_t *) ctx->in)[14] = ctx->bits[0]; + ((uint32_t *) ctx->in)[15] = ctx->bits[1]; + + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + byteReverse((unsigned char *) ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void MD5Transform(uint32_t buf[4], uint32_t const in[16]) +{ + register uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#endif diff --git a/main/netsock.c b/main/netsock.c new file mode 100644 index 000000000..e19433bbb --- /dev/null +++ b/main/netsock.c @@ -0,0 +1,211 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Kevin P. Fleming + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Network socket handling + * + * \author Kevin P. Fleming + * \author Mark Spencer + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) +#include +#include +#endif + +#if defined (SOLARIS) +#include +#endif + +#include "asterisk/netsock.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/options.h" +#include "asterisk/utils.h" +#include "asterisk/lock.h" +#include "asterisk/srv.h" + +struct ast_netsock { + ASTOBJ_COMPONENTS(struct ast_netsock); + struct sockaddr_in bindaddr; + int sockfd; + int *ioref; + struct io_context *ioc; + void *data; +}; + +struct ast_netsock_list { + ASTOBJ_CONTAINER_COMPONENTS(struct ast_netsock); + struct io_context *ioc; +}; + +static void ast_netsock_destroy(struct ast_netsock *netsock) +{ + ast_io_remove(netsock->ioc, netsock->ioref); + close(netsock->sockfd); + free(netsock); +} + +struct ast_netsock_list *ast_netsock_list_alloc(void) +{ + return ast_calloc(1, sizeof(struct ast_netsock_list)); +} + +int ast_netsock_init(struct ast_netsock_list *list) +{ + memset(list, 0, sizeof(*list)); + ASTOBJ_CONTAINER_INIT(list); + + return 0; +} + +int ast_netsock_release(struct ast_netsock_list *list) +{ + ASTOBJ_CONTAINER_DESTROYALL(list, ast_netsock_destroy); + ASTOBJ_CONTAINER_DESTROY(list); + + return 0; +} + +struct ast_netsock *ast_netsock_find(struct ast_netsock_list *list, + struct sockaddr_in *sa) +{ + struct ast_netsock *sock = NULL; + + ASTOBJ_CONTAINER_TRAVERSE(list, !sock, { + ASTOBJ_RDLOCK(iterator); + if (!inaddrcmp(&iterator->bindaddr, sa)) + sock = iterator; + ASTOBJ_UNLOCK(iterator); + }); + + return sock; +} + +struct ast_netsock *ast_netsock_bindaddr(struct ast_netsock_list *list, struct io_context *ioc, struct sockaddr_in *bindaddr, int tos, ast_io_cb callback, void *data) +{ + int netsocket = -1; + int *ioref; + + struct ast_netsock *ns; + + /* Make a UDP socket */ + netsocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + + if (netsocket < 0) { + ast_log(LOG_ERROR, "Unable to create network socket: %s\n", strerror(errno)); + return NULL; + } + if (bind(netsocket,(struct sockaddr *)bindaddr, sizeof(struct sockaddr_in))) { + ast_log(LOG_ERROR, "Unable to bind to %s port %d: %s\n", ast_inet_ntoa(bindaddr->sin_addr), ntohs(bindaddr->sin_port), strerror(errno)); + close(netsocket); + return NULL; + } + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Using TOS bits %d\n", tos); + + if (setsockopt(netsocket, IPPROTO_IP, IP_TOS, &tos, sizeof(tos))) + ast_log(LOG_WARNING, "Unable to set TOS to %d\n", tos); + + if (!(ns = ast_calloc(1, sizeof(struct ast_netsock)))) { + close(netsocket); + return NULL; + } + + /* Establish I/O callback for socket read */ + if (!(ioref = ast_io_add(ioc, netsocket, callback, AST_IO_IN, ns))) { + close(netsocket); + free(ns); + return NULL; + } + ASTOBJ_INIT(ns); + ns->ioref = ioref; + ns->ioc = ioc; + ns->sockfd = netsocket; + ns->data = data; + memcpy(&ns->bindaddr, bindaddr, sizeof(ns->bindaddr)); + ASTOBJ_CONTAINER_LINK(list, ns); + + return ns; +} + +struct ast_netsock *ast_netsock_bind(struct ast_netsock_list *list, struct io_context *ioc, const char *bindinfo, int defaultport, int tos, ast_io_cb callback, void *data) +{ + struct sockaddr_in sin; + char *tmp; + char *host; + char *port; + int portno; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(defaultport); + tmp = ast_strdupa(bindinfo); + + host = strsep(&tmp, ":"); + port = tmp; + + if (port && ((portno = atoi(port)) > 0)) + sin.sin_port = htons(portno); + + inet_aton(host, &sin.sin_addr); + + return ast_netsock_bindaddr(list, ioc, &sin, tos, callback, data); +} + +int ast_netsock_sockfd(const struct ast_netsock *ns) +{ + return ns ? ns-> sockfd : -1; +} + +const struct sockaddr_in *ast_netsock_boundaddr(const struct ast_netsock *ns) +{ + return &(ns->bindaddr); +} + +void *ast_netsock_data(const struct ast_netsock *ns) +{ + return ns->data; +} + +void ast_netsock_unref(struct ast_netsock *ns) +{ + ASTOBJ_UNREF(ns, ast_netsock_destroy); +} diff --git a/main/pbx.c b/main/pbx.c new file mode 100644 index 000000000..94d44c918 --- /dev/null +++ b/main/pbx.c @@ -0,0 +1,5963 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Core PBX routines. + * + * \author Mark Spencer + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/cli.h" +#include "asterisk/pbx.h" +#include "asterisk/channel.h" +#include "asterisk/options.h" +#include "asterisk/logger.h" +#include "asterisk/file.h" +#include "asterisk/callerid.h" +#include "asterisk/cdr.h" +#include "asterisk/config.h" +#include "asterisk/term.h" +#include "asterisk/manager.h" +#include "asterisk/ast_expr.h" +#include "asterisk/linkedlists.h" +#define SAY_STUBS /* generate declarations and stubs for say methods */ +#include "asterisk/say.h" +#include "asterisk/utils.h" +#include "asterisk/causes.h" +#include "asterisk/musiconhold.h" +#include "asterisk/app.h" +#include "asterisk/devicestate.h" +#include "asterisk/stringfields.h" + +/*! + * \note I M P O R T A N T : + * + * The speed of extension handling will likely be among the most important + * aspects of this PBX. The switching scheme as it exists right now isn't + * terribly bad (it's O(N+M), where N is the # of extensions and M is the avg # + * of priorities, but a constant search time here would be great ;-) + * + */ + +#ifdef LOW_MEMORY +#define EXT_DATA_SIZE 256 +#else +#define EXT_DATA_SIZE 8192 +#endif + +#define SWITCH_DATA_LENGTH 256 + +#define VAR_BUF_SIZE 4096 + +#define VAR_NORMAL 1 +#define VAR_SOFTTRAN 2 +#define VAR_HARDTRAN 3 + +#define BACKGROUND_SKIP (1 << 0) +#define BACKGROUND_NOANSWER (1 << 1) +#define BACKGROUND_MATCHEXTEN (1 << 2) +#define BACKGROUND_PLAYBACK (1 << 3) + +AST_APP_OPTIONS(background_opts, { + AST_APP_OPTION('s', BACKGROUND_SKIP), + AST_APP_OPTION('n', BACKGROUND_NOANSWER), + AST_APP_OPTION('m', BACKGROUND_MATCHEXTEN), + AST_APP_OPTION('p', BACKGROUND_PLAYBACK), +}); + +#define WAITEXTEN_MOH (1 << 0) + +AST_APP_OPTIONS(waitexten_opts, { + AST_APP_OPTION_ARG('m', WAITEXTEN_MOH, 1), +}); + +struct ast_context; + +/*! + \brief ast_exten: An extension + The dialplan is saved as a linked list with each context + having it's own linked list of extensions - one item per + priority. +*/ +struct ast_exten { + char *exten; /*!< Extension name */ + int matchcid; /*!< Match caller id ? */ + const char *cidmatch; /*!< Caller id to match for this extension */ + int priority; /*!< Priority */ + const char *label; /*!< Label */ + struct ast_context *parent; /*!< The context this extension belongs to */ + const char *app; /*!< Application to execute */ + void *data; /*!< Data to use (arguments) */ + void (*datad)(void *); /*!< Data destructor */ + struct ast_exten *peer; /*!< Next higher priority with our extension */ + const char *registrar; /*!< Registrar */ + struct ast_exten *next; /*!< Extension with a greater ID */ + char stuff[0]; +}; + +/*! \brief ast_include: include= support in extensions.conf */ +struct ast_include { + const char *name; + const char *rname; /*!< Context to include */ + const char *registrar; /*!< Registrar */ + int hastime; /*!< If time construct exists */ + struct ast_timing timing; /*!< time construct */ + struct ast_include *next; /*!< Link them together */ + char stuff[0]; +}; + +/*! \brief ast_sw: Switch statement in extensions.conf */ +struct ast_sw { + char *name; + const char *registrar; /*!< Registrar */ + char *data; /*!< Data load */ + int eval; + AST_LIST_ENTRY(ast_sw) list; + char *tmpdata; + char stuff[0]; +}; + +/*! \brief ast_ignorepat: Ignore patterns in dial plan */ +struct ast_ignorepat { + const char *registrar; + struct ast_ignorepat *next; + const char pattern[0]; +}; + +/*! \brief ast_context: An extension context */ +struct ast_context { + ast_mutex_t lock; /*!< A lock to prevent multiple threads from clobbering the context */ + struct ast_exten *root; /*!< The root of the list of extensions */ + struct ast_context *next; /*!< Link them together */ + struct ast_include *includes; /*!< Include other contexts */ + struct ast_ignorepat *ignorepats; /*!< Patterns for which to continue playing dialtone */ + const char *registrar; /*!< Registrar */ + AST_LIST_HEAD_NOLOCK(, ast_sw) alts; /*!< Alternative switches */ + ast_mutex_t macrolock; /*!< A lock to implement "exclusive" macros - held whilst a call is executing in the macro */ + char name[0]; /*!< Name of the context */ +}; + + +/*! \brief ast_app: A registered application */ +struct ast_app { + int (*execute)(struct ast_channel *chan, void *data); + const char *synopsis; /*!< Synopsis text for 'show applications' */ + const char *description; /*!< Description (help text) for 'show application <name>' */ + AST_LIST_ENTRY(ast_app) list; /*!< Next app in list */ + struct module *module; /*!< Module this app belongs to */ + char name[0]; /*!< Name of the application */ +}; + +/*! \brief ast_state_cb: An extension state notify register item */ +struct ast_state_cb { + int id; + void *data; + ast_state_cb_type callback; + struct ast_state_cb *next; +}; + +/*! \brief Structure for dial plan hints + + \note Hints are pointers from an extension in the dialplan to one or + more devices (tech/name) */ +struct ast_hint { + struct ast_exten *exten; /*!< Extension */ + int laststate; /*!< Last known state */ + struct ast_state_cb *callbacks; /*!< Callback list for this extension */ + AST_LIST_ENTRY(ast_hint) list; /*!< Pointer to next hint in list */ +}; + +static const struct cfextension_states { + int extension_state; + const char * const text; +} extension_states[] = { + { AST_EXTENSION_NOT_INUSE, "Idle" }, + { AST_EXTENSION_INUSE, "InUse" }, + { AST_EXTENSION_BUSY, "Busy" }, + { AST_EXTENSION_UNAVAILABLE, "Unavailable" }, + { AST_EXTENSION_RINGING, "Ringing" }, + { AST_EXTENSION_INUSE | AST_EXTENSION_RINGING, "InUse&Ringing" }, + { AST_EXTENSION_ONHOLD, "Hold" }, + { AST_EXTENSION_INUSE | AST_EXTENSION_ONHOLD, "InUse&Hold" } +}; + +static int pbx_builtin_answer(struct ast_channel *, void *); +static int pbx_builtin_goto(struct ast_channel *, void *); +static int pbx_builtin_hangup(struct ast_channel *, void *); +static int pbx_builtin_background(struct ast_channel *, void *); +static int pbx_builtin_wait(struct ast_channel *, void *); +static int pbx_builtin_waitexten(struct ast_channel *, void *); +static int pbx_builtin_resetcdr(struct ast_channel *, void *); +static int pbx_builtin_setamaflags(struct ast_channel *, void *); +static int pbx_builtin_ringing(struct ast_channel *, void *); +static int pbx_builtin_progress(struct ast_channel *, void *); +static int pbx_builtin_congestion(struct ast_channel *, void *); +static int pbx_builtin_busy(struct ast_channel *, void *); +static int pbx_builtin_setglobalvar(struct ast_channel *, void *); +static int pbx_builtin_noop(struct ast_channel *, void *); +static int pbx_builtin_gotoif(struct ast_channel *, void *); +static int pbx_builtin_gotoiftime(struct ast_channel *, void *); +static int pbx_builtin_execiftime(struct ast_channel *, void *); +static int pbx_builtin_saynumber(struct ast_channel *, void *); +static int pbx_builtin_saydigits(struct ast_channel *, void *); +static int pbx_builtin_saycharacters(struct ast_channel *, void *); +static int pbx_builtin_sayphonetic(struct ast_channel *, void *); +int pbx_builtin_setvar(struct ast_channel *, void *); +static int pbx_builtin_importvar(struct ast_channel *, void *); + +AST_MUTEX_DEFINE_STATIC(globalslock); +static struct varshead globals; + +static int autofallthrough = 0; + +AST_MUTEX_DEFINE_STATIC(maxcalllock); +static int countcalls = 0; + +static AST_LIST_HEAD_STATIC(acf_root, ast_custom_function); + +/*! \brief Declaration of builtin applications */ +static struct pbx_builtin { + char name[AST_MAX_APP]; + int (*execute)(struct ast_channel *chan, void *data); + char *synopsis; + char *description; +} builtins[] = +{ + /* These applications are built into the PBX core and do not + need separate modules */ + + { "Answer", pbx_builtin_answer, + "Answer a channel if ringing", + " Answer([delay]): If the call has not been answered, this application will\n" + "answer it. Otherwise, it has no effect on the call. If a delay is specified,\n" + "Asterisk will wait this number of milliseconds before answering the call.\n" + }, + + { "BackGround", pbx_builtin_background, + "Play an audio file while waiting for digits of an extension to go to.", + " Background(filename1[&filename2...][|options[|langoverride][|context]]):\n" + "This application will play the given list of files while waiting for an\n" + "extension to be dialed by the calling channel. To continue waiting for digits\n" + "after this application has finished playing files, the WaitExten application\n" + "should be used. The 'langoverride' option explicitly specifies which language\n" + "to attempt to use for the requested sound files. If a 'context' is specified,\n" + "this is the dialplan context that this application will use when exiting to a\n" + "dialed extension." + " If one of the requested sound files does not exist, call processing will be\n" + "terminated.\n" + " Options:\n" + " s - Causes the playback of the message to be skipped\n" + " if the channel is not in the 'up' state (i.e. it\n" + " hasn't been answered yet). If this happens, the\n" + " application will return immediately.\n" + " n - Don't answer the channel before playing the files.\n" + " m - Only break if a digit hit matches a one digit\n" + " extension in the destination context.\n" + }, + + { "Busy", pbx_builtin_busy, + "Indicate the Busy condition", + " Busy([timeout]): This application will indicate the busy condition to\n" + "the calling channel. If the optional timeout is specified, the calling channel\n" + "will be hung up after the specified number of seconds. Otherwise, this\n" + "application will wait until the calling channel hangs up.\n" + }, + + { "Congestion", pbx_builtin_congestion, + "Indicate the Congestion condition", + " Congestion([timeout]): This application will indicate the congestion\n" + "condition to the calling channel. If the optional timeout is specified, the\n" + "calling channel will be hung up after the specified number of seconds.\n" + "Otherwise, this application will wait until the calling channel hangs up.\n" + }, + + { "Goto", pbx_builtin_goto, + "Jump to a particular priority, extension, or context", + " Goto([[context|]extension|]priority): This application will cause the\n" + "calling channel to continue dialplan execution at the specified priority.\n" + "If no specific extension, or extension and context, are specified, then this\n" + "application will jump to the specified priority of the current extension.\n" + " If the attempt to jump to another location in the dialplan is not successful,\n" + "then the channel will continue at the next priority of the current extension.\n" + }, + + { "GotoIf", pbx_builtin_gotoif, + "Conditional goto", + " GotoIf(condition?[labeliftrue]:[labeliffalse]): This application will cause\n" + "the calling channel to jump to the specified location in the dialplan based on\n" + "the evaluation of the given condition. The channel will continue at\n" + "'labeliftrue' if the condition is true, or 'labeliffalse' if the condition is\n" + "false. The labels are specified with the same syntax as used within the Goto\n" + "application. If the label chosen by the condition is omitted, no jump is\n" + "performed, but execution continues with the next priority in the dialplan.\n" + }, + + { "GotoIfTime", pbx_builtin_gotoiftime, + "Conditional Goto based on the current time", + " GotoIfTime(|||?[[context|]exten|]priority):\n" + "This application will have the calling channel jump to the specified location\n" + "in the dialplan if the current time matches the given time specification.\n" + }, + + { "ExecIfTime", pbx_builtin_execiftime, + "Conditional application execution based on the current time", + " ExecIfTime(|||?appname[|appargs]):\n" + "This application will execute the specified dialplan application, with optional\n" + "arguments, if the current time matches the given time specification.\n" + }, + + { "Hangup", pbx_builtin_hangup, + "Hang up the calling channel", + " Hangup([causecode]): This application will hang up the calling channel.\n" + "If a causecode is given the channel's hangup cause will be set to the given\n" + "value.\n" + }, + + { "NoOp", pbx_builtin_noop, + "Do Nothing", + " NoOp(): This applicatiion does nothing. However, it is useful for debugging\n" + "purposes. Any text that is provided as arguments to this application can be\n" + "viewed at the Asterisk CLI. This method can be used to see the evaluations of\n" + "variables or functions without having any effect." + }, + + { "Progress", pbx_builtin_progress, + "Indicate progress", + " Progress(): This application will request that in-band progress information\n" + "be provided to the calling channel.\n" + }, + + { "ResetCDR", pbx_builtin_resetcdr, + "Resets the Call Data Record", + " ResetCDR([options]): This application causes the Call Data Record to be\n" + "reset.\n" + " Options:\n" + " w -- Store the current CDR record before resetting it.\n" + " a -- Store any stacked records.\n" + " v -- Save CDR variables.\n" + }, + + { "Ringing", pbx_builtin_ringing, + "Indicate ringing tone", + " Ringing(): This application will request that the channel indicate a ringing\n" + "tone to the user.\n" + }, + + { "SayNumber", pbx_builtin_saynumber, + "Say Number", + " SayNumber(digits[,gender]): This application will play the sounds that\n" + "correspond to the given number. Optionally, a gender may be specified.\n" + "This will use the language that is currently set for the channel. See the\n" + "LANGUAGE function for more information on setting the language for the channel.\n" + }, + + { "SayDigits", pbx_builtin_saydigits, + "Say Digits", + " SayDigits(digits): This application will play the sounds that correspond\n" + "to the digits of the given number. This will use the language that is currently\n" + "set for the channel. See the LANGUAGE function for more information on setting\n" + "the language for the channel.\n" + }, + + { "SayAlpha", pbx_builtin_saycharacters, + "Say Alpha", + " SayAlpha(string): This application will play the sounds that correspond to\n" + "the letters of the given string.\n" + }, + + { "SayPhonetic", pbx_builtin_sayphonetic, + "Say Phonetic", + " SayPhonetic(string): This application will play the sounds from the phonetic\n" + "alphabet that correspond to the letters in the given string.\n" + }, + + { "SetAMAFlags", pbx_builtin_setamaflags, + "Set the AMA Flags", + " SetAMAFlags([flag]): This application will set the channel's AMA Flags for\n" + " billing purposes.\n" + }, + + { "SetGlobalVar", pbx_builtin_setglobalvar, + "Set a global variable to a given value", + " SetGlobalVar(variable=value): This application sets a given global variable to\n" + "the specified value.\n" + }, + + { "Set", pbx_builtin_setvar, + "Set channel variable(s) or function value(s)", + " Set(name1=value1|name2=value2|..[|options])\n" + "This function can be used to set the value of channel variables or dialplan\n" + "functions. It will accept up to 24 name/value pairs. When setting variables,\n" + "if the variable name is prefixed with _, the variable will be inherited into\n" + "channels created from the current channel. If the variable name is prefixed\n" + "with __, the variable will be inherited into channels created from the current\n" + "channel and all children channels.\n" + " Options:\n" + " g - Set variable globally instead of on the channel\n" + " (applies only to variables, not functions)\n" + }, + + { "ImportVar", pbx_builtin_importvar, + "Import a variable from a channel into a new variable", + " ImportVar(newvar=channelname|variable): This application imports a variable\n" + "from the specified channel (as opposed to the current one) and stores it as\n" + "a variable in the current channel (the channel that is calling this\n" + "application). Variables created by this application have the same inheritance\n" + "properties as those created with the Set application. See the documentation for\n" + "Set for more information.\n" + }, + + { "Wait", pbx_builtin_wait, + "Waits for some time", + " Wait(seconds): This application waits for a specified number of seconds.\n" + "Then, dialplan execution will continue at the next priority.\n" + " Note that the seconds can be passed with fractions of a second. For example,\n" + "'1.5' will ask the application to wait for 1.5 seconds.\n" + }, + + { "WaitExten", pbx_builtin_waitexten, + "Waits for an extension to be entered", + " WaitExten([seconds][|options]): This application waits for the user to enter\n" + "a new extension for a specified number of seconds.\n" + " Note that the seconds can be passed with fractions of a second. For example,\n" + "'1.5' will ask the application to wait for 1.5 seconds.\n" + " Options:\n" + " m[(x)] - Provide music on hold to the caller while waiting for an extension.\n" + " Optionally, specify the class for music on hold within parenthesis.\n" + }, + +}; + +static struct ast_context *contexts = NULL; +AST_MUTEX_DEFINE_STATIC(conlock); /*!< Lock for the ast_context list */ + +static AST_LIST_HEAD_STATIC(apps, ast_app); + +static AST_LIST_HEAD_STATIC(switches, ast_switch); + +static int stateid = 1; +/* WARNING: + When holding this list's lock, do _not_ do anything that will cause conlock + to be taken, unless you _already_ hold it. The ast_merge_contexts_and_delete + function will take the locks in conlock/hints order, so any other + paths that require both locks must also take them in that order. +*/ +static AST_LIST_HEAD_STATIC(hints, ast_hint); +struct ast_state_cb *statecbs = NULL; + +/* + \note This function is special. It saves the stack so that no matter + how many times it is called, it returns to the same place */ +int pbx_exec(struct ast_channel *c, /*!< Channel */ + struct ast_app *app, /*!< Application */ + void *data) /*!< Data for execution */ +{ + int res; + + const char *saved_c_appl; + const char *saved_c_data; + + if (c->cdr) + ast_cdr_setapp(c->cdr, app->name, data); + + /* save channel values */ + saved_c_appl= c->appl; + saved_c_data= c->data; + + c->appl = app->name; + c->data = data; + /* XXX remember what to to when we have linked apps to modules */ + if (app->module) { + /* XXX LOCAL_USER_ADD(app->module) */ + } + res = app->execute(c, data); + if (app->module) { + /* XXX LOCAL_USER_REMOVE(app->module) */ + } + /* restore channel values */ + c->appl = saved_c_appl; + c->data = saved_c_data; + return res; +} + + +/*! Go no deeper than this through includes (not counting loops) */ +#define AST_PBX_MAX_STACK 128 + +/*! \brief Find application handle in linked list + */ +struct ast_app *pbx_findapp(const char *app) +{ + struct ast_app *tmp; + + AST_LIST_LOCK(&apps); + AST_LIST_TRAVERSE(&apps, tmp, list) { + if (!strcasecmp(tmp->name, app)) + break; + } + AST_LIST_UNLOCK(&apps); + + return tmp; +} + +static struct ast_switch *pbx_findswitch(const char *sw) +{ + struct ast_switch *asw; + + AST_LIST_LOCK(&switches); + AST_LIST_TRAVERSE(&switches, asw, list) { + if (!strcasecmp(asw->name, sw)) + break; + } + AST_LIST_UNLOCK(&switches); + + return asw; +} + +static inline int include_valid(struct ast_include *i) +{ + if (!i->hastime) + return 1; + + return ast_check_timing(&(i->timing)); +} + +static void pbx_destroy(struct ast_pbx *p) +{ + free(p); +} + +/* + * Special characters used in patterns: + * '_' underscore is the leading character of a pattern. + * In other position it is treated as a regular char. + * ' ' '-' space and '-' are separator and ignored. + * . one or more of any character. Only allowed at the end of + * a pattern. + * ! zero or more of anything. Also impacts the result of CANMATCH + * and MATCHMORE. Only allowed at the end of a pattern. + * In the core routine, ! causes a match with a return code of 2. + * In turn, depending on the search mode: (XXX check if it is implemented) + * - E_MATCH retuns 1 (does match) + * - E_MATCHMORE returns 0 (no match) + * - E_CANMATCH returns 1 (does match) + * + * / should not appear as it is considered the separator of the CID info. + * XXX at the moment we may stop on this char. + * + * X Z N match ranges 0-9, 1-9, 2-9 respectively. + * [ denotes the start of a set of character. Everything inside + * is considered literally. We can have ranges a-d and individual + * characters. A '[' and '-' can be considered literally if they + * are just before ']'. + * XXX currently there is no way to specify ']' in a range, nor \ is + * considered specially. + * + * When we compare a pattern with a specific extension, all characters in the extension + * itself are considered literally with the only exception of '-' which is considered + * as a separator and thus ignored. + * XXX do we want to consider space as a separator as well ? + * XXX do we want to consider the separators in non-patterns as well ? + */ + +/*! + * \brief helper functions to sort extensions and patterns in the desired way, + * so that more specific patterns appear first. + * + * ext_cmp1 compares individual characters (or sets of), returning + * an int where bits 0-7 are the ASCII code of the first char in the set, + * while bit 8-15 are the cardinality of the set minus 1. + * This way more specific patterns (smaller cardinality) appear first. + * Wildcards have a special value, so that we can directly compare them to + * sets by subtracting the two values. In particular: + * 0x000xx one character, xx + * 0x0yyxx yy character set starting with xx + * 0x10000 '.' (one or more of anything) + * 0x20000 '!' (zero or more of anything) + * 0x30000 NUL (end of string) + * 0x40000 error in set. + * The pointer to the string is advanced according to needs. + * NOTES: + * 1. the empty set is equivalent to NUL. + * 2. given that a full set has always 0 as the first element, + * we could encode the special cases as 0xffXX where XX + * is 1, 2, 3, 4 as used above. + */ +static int ext_cmp1(const char **p) +{ + uint32_t chars[8]; + int c, cmin = 0xff, count = 0; + const char *end; + + /* load, sign extend and advance pointer until we find + * a valid character. + */ + while ( (c = *(*p)++) && (c == ' ' || c == '-') ) + ; /* ignore some characters */ + + /* always return unless we have a set of chars */ + switch (c) { + default: /* ordinary character */ + return 0x0000 | (c & 0xff); + + case 'N': /* 2..9 */ + return 0x0700 | '2' ; + + case 'X': /* 0..9 */ + return 0x0900 | '0'; + + case 'Z': /* 1..9 */ + return 0x0800 | '1'; + + case '.': /* wildcard */ + return 0x10000; + + case '!': /* earlymatch */ + return 0x20000; /* less specific than NULL */ + + case '\0': /* empty string */ + *p = NULL; + return 0x30000; + + case '[': /* pattern */ + break; + } + /* locate end of set */ + end = strchr(*p, ']'); + + if (end == NULL) { + ast_log(LOG_WARNING, "Wrong usage of [] in the extension\n"); + return 0x40000; /* XXX make this entry go last... */ + } + + bzero(chars, sizeof(chars)); /* clear all chars in the set */ + for (; *p < end ; (*p)++) { + unsigned char c1, c2; /* first-last char in range */ + c1 = (unsigned char)((*p)[0]); + if (*p + 2 < end && (*p)[1] == '-') { /* this is a range */ + c2 = (unsigned char)((*p)[2]); + *p += 2; /* skip a total of 3 chars */ + } else /* individual character */ + c2 = c1; + if (c1 < cmin) + cmin = c1; + for (; c1 <= c2; c1++) { + uint32_t mask = 1 << (c1 % 32); + if ( (chars[ c1 / 32 ] & mask) == 0) + count += 0x100; + chars[ c1 / 32 ] |= mask; + } + } + (*p)++; + return count == 0 ? 0x30000 : (count | cmin); +} + +/*! + * \brief the full routine to compare extensions in rules. + */ +static int ext_cmp(const char *a, const char *b) +{ + /* make sure non-patterns come first. + * If a is not a pattern, it either comes first or + * we use strcmp to compare the strings. + */ + int ret = 0; + + if (a[0] != '_') + return (b[0] == '_') ? -1 : strcmp(a, b); + + /* Now we know a is a pattern; if b is not, a comes first */ + if (b[0] != '_') + return 1; +#if 0 /* old mode for ext matching */ + return strcmp(a, b); +#endif + /* ok we need full pattern sorting routine */ + while (!ret && a && b) + ret = ext_cmp1(&a) - ext_cmp1(&b); + if (ret == 0) + return 0; + else + return (ret > 0) ? 1 : -1; +} + +/*! + * When looking up extensions, we can have different requests + * identified by the 'action' argument, as follows. + * Note that the coding is such that the low 4 bits are the + * third argument to extension_match_core. + */ +enum ext_match_t { + E_MATCHMORE = 0x00, /* extension can match but only with more 'digits' */ + E_CANMATCH = 0x01, /* extension can match with or without more 'digits' */ + E_MATCH = 0x02, /* extension is an exact match */ + E_MATCH_MASK = 0x03, /* mask for the argument to extension_match_core() */ + E_SPAWN = 0x12, /* want to spawn an extension. Requires exact match */ + E_FINDLABEL = 0x22 /* returns the priority for a given label. Requires exact match */ +}; + +/* + * Internal function for ast_extension_{match|close} + * return 0 on no-match, 1 on match, 2 on early match. + * mode is as follows: + * E_MATCH success only on exact match + * E_MATCHMORE success only on partial match (i.e. leftover digits in pattern) + * E_CANMATCH either of the above. + */ +static int _extension_match_core(const char *pattern, const char *data, enum ext_match_t mode) +{ + mode &= E_MATCH_MASK; /* only consider the relevant bits */ + + if (pattern[0] != '_') { /* not a pattern, try exact or partial match */ + int ld = strlen(data), lp = strlen(pattern); + + if (lp < ld) /* pattern too short, cannot match */ + return 0; + /* depending on the mode, accept full or partial match or both */ + if (mode == E_MATCH) + return !strcmp(pattern, data); /* 1 on match, 0 on fail */ + if (ld == 0 || !strncasecmp(pattern, data, ld)) /* partial or full match */ + return (mode == E_MATCHMORE) ? lp > ld : 1; /* XXX should consider '!' and '/' ? */ + else + return 0; + } + pattern++; /* skip leading _ */ + /* + * XXX below we stop at '/' which is a separator for the CID info. However we should + * not store '/' in the pattern at all. When we insure it, we can remove the checks. + */ + while (*data && *pattern && *pattern != '/') { + const char *end; + + if (*data == '-') { /* skip '-' in data (just a separator) */ + data++; + continue; + } + switch (toupper(*pattern)) { + case '[': /* a range */ + end = strchr(pattern+1, ']'); /* XXX should deal with escapes ? */ + if (end == NULL) { + ast_log(LOG_WARNING, "Wrong usage of [] in the extension\n"); + return 0; /* unconditional failure */ + } + for (pattern++; pattern != end; pattern++) { + if (pattern+2 < end && pattern[1] == '-') { /* this is a range */ + if (*data >= pattern[0] && *data <= pattern[2]) + break; /* match found */ + else { + pattern += 2; /* skip a total of 3 chars */ + continue; + } + } else if (*data == pattern[0]) + break; /* match found */ + } + if (pattern == end) + return 0; + pattern = end; /* skip and continue */ + break; + case 'N': + if (*data < '2' || *data > '9') + return 0; + break; + case 'X': + if (*data < '0' || *data > '9') + return 0; + break; + case 'Z': + if (*data < '1' || *data > '9') + return 0; + break; + case '.': /* Must match, even with more digits */ + return 1; + case '!': /* Early match */ + return 2; + case ' ': + case '-': /* Ignore these in patterns */ + data--; /* compensate the final data++ */ + break; + default: + if (*data != *pattern) + return 0; + } + data++; + pattern++; + } + if (*data) /* data longer than pattern, no match */ + return 0; + /* + * match so far, but ran off the end of the data. + * Depending on what is next, determine match or not. + */ + if (*pattern == '\0' || *pattern == '/') /* exact match */ + return (mode == E_MATCHMORE) ? 0 : 1; /* this is a failure for E_MATCHMORE */ + else if (*pattern == '!') /* early match */ + return 2; + else /* partial match */ + return (mode == E_MATCH) ? 0 : 1; /* this is a failure for E_MATCH */ +} + +/* + * Wrapper around _extension_match_core() to do performance measurement + * using the profiling code. + */ +static int extension_match_core(const char *pattern, const char *data, enum ext_match_t mode) +{ + int i; + static int prof_id = -2; /* marker for 'unallocated' id */ + if (prof_id == -2) + prof_id = ast_add_profile("ext_match", 0); + ast_mark(prof_id, 1); + i = _extension_match_core(pattern, data, mode); + ast_mark(prof_id, 0); + return i; +} + +int ast_extension_match(const char *pattern, const char *data) +{ + return extension_match_core(pattern, data, E_MATCH); +} + +int ast_extension_close(const char *pattern, const char *data, int needmore) +{ + if (needmore != E_MATCHMORE && needmore != E_CANMATCH) + ast_log(LOG_WARNING, "invalid argument %d\n", needmore); + return extension_match_core(pattern, data, needmore); +} + +struct ast_context *ast_context_find(const char *name) +{ + struct ast_context *tmp = NULL; + ast_mutex_lock(&conlock); + while ( (tmp = ast_walk_contexts(tmp)) ) { + if (!name || !strcasecmp(name, tmp->name)) + break; + } + ast_mutex_unlock(&conlock); + return tmp; +} + +#define STATUS_NO_CONTEXT 1 +#define STATUS_NO_EXTENSION 2 +#define STATUS_NO_PRIORITY 3 +#define STATUS_NO_LABEL 4 +#define STATUS_SUCCESS 5 + +static int matchcid(const char *cidpattern, const char *callerid) +{ + /* If the Caller*ID pattern is empty, then we're matching NO Caller*ID, so + failing to get a number should count as a match, otherwise not */ + + if (ast_strlen_zero(callerid)) + return ast_strlen_zero(cidpattern) ? 1 : 0; + + return ast_extension_match(cidpattern, callerid); +} + +/* request and result for pbx_find_extension */ +struct pbx_find_info { +#if 0 + const char *context; + const char *exten; + int priority; +#endif + + char *incstack[AST_PBX_MAX_STACK]; /* filled during the search */ + int stacklen; /* modified during the search */ + int status; /* set on return */ + struct ast_switch *swo; /* set on return */ + const char *data; /* set on return */ + const char *foundcontext; /* set on return */ +}; + +static struct ast_exten *pbx_find_extension(struct ast_channel *chan, + struct ast_context *bypass, struct pbx_find_info *q, + const char *context, const char *exten, int priority, + const char *label, const char *callerid, enum ext_match_t action) +{ + int x, res; + struct ast_context *tmp; + struct ast_exten *e, *eroot; + struct ast_include *i; + struct ast_sw *sw; + + /* Initialize status if appropriate */ + if (q->stacklen == 0) { + q->status = STATUS_NO_CONTEXT; + q->swo = NULL; + q->data = NULL; + q->foundcontext = NULL; + } + /* Check for stack overflow */ + if (q->stacklen >= AST_PBX_MAX_STACK) { + ast_log(LOG_WARNING, "Maximum PBX stack exceeded\n"); + return NULL; + } + /* Check first to see if we've already been checked */ + for (x = 0; x < q->stacklen; x++) { + if (!strcasecmp(q->incstack[x], context)) + return NULL; + } + if (bypass) /* bypass means we only look there */ + tmp = bypass; + else { /* look in contexts */ + tmp = NULL; + while ((tmp = ast_walk_contexts(tmp)) ) { + if (!strcmp(tmp->name, context)) + break; + } + if (!tmp) + return NULL; + } + if (q->status < STATUS_NO_EXTENSION) + q->status = STATUS_NO_EXTENSION; + + /* scan the list trying to match extension and CID */ + eroot = NULL; + while ( (eroot = ast_walk_context_extensions(tmp, eroot)) ) { + int match = extension_match_core(eroot->exten, exten, action); + /* 0 on fail, 1 on match, 2 on earlymatch */ + + if (!match || (eroot->matchcid && !matchcid(eroot->cidmatch, callerid))) + continue; /* keep trying */ + if (match == 2 && action == E_MATCHMORE) { + /* We match an extension ending in '!'. + * The decision in this case is final and is NULL (no match). + */ + return NULL; + } + /* found entry, now look for the right priority */ + if (q->status < STATUS_NO_PRIORITY) + q->status = STATUS_NO_PRIORITY; + e = NULL; + while ( (e = ast_walk_extension_priorities(eroot, e)) ) { + /* Match label or priority */ + if (action == E_FINDLABEL) { + if (q->status < STATUS_NO_LABEL) + q->status = STATUS_NO_LABEL; + if (label && e->label && !strcmp(label, e->label)) + break; /* found it */ + } else if (e->priority == priority) { + break; /* found it */ + } /* else keep searching */ + } + if (e) { /* found a valid match */ + q->status = STATUS_SUCCESS; + q->foundcontext = context; + return e; + } + } + /* Check alternative switches */ + AST_LIST_TRAVERSE(&tmp->alts, sw, list) { + struct ast_switch *asw = pbx_findswitch(sw->name); + ast_switch_f *aswf = NULL; + char *datap; + + if (!asw) { + ast_log(LOG_WARNING, "No such switch '%s'\n", sw->name); + continue; + } + /* Substitute variables now */ + if (sw->eval) + pbx_substitute_variables_helper(chan, sw->data, sw->tmpdata, SWITCH_DATA_LENGTH - 1); + + /* equivalent of extension_match_core() at the switch level */ + if (action == E_CANMATCH) + aswf = asw->canmatch; + else if (action == E_MATCHMORE) + aswf = asw->matchmore; + else /* action == E_MATCH */ + aswf = asw->exists; + datap = sw->eval ? sw->tmpdata : sw->data; + res = !aswf ? 0 : aswf(chan, context, exten, priority, callerid, datap); + if (res) { /* Got a match */ + q->swo = asw; + q->data = datap; + q->foundcontext = context; + /* XXX keep status = STATUS_NO_CONTEXT ? */ + return NULL; + } + } + q->incstack[q->stacklen++] = tmp->name; /* Setup the stack */ + /* Now try any includes we have in this context */ + for (i = tmp->includes; i; i = i->next) { + if (include_valid(i)) { + if ((e = pbx_find_extension(chan, bypass, q, i->rname, exten, priority, label, callerid, action))) + return e; + if (q->swo) + return NULL; + } + } + return NULL; +} + +/*! \brief extract offset:length from variable name. + * Returns 1 if there is a offset:length part, which is + * trimmed off (values go into variables) + */ +static int parse_variable_name(char *var, int *offset, int *length, int *isfunc) +{ + int parens=0; + + *offset = 0; + *length = INT_MAX; + *isfunc = 0; + for (; *var; var++) { + if (*var == '(') { + (*isfunc)++; + parens++; + } else if (*var == ')') { + parens--; + } else if (*var == ':' && parens == 0) { + *var++ = '\0'; + sscanf(var, "%d:%d", offset, length); + return 1; /* offset:length valid */ + } + } + return 0; +} + +/*! \brief takes a substring. It is ok to call with value == workspace. + * + * offset < 0 means start from the end of the string and set the beginning + * to be that many characters back. + * length is the length of the substring. A value less than 0 means to leave + * that many off the end. + * Always return a copy in workspace. + */ +static char *substring(const char *value, int offset, int length, char *workspace, size_t workspace_len) +{ + char *ret = workspace; + int lr; /* length of the input string after the copy */ + + ast_copy_string(workspace, value, workspace_len); /* always make a copy */ + + lr = strlen(ret); /* compute length after copy, so we never go out of the workspace */ + + /* Quick check if no need to do anything */ + if (offset == 0 && length >= lr) /* take the whole string */ + return ret; + + if (offset < 0) { /* translate negative offset into positive ones */ + offset = lr + offset; + if (offset < 0) /* If the negative offset was greater than the length of the string, just start at the beginning */ + offset = 0; + } + + /* too large offset result in empty string so we know what to return */ + if (offset >= lr) + return ret + lr; /* the final '\0' */ + + ret += offset; /* move to the start position */ + if (length >= 0 && length < lr - offset) /* truncate if necessary */ + ret[length] = '\0'; + else if (length < 0) { + if (lr > offset - length) /* After we remove from the front and from the rear, is there anything left? */ + ret[lr + length - offset] = '\0'; + else + ret[0] = '\0'; + } + + return ret; +} + +/*! \brief pbx_retrieve_variable: Support for Asterisk built-in variables and + functions in the dialplan + ---*/ +void pbx_retrieve_variable(struct ast_channel *c, const char *var, char **ret, char *workspace, int workspacelen, struct varshead *headp) +{ + const char not_found = '\0'; + char *tmpvar; + const char *s; /* the result */ + int offset, length; + int i, need_substring; + struct varshead *places[2] = { headp, &globals }; /* list of places where we may look */ + + if (c) { + places[0] = &c->varshead; + } + /* + * Make a copy of var because parse_variable_name() modifies the string. + * Then if called directly, we might need to run substring() on the result; + * remember this for later in 'need_substring', 'offset' and 'length' + */ + tmpvar = ast_strdupa(var); /* parse_variable_name modifies the string */ + need_substring = parse_variable_name(tmpvar, &offset, &length, &i /* ignored */); + + /* + * Look first into predefined variables, then into variable lists. + * Variable 's' points to the result, according to the following rules: + * s == ¬_found (set at the beginning) means that we did not find a + * matching variable and need to look into more places. + * If s != ¬_found, s is a valid result string as follows: + * s = NULL if the variable does not have a value; + * you typically do this when looking for an unset predefined variable. + * s = workspace if the result has been assembled there; + * typically done when the result is built e.g. with an snprintf(), + * so we don't need to do an additional copy. + * s != workspace in case we have a string, that needs to be copied + * (the ast_copy_string is done once for all at the end). + * Typically done when the result is already available in some string. + */ + s = ¬_found; /* default value */ + if (c) { /* This group requires a valid channel */ + /* Names with common parts are looked up a piece at a time using strncmp. */ + if (!strncmp(var, "CALL", 4)) { + if (!strncmp(var + 4, "ING", 3)) { + if (!strcmp(var + 7, "PRES")) { /* CALLINGPRES */ + snprintf(workspace, workspacelen, "%d", c->cid.cid_pres); + s = workspace; + } else if (!strcmp(var + 7, "ANI2")) { /* CALLINGANI2 */ + snprintf(workspace, workspacelen, "%d", c->cid.cid_ani2); + s = workspace; + } else if (!strcmp(var + 7, "TON")) { /* CALLINGTON */ + snprintf(workspace, workspacelen, "%d", c->cid.cid_ton); + s = workspace; + } else if (!strcmp(var + 7, "TNS")) { /* CALLINGTNS */ + snprintf(workspace, workspacelen, "%d", c->cid.cid_tns); + s = workspace; + } + } + } else if (!strcmp(var, "HINT")) { + s = ast_get_hint(workspace, workspacelen, NULL, 0, c, c->context, c->exten) ? workspace : NULL; + } else if (!strcmp(var, "HINTNAME")) { + s = ast_get_hint(NULL, 0, workspace, workspacelen, c, c->context, c->exten) ? workspace : NULL; + } else if (!strcmp(var, "EXTEN")) { + s = c->exten; + } else if (!strcmp(var, "CONTEXT")) { + s = c->context; + } else if (!strcmp(var, "PRIORITY")) { + snprintf(workspace, workspacelen, "%d", c->priority); + s = workspace; + } else if (!strcmp(var, "CHANNEL")) { + s = c->name; + } else if (!strcmp(var, "UNIQUEID")) { + s = c->uniqueid; + } else if (!strcmp(var, "HANGUPCAUSE")) { + snprintf(workspace, workspacelen, "%d", c->hangupcause); + s = workspace; + } + } + if (s == ¬_found) { /* look for more */ + if (!strcmp(var, "EPOCH")) { + snprintf(workspace, workspacelen, "%u",(int)time(NULL)); + s = workspace; + } else if (!strcmp(var, "SYSTEMNAME")) { + s = ast_config_AST_SYSTEM_NAME; + } + } + /* if not found, look into chanvars or global vars */ + for (i = 0; s == ¬_found && i < (sizeof(places) / sizeof(places[0])); i++) { + struct ast_var_t *variables; + if (!places[i]) + continue; + if (places[i] == &globals) + ast_mutex_lock(&globalslock); + AST_LIST_TRAVERSE(places[i], variables, entries) { + if (strcasecmp(ast_var_name(variables), var)==0) { + s = ast_var_value(variables); + break; + } + } + if (places[i] == &globals) + ast_mutex_unlock(&globalslock); + } + if (s == ¬_found || s == NULL) + *ret = NULL; + else { + if (s != workspace) + ast_copy_string(workspace, s, workspacelen); + *ret = workspace; + if (need_substring) + *ret = substring(*ret, offset, length, workspace, workspacelen); + } +} + +/*! \brief CLI function to show installed custom functions + \addtogroup CLI_functions + */ +static int handle_show_functions(int fd, int argc, char *argv[]) +{ + struct ast_custom_function *acf; + int count_acf = 0; + int like = 0; + + if (argc == 4 && (!strcmp(argv[2], "like")) ) { + like = 1; + } else if (argc != 2) { + return RESULT_SHOWUSAGE; + } + + ast_cli(fd, "%s Custom Functions:\n--------------------------------------------------------------------------------\n", like ? "Matching" : "Installed"); + + AST_LIST_LOCK(&acf_root); + AST_LIST_TRAVERSE(&acf_root, acf, acflist) { + if (!like || strstr(acf->name, argv[3])) { + count_acf++; + ast_cli(fd, "%-20.20s %-35.35s %s\n", acf->name, acf->syntax, acf->synopsis); + } + } + AST_LIST_UNLOCK(&acf_root); + + ast_cli(fd, "%d %scustom functions installed.\n", count_acf, like ? "matching " : ""); + + return RESULT_SUCCESS; +} + +static int handle_show_function(int fd, int argc, char *argv[]) +{ + struct ast_custom_function *acf; + /* Maximum number of characters added by terminal coloring is 22 */ + char infotitle[64 + AST_MAX_APP + 22], syntitle[40], destitle[40]; + char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL; + char stxtitle[40], *syntax = NULL; + int synopsis_size, description_size, syntax_size; + + if (argc < 3) + return RESULT_SHOWUSAGE; + + if (!(acf = ast_custom_function_find(argv[2]))) { + ast_cli(fd, "No function by that name registered.\n"); + return RESULT_FAILURE; + + } + + if (acf->synopsis) + synopsis_size = strlen(acf->synopsis) + 23; + else + synopsis_size = strlen("Not available") + 23; + synopsis = alloca(synopsis_size); + + if (acf->desc) + description_size = strlen(acf->desc) + 23; + else + description_size = strlen("Not available") + 23; + description = alloca(description_size); + + if (acf->syntax) + syntax_size = strlen(acf->syntax) + 23; + else + syntax_size = strlen("Not available") + 23; + syntax = alloca(syntax_size); + + snprintf(info, 64 + AST_MAX_APP, "\n -= Info about function '%s' =- \n\n", acf->name); + term_color(infotitle, info, COLOR_MAGENTA, 0, 64 + AST_MAX_APP + 22); + term_color(stxtitle, "[Syntax]\n", COLOR_MAGENTA, 0, 40); + term_color(syntitle, "[Synopsis]\n", COLOR_MAGENTA, 0, 40); + term_color(destitle, "[Description]\n", COLOR_MAGENTA, 0, 40); + term_color(syntax, + acf->syntax ? acf->syntax : "Not available", + COLOR_CYAN, 0, syntax_size); + term_color(synopsis, + acf->synopsis ? acf->synopsis : "Not available", + COLOR_CYAN, 0, synopsis_size); + term_color(description, + acf->desc ? acf->desc : "Not available", + COLOR_CYAN, 0, description_size); + + ast_cli(fd,"%s%s%s\n\n%s%s\n\n%s%s\n", infotitle, stxtitle, syntax, syntitle, synopsis, destitle, description); + + return RESULT_SUCCESS; +} + +static char *complete_show_function(const char *line, const char *word, int pos, int state) +{ + struct ast_custom_function *acf; + char *ret = NULL; + int which = 0; + int wordlen = strlen(word); + + /* case-insensitive for convenience in this 'complete' function */ + AST_LIST_LOCK(&acf_root); + AST_LIST_TRAVERSE(&acf_root, acf, acflist) { + if (!strncasecmp(word, acf->name, wordlen) && ++which > state) { + ret = strdup(acf->name); + break; + } + } + AST_LIST_UNLOCK(&acf_root); + + return ret; +} + +struct ast_custom_function *ast_custom_function_find(const char *name) +{ + struct ast_custom_function *acf = NULL; + + AST_LIST_LOCK(&acf_root); + AST_LIST_TRAVERSE(&acf_root, acf, acflist) { + if (!strcmp(name, acf->name)) + break; + } + AST_LIST_UNLOCK(&acf_root); + + return acf; +} + +int ast_custom_function_unregister(struct ast_custom_function *acf) +{ + struct ast_custom_function *cur; + + if (!acf) + return -1; + + AST_LIST_LOCK(&acf_root); + AST_LIST_TRAVERSE_SAFE_BEGIN(&acf_root, cur, acflist) { + if (cur == acf) { + AST_LIST_REMOVE_CURRENT(&acf_root, acflist); + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Unregistered custom function %s\n", acf->name); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + AST_LIST_UNLOCK(&acf_root); + + return acf ? 0 : -1; +} + +int ast_custom_function_register(struct ast_custom_function *acf) +{ + struct ast_custom_function *cur; + + if (!acf) + return -1; + + AST_LIST_LOCK(&acf_root); + + if (ast_custom_function_find(acf->name)) { + ast_log(LOG_ERROR, "Function %s already registered.\n", acf->name); + AST_LIST_UNLOCK(&acf_root); + return -1; + } + + /* Store in alphabetical order */ + AST_LIST_TRAVERSE_SAFE_BEGIN(&acf_root, cur, acflist) { + if (strcasecmp(acf->name, cur->name) < 0) { + AST_LIST_INSERT_BEFORE_CURRENT(&acf_root, acf, acflist); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + if (!cur) + AST_LIST_INSERT_TAIL(&acf_root, acf, acflist); + + AST_LIST_UNLOCK(&acf_root); + + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Registered custom function %s\n", acf->name); + + return 0; +} + +/*! \brief return a pointer to the arguments of the function, + * and terminates the function name with '\\0' + */ +static char *func_args(char *function) +{ + char *args = strchr(function, '('); + + if (!args) + ast_log(LOG_WARNING, "Function doesn't contain parentheses. Assuming null argument.\n"); + else { + char *p; + *args++ = '\0'; + if ((p = strrchr(args, ')')) ) + *p = '\0'; + else + ast_log(LOG_WARNING, "Can't find trailing parenthesis?\n"); + } + return args; +} + +int ast_func_read(struct ast_channel *chan, char *function, char *workspace, size_t len) +{ + char *args = func_args(function); + struct ast_custom_function *acfptr = ast_custom_function_find(function); + + if (acfptr == NULL) + ast_log(LOG_ERROR, "Function %s not registered\n", function); + else if (!acfptr->read) + ast_log(LOG_ERROR, "Function %s cannot be read\n", function); + else + return acfptr->read(chan, function, args, workspace, len); + return -1; +} + +int ast_func_write(struct ast_channel *chan, char *function, const char *value) +{ + char *args = func_args(function); + struct ast_custom_function *acfptr = ast_custom_function_find(function); + + if (acfptr == NULL) + ast_log(LOG_ERROR, "Function %s not registered\n", function); + else if (!acfptr->write) + ast_log(LOG_ERROR, "Function %s cannot be written to\n", function); + else + return acfptr->write(chan, function, args, value); + + return -1; +} + +static void pbx_substitute_variables_helper_full(struct ast_channel *c, struct varshead *headp, const char *cp1, char *cp2, int count) +{ + /* Substitutes variables into cp2, based on string cp1, and assuming cp2 to be + zero-filled */ + char *cp4; + const char *tmp, *whereweare; + int length, offset, offset2, isfunction; + char *workspace = NULL; + char *ltmp = NULL, *var = NULL; + char *nextvar, *nextexp, *nextthing; + char *vars, *vare; + int pos, brackets, needsub, len; + + whereweare=tmp=cp1; + while (!ast_strlen_zero(whereweare) && count) { + /* Assume we're copying the whole remaining string */ + pos = strlen(whereweare); + nextvar = NULL; + nextexp = NULL; + nextthing = strchr(whereweare, '$'); + if (nextthing) { + switch(nextthing[1]) { + case '{': + nextvar = nextthing; + pos = nextvar - whereweare; + break; + case '[': + nextexp = nextthing; + pos = nextexp - whereweare; + break; + } + } + + if (pos) { + /* Can't copy more than 'count' bytes */ + if (pos > count) + pos = count; + + /* Copy that many bytes */ + memcpy(cp2, whereweare, pos); + + count -= pos; + cp2 += pos; + whereweare += pos; + } + + if (nextvar) { + /* We have a variable. Find the start and end, and determine + if we are going to have to recursively call ourselves on the + contents */ + vars = vare = nextvar + 2; + brackets = 1; + needsub = 0; + + /* Find the end of it */ + while (brackets && *vare) { + if ((vare[0] == '$') && (vare[1] == '{')) { + needsub++; + } else if (vare[0] == '{') { + brackets++; + } else if (vare[0] == '}') { + brackets--; + } else if ((vare[0] == '$') && (vare[1] == '[')) + needsub++; + vare++; + } + if (brackets) + ast_log(LOG_NOTICE, "Error in extension logic (missing '}')\n"); + len = vare - vars - 1; + + /* Skip totally over variable string */ + whereweare += (len + 3); + + if (!var) + var = alloca(VAR_BUF_SIZE); + + /* Store variable name (and truncate) */ + ast_copy_string(var, vars, len + 1); + + /* Substitute if necessary */ + if (needsub) { + if (!ltmp) + ltmp = alloca(VAR_BUF_SIZE); + + memset(ltmp, 0, VAR_BUF_SIZE); + pbx_substitute_variables_helper_full(c, headp, var, ltmp, VAR_BUF_SIZE - 1); + vars = ltmp; + } else { + vars = var; + } + + if (!workspace) + workspace = alloca(VAR_BUF_SIZE); + + workspace[0] = '\0'; + + parse_variable_name(vars, &offset, &offset2, &isfunction); + if (isfunction) { + /* Evaluate function */ + cp4 = ast_func_read(c, vars, workspace, VAR_BUF_SIZE) ? NULL : workspace; + + ast_log(LOG_DEBUG, "Function result is '%s'\n", cp4 ? cp4 : "(null)"); + } else { + /* Retrieve variable value */ + pbx_retrieve_variable(c, vars, &cp4, workspace, VAR_BUF_SIZE, headp); + } + if (cp4) { + cp4 = substring(cp4, offset, offset2, workspace, VAR_BUF_SIZE); + + length = strlen(cp4); + if (length > count) + length = count; + memcpy(cp2, cp4, length); + count -= length; + cp2 += length; + } + } else if (nextexp) { + /* We have an expression. Find the start and end, and determine + if we are going to have to recursively call ourselves on the + contents */ + vars = vare = nextexp + 2; + brackets = 1; + needsub = 0; + + /* Find the end of it */ + while(brackets && *vare) { + if ((vare[0] == '$') && (vare[1] == '[')) { + needsub++; + brackets++; + vare++; + } else if (vare[0] == '[') { + brackets++; + } else if (vare[0] == ']') { + brackets--; + } else if ((vare[0] == '$') && (vare[1] == '{')) { + needsub++; + vare++; + } + vare++; + } + if (brackets) + ast_log(LOG_NOTICE, "Error in extension logic (missing ']')\n"); + len = vare - vars - 1; + + /* Skip totally over expression */ + whereweare += (len + 3); + + if (!var) + var = alloca(VAR_BUF_SIZE); + + /* Store variable name (and truncate) */ + ast_copy_string(var, vars, len + 1); + + /* Substitute if necessary */ + if (needsub) { + if (!ltmp) + ltmp = alloca(VAR_BUF_SIZE); + + memset(ltmp, 0, VAR_BUF_SIZE); + pbx_substitute_variables_helper_full(c, headp, var, ltmp, VAR_BUF_SIZE - 1); + vars = ltmp; + } else { + vars = var; + } + + length = ast_expr(vars, cp2, count); + + if (length) { + ast_log(LOG_DEBUG, "Expression result is '%s'\n", cp2); + count -= length; + cp2 += length; + } + } else + break; + } +} + +void pbx_substitute_variables_helper(struct ast_channel *c, const char *cp1, char *cp2, int count) +{ + pbx_substitute_variables_helper_full(c, (c) ? &c->varshead : NULL, cp1, cp2, count); +} + +void pbx_substitute_variables_varshead(struct varshead *headp, const char *cp1, char *cp2, int count) +{ + pbx_substitute_variables_helper_full(NULL, headp, cp1, cp2, count); +} + +static void pbx_substitute_variables(char *passdata, int datalen, struct ast_channel *c, struct ast_exten *e) +{ + memset(passdata, 0, datalen); + + /* No variables or expressions in e->data, so why scan it? */ + if (!strchr(e->data, '$') && !strstr(e->data,"${") && !strstr(e->data,"$[") && !strstr(e->data,"$(")) { + ast_copy_string(passdata, e->data, datalen); + return; + } + + pbx_substitute_variables_helper(c, e->data, passdata, datalen - 1); +} + +/*! \brief The return value depends on the action: + * + * E_MATCH, E_CANMATCH, E_MATCHMORE require a real match, + * and return 0 on failure, -1 on match; + * E_FINDLABEL maps the label to a priority, and returns + * the priority on success, ... XXX + * E_SPAWN, spawn an application, + * and return 0 on success, -1 on failure. + */ +static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con, + const char *context, const char *exten, int priority, + const char *label, const char *callerid, enum ext_match_t action) +{ + struct ast_exten *e; + struct ast_app *app; + int res; + struct pbx_find_info q = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */ + char passdata[EXT_DATA_SIZE]; + + int matching_action = (action == E_MATCH || action == E_CANMATCH || action == E_MATCHMORE); + + ast_mutex_lock(&conlock); + e = pbx_find_extension(c, con, &q, context, exten, priority, label, callerid, action); + if (e) { + if (matching_action) { + ast_mutex_unlock(&conlock); + return -1; /* success, we found it */ + } else if (action == E_FINDLABEL) { /* map the label to a priority */ + res = e->priority; + ast_mutex_unlock(&conlock); + return res; /* the priority we were looking for */ + } else { /* spawn */ + app = pbx_findapp(e->app); + ast_mutex_unlock(&conlock); + if (!app) { + ast_log(LOG_WARNING, "No application '%s' for extension (%s, %s, %d)\n", e->app, context, exten, priority); + return -1; + } + if (c->context != context) + ast_copy_string(c->context, context, sizeof(c->context)); + if (c->exten != exten) + ast_copy_string(c->exten, exten, sizeof(c->exten)); + c->priority = priority; + pbx_substitute_variables(passdata, sizeof(passdata), c, e); + if (option_debug) { + char atmp[80]; + char atmp2[EXT_DATA_SIZE+100]; + ast_log(LOG_DEBUG, "Launching '%s'\n", app->name); + snprintf(atmp, sizeof(atmp), "STACK-%s-%s-%d", context, exten, priority); + snprintf(atmp2, sizeof(atmp2), "%s(\"%s\", \"%s\") %s", + app->name, c->name, passdata, "in new stack"); + pbx_builtin_setvar_helper(c, atmp, atmp2); + } + if (option_verbose > 2) { + char tmp[80], tmp2[80], tmp3[EXT_DATA_SIZE]; + ast_verbose( VERBOSE_PREFIX_3 "Executing [%s@%s:%d] %s(\"%s\", \"%s\") %s\n", + exten, context, priority, + term_color(tmp, app->name, COLOR_BRCYAN, 0, sizeof(tmp)), + term_color(tmp2, c->name, COLOR_BRMAGENTA, 0, sizeof(tmp2)), + term_color(tmp3, passdata, COLOR_BRMAGENTA, 0, sizeof(tmp3)), + "in new stack"); + } + manager_event(EVENT_FLAG_CALL, "Newexten", + "Channel: %s\r\n" + "Context: %s\r\n" + "Extension: %s\r\n" + "Priority: %d\r\n" + "Application: %s\r\n" + "AppData: %s\r\n" + "Uniqueid: %s\r\n", + c->name, c->context, c->exten, c->priority, app->name, passdata, c->uniqueid); + return pbx_exec(c, app, passdata); /* 0 on success, -1 on failure */ + } + } else if (q.swo) { /* not found here, but in another switch */ + ast_mutex_unlock(&conlock); + if (matching_action) + return -1; + else { + if (!q.swo->exec) { + ast_log(LOG_WARNING, "No execution engine for switch %s\n", q.swo->name); + res = -1; + } + return q.swo->exec(c, q.foundcontext ? q.foundcontext : context, exten, priority, callerid, q.data); + } + } else { /* not found anywhere, see what happened */ + ast_mutex_unlock(&conlock); + switch (q.status) { + case STATUS_NO_CONTEXT: + if (!matching_action) + ast_log(LOG_NOTICE, "Cannot find extension context '%s'\n", context); + break; + case STATUS_NO_EXTENSION: + if (!matching_action) + ast_log(LOG_NOTICE, "Cannot find extension '%s' in context '%s'\n", exten, context); + break; + case STATUS_NO_PRIORITY: + if (!matching_action) + ast_log(LOG_NOTICE, "No such priority %d in extension '%s' in context '%s'\n", priority, exten, context); + break; + case STATUS_NO_LABEL: + if (context) + ast_log(LOG_NOTICE, "No such label '%s' in extension '%s' in context '%s'\n", label, exten, context); + break; + default: + ast_log(LOG_DEBUG, "Shouldn't happen!\n"); + } + + return (matching_action) ? 0 : -1; + } +} + +/*! \brief ast_hint_extension: Find hint for given extension in context */ +static struct ast_exten *ast_hint_extension(struct ast_channel *c, const char *context, const char *exten) +{ + struct ast_exten *e; + struct pbx_find_info q = { .stacklen = 0 }; /* the rest is set in pbx_find_context */ + + ast_mutex_lock(&conlock); + e = pbx_find_extension(c, NULL, &q, context, exten, PRIORITY_HINT, NULL, "", E_MATCH); + ast_mutex_unlock(&conlock); + + return e; +} + +/*! \brief ast_extensions_state2: Check state of extension by using hints */ +static int ast_extension_state2(struct ast_exten *e) +{ + char hint[AST_MAX_EXTENSION]; + char *cur, *rest; + int allunavailable = 1, allbusy = 1, allfree = 1, allonhold = 1; + int busy = 0, inuse = 0, ring = 0; + + if (!e) + return -1; + + ast_copy_string(hint, ast_get_extension_app(e), sizeof(hint)); + + rest = hint; /* One or more devices separated with a & character */ + while ( (cur = strsep(&rest, "&")) ) { + int res = ast_device_state(cur); + switch (res) { + case AST_DEVICE_NOT_INUSE: + allunavailable = 0; + allbusy = 0; + allonhold = 0; + break; + case AST_DEVICE_INUSE: + inuse = 1; + allunavailable = 0; + allfree = 0; + allonhold = 0; + break; + case AST_DEVICE_RINGING: + ring = 1; + allunavailable = 0; + allfree = 0; + allonhold = 0; + break; + case AST_DEVICE_RINGINUSE: + inuse = 1; + ring = 1; + allunavailable = 0; + allfree = 0; + allonhold = 0; + break; + case AST_DEVICE_ONHOLD: + allunavailable = 0; + allfree = 0; + break; + case AST_DEVICE_BUSY: + allunavailable = 0; + allfree = 0; + allonhold = 0; + busy = 1; + break; + case AST_DEVICE_UNAVAILABLE: + case AST_DEVICE_INVALID: + allbusy = 0; + allfree = 0; + allonhold = 0; + break; + default: + allunavailable = 0; + allbusy = 0; + allfree = 0; + allonhold = 0; + } + } + + if (!inuse && ring) + return AST_EXTENSION_RINGING; + if (inuse && ring) + return (AST_EXTENSION_INUSE | AST_EXTENSION_RINGING); + if (inuse) + return AST_EXTENSION_INUSE; + if (allfree) + return AST_EXTENSION_NOT_INUSE; + if (allonhold) + return AST_EXTENSION_ONHOLD; + if (allbusy) + return AST_EXTENSION_BUSY; + if (allunavailable) + return AST_EXTENSION_UNAVAILABLE; + if (busy) + return AST_EXTENSION_INUSE; + + return AST_EXTENSION_NOT_INUSE; +} + +/*! \brief ast_extension_state2str: Return extension_state as string */ +const char *ast_extension_state2str(int extension_state) +{ + int i; + + for (i = 0; (i < (sizeof(extension_states) / sizeof(extension_states[0]))); i++) { + if (extension_states[i].extension_state == extension_state) + return extension_states[i].text; + } + return "Unknown"; +} + +/*! \brief ast_extension_state: Check extension state for an extension by using hint */ +int ast_extension_state(struct ast_channel *c, const char *context, const char *exten) +{ + struct ast_exten *e; + + e = ast_hint_extension(c, context, exten); /* Do we have a hint for this extension ? */ + if (!e) + return -1; /* No hint, return -1 */ + + return ast_extension_state2(e); /* Check all devices in the hint */ +} + +void ast_hint_state_changed(const char *device) +{ + struct ast_hint *hint; + + AST_LIST_LOCK(&hints); + + AST_LIST_TRAVERSE(&hints, hint, list) { + struct ast_state_cb *cblist; + char buf[AST_MAX_EXTENSION]; + char *parse = buf; + char *cur; + int state; + + ast_copy_string(buf, ast_get_extension_app(hint->exten), sizeof(buf)); + while ( (cur = strsep(&parse, "&")) ) { + if (!strcasecmp(cur, device)) + break; + } + if (!cur) + continue; + + /* Get device state for this hint */ + state = ast_extension_state2(hint->exten); + + if ((state == -1) || (state == hint->laststate)) + continue; + + /* Device state changed since last check - notify the watchers */ + + /* For general callbacks */ + for (cblist = statecbs; cblist; cblist = cblist->next) + cblist->callback(hint->exten->parent->name, hint->exten->exten, state, cblist->data); + + /* For extension callbacks */ + for (cblist = hint->callbacks; cblist; cblist = cblist->next) + cblist->callback(hint->exten->parent->name, hint->exten->exten, state, cblist->data); + + hint->laststate = state; /* record we saw the change */ + } + + AST_LIST_UNLOCK(&hints); +} + +/*! \brief ast_extension_state_add: Add watcher for extension states */ +int ast_extension_state_add(const char *context, const char *exten, + ast_state_cb_type callback, void *data) +{ + struct ast_hint *hint; + struct ast_state_cb *cblist; + struct ast_exten *e; + + /* If there's no context and extension: add callback to statecbs list */ + if (!context && !exten) { + AST_LIST_LOCK(&hints); + + for (cblist = statecbs; cblist; cblist = cblist->next) { + if (cblist->callback == callback) { + cblist->data = data; + AST_LIST_UNLOCK(&hints); + return 0; + } + } + + /* Now insert the callback */ + if (!(cblist = ast_calloc(1, sizeof(*cblist)))) { + AST_LIST_UNLOCK(&hints); + return -1; + } + cblist->id = 0; + cblist->callback = callback; + cblist->data = data; + + cblist->next = statecbs; + statecbs = cblist; + + AST_LIST_UNLOCK(&hints); + return 0; + } + + if (!context || !exten) + return -1; + + /* This callback type is for only one hint, so get the hint */ + e = ast_hint_extension(NULL, context, exten); + if (!e) { + return -1; + } + + /* Find the hint in the list of hints */ + AST_LIST_LOCK(&hints); + + AST_LIST_TRAVERSE(&hints, hint, list) { + if (hint->exten == e) + break; + } + + if (!hint) { + /* We have no hint, sorry */ + AST_LIST_UNLOCK(&hints); + return -1; + } + + /* Now insert the callback in the callback list */ + if (!(cblist = ast_calloc(1, sizeof(*cblist)))) { + AST_LIST_UNLOCK(&hints); + return -1; + } + cblist->id = stateid++; /* Unique ID for this callback */ + cblist->callback = callback; /* Pointer to callback routine */ + cblist->data = data; /* Data for the callback */ + + cblist->next = hint->callbacks; + hint->callbacks = cblist; + + AST_LIST_UNLOCK(&hints); + return cblist->id; +} + +/*! \brief ast_extension_state_del: Remove a watcher from the callback list */ +int ast_extension_state_del(int id, ast_state_cb_type callback) +{ + struct ast_state_cb **p_cur = NULL; /* address of pointer to us */ + int ret = -1; + + if (!id && !callback) + return -1; + + AST_LIST_LOCK(&hints); + + if (!id) { /* id == 0 is a callback without extension */ + for (p_cur = &statecbs; *p_cur; p_cur = &(*p_cur)->next) { + if ((*p_cur)->callback == callback) + break; + } + } else { /* callback with extension, find the callback based on ID */ + struct ast_hint *hint; + AST_LIST_TRAVERSE(&hints, hint, list) { + for (p_cur = &hint->callbacks; *p_cur; p_cur = &(*p_cur)->next) { + if ((*p_cur)->id == id) + break; + } + if (*p_cur) /* found in the inner loop */ + break; + } + } + if (p_cur && *p_cur) { + struct ast_state_cb *cur = *p_cur; + *p_cur = cur->next; + free(cur); + ret = 0; + } + AST_LIST_UNLOCK(&hints); + return ret; +} + +/*! \brief ast_add_hint: Add hint to hint list, check initial extension state */ +static int ast_add_hint(struct ast_exten *e) +{ + struct ast_hint *hint; + + if (!e) + return -1; + + AST_LIST_LOCK(&hints); + + /* Search if hint exists, do nothing */ + AST_LIST_TRAVERSE(&hints, hint, list) { + if (hint->exten == e) { + AST_LIST_UNLOCK(&hints); + if (option_debug > 1) + ast_log(LOG_DEBUG, "HINTS: Not re-adding existing hint %s: %s\n", ast_get_extension_name(e), ast_get_extension_app(e)); + return -1; + } + } + + if (option_debug > 1) + ast_log(LOG_DEBUG, "HINTS: Adding hint %s: %s\n", ast_get_extension_name(e), ast_get_extension_app(e)); + + if (!(hint = ast_calloc(1, sizeof(*hint)))) { + AST_LIST_UNLOCK(&hints); + return -1; + } + /* Initialize and insert new item at the top */ + hint->exten = e; + hint->laststate = ast_extension_state2(e); + AST_LIST_INSERT_HEAD(&hints, hint, list); + + AST_LIST_UNLOCK(&hints); + return 0; +} + +/*! \brief ast_change_hint: Change hint for an extension */ +static int ast_change_hint(struct ast_exten *oe, struct ast_exten *ne) +{ + struct ast_hint *hint; + int res = -1; + + AST_LIST_LOCK(&hints); + AST_LIST_TRAVERSE(&hints, hint, list) { + if (hint->exten == oe) { + hint->exten = ne; + res = 0; + break; + } + } + AST_LIST_UNLOCK(&hints); + + return res; +} + +/*! \brief ast_remove_hint: Remove hint from extension */ +static int ast_remove_hint(struct ast_exten *e) +{ + /* Cleanup the Notifys if hint is removed */ + struct ast_hint *hint; + struct ast_state_cb *cblist, *cbprev; + int res = -1; + + if (!e) + return -1; + + AST_LIST_LOCK(&hints); + AST_LIST_TRAVERSE_SAFE_BEGIN(&hints, hint, list) { + if (hint->exten == e) { + cbprev = NULL; + cblist = hint->callbacks; + while (cblist) { + /* Notify with -1 and remove all callbacks */ + cbprev = cblist; + cblist = cblist->next; + cbprev->callback(hint->exten->parent->name, hint->exten->exten, AST_EXTENSION_DEACTIVATED, cbprev->data); + free(cbprev); + } + hint->callbacks = NULL; + AST_LIST_REMOVE_CURRENT(&hints, list); + free(hint); + res = 0; + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + AST_LIST_UNLOCK(&hints); + + return res; +} + + +/*! \brief ast_get_hint: Get hint for channel */ +int ast_get_hint(char *hint, int hintsize, char *name, int namesize, struct ast_channel *c, const char *context, const char *exten) +{ + struct ast_exten *e = ast_hint_extension(c, context, exten); + + if (e) { + if (hint) + ast_copy_string(hint, ast_get_extension_app(e), hintsize); + if (name) { + const char *tmp = ast_get_extension_app_data(e); + if (tmp) + ast_copy_string(name, tmp, namesize); + } + return -1; + } + return 0; +} + +int ast_exists_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid) +{ + return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_MATCH); +} + +int ast_findlabel_extension(struct ast_channel *c, const char *context, const char *exten, const char *label, const char *callerid) +{ + return pbx_extension_helper(c, NULL, context, exten, 0, label, callerid, E_FINDLABEL); +} + +int ast_findlabel_extension2(struct ast_channel *c, struct ast_context *con, const char *exten, const char *label, const char *callerid) +{ + return pbx_extension_helper(c, con, NULL, exten, 0, label, callerid, E_FINDLABEL); +} + +int ast_canmatch_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid) +{ + return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_CANMATCH); +} + +int ast_matchmore_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid) +{ + return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_MATCHMORE); +} + +int ast_spawn_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid) +{ + return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_SPAWN); +} + +/* helper function to set extension and priority */ +static void set_ext_pri(struct ast_channel *c, const char *exten, int pri) +{ + ast_copy_string(c->exten, exten, sizeof(c->exten)); + c->priority = pri; +} + +/*! + * \brief collect digits from the channel into the buffer, + * return -1 on error, 0 on timeout or done. + */ +static int collect_digits(struct ast_channel *c, int waittime, char *buf, int buflen, int pos) +{ + int digit; + + buf[pos] = '\0'; /* make sure it is properly terminated */ + while (ast_matchmore_extension(c, c->context, buf, 1, c->cid.cid_num)) { + /* As long as we're willing to wait, and as long as it's not defined, + keep reading digits until we can't possibly get a right answer anymore. */ + digit = ast_waitfordigit(c, waittime * 1000); + if (c->_softhangup == AST_SOFTHANGUP_ASYNCGOTO) { + c->_softhangup = 0; + } else { + if (!digit) /* No entry */ + break; + if (digit < 0) /* Error, maybe a hangup */ + return -1; + if (pos < buflen - 1) { /* XXX maybe error otherwise ? */ + buf[pos++] = digit; + buf[pos] = '\0'; + } + waittime = c->pbx->dtimeout; + } + } + return 0; +} + +static int __ast_pbx_run(struct ast_channel *c) +{ + int found = 0; /* set if we find at least one match */ + int res = 0; + int autoloopflag; + int error = 0; /* set an error conditions */ + + /* A little initial setup here */ + if (c->pbx) { + ast_log(LOG_WARNING, "%s already has PBX structure??\n", c->name); + /* XXX and now what ? */ + free(c->pbx); + } + if (!(c->pbx = ast_calloc(1, sizeof(*c->pbx)))) + return -1; + if (c->amaflags) { + if (!c->cdr) { + c->cdr = ast_cdr_alloc(); + if (!c->cdr) { + ast_log(LOG_WARNING, "Unable to create Call Detail Record\n"); + free(c->pbx); + return -1; + } + ast_cdr_init(c->cdr, c); + } + } + /* Set reasonable defaults */ + c->pbx->rtimeout = 10; + c->pbx->dtimeout = 5; + + autoloopflag = ast_test_flag(c, AST_FLAG_IN_AUTOLOOP); /* save value to restore at the end */ + ast_set_flag(c, AST_FLAG_IN_AUTOLOOP); + + /* Start by trying whatever the channel is set to */ + if (!ast_exists_extension(c, c->context, c->exten, c->priority, c->cid.cid_num)) { + /* If not successful fall back to 's' */ + if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Starting %s at %s,%s,%d failed so falling back to exten 's'\n", c->name, c->context, c->exten, c->priority); + /* XXX the original code used the existing priority in the call to + * ast_exists_extension(), and reset it to 1 afterwards. + * I believe the correct thing is to set it to 1 immediately. + */ + set_ext_pri(c, "s", 1); + if (!ast_exists_extension(c, c->context, c->exten, c->priority, c->cid.cid_num)) { + /* JK02: And finally back to default if everything else failed */ + if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Starting %s at %s,%s,%d still failed so falling back to context 'default'\n", c->name, c->context, c->exten, c->priority); + ast_copy_string(c->context, "default", sizeof(c->context)); + } + } + if (c->cdr && ast_tvzero(c->cdr->start)) + ast_cdr_start(c->cdr); + for (;;) { + char dst_exten[256]; /* buffer to accumulate digits */ + int pos = 0; /* XXX should check bounds */ + int digit = 0; + + /* loop on priorities in this context/exten */ + while (ast_exists_extension(c, c->context, c->exten, c->priority, c->cid.cid_num)) { + found = 1; + if ((res = ast_spawn_extension(c, c->context, c->exten, c->priority, c->cid.cid_num))) { + /* Something bad happened, or a hangup has been requested. */ + if (strchr("0123456789ABCDEF*#", res)) { + ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res); + pos = 0; + dst_exten[pos++] = digit = res; + dst_exten[pos] = '\0'; + break; + } + if (res == AST_PBX_KEEPALIVE) { + if (option_debug) + ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited KEEPALIVE on '%s'\n", c->context, c->exten, c->priority, c->name); + else if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited KEEPALIVE on '%s'\n", c->context, c->exten, c->priority, c->name); + error = 1; + break; + } + if (option_debug) + ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited non-zero on '%s'\n", c->context, c->exten, c->priority, c->name); + else if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n", c->context, c->exten, c->priority, c->name); + if (c->_softhangup == AST_SOFTHANGUP_ASYNCGOTO) { + c->_softhangup =0; + } else if (c->_softhangup == AST_SOFTHANGUP_TIMEOUT) { + /* atimeout, nothing bad */ + } else { + if (c->cdr) + ast_cdr_update(c); + error = 1; + break; + } + } + if (c->_softhangup == AST_SOFTHANGUP_TIMEOUT && ast_exists_extension(c,c->context,"T",1,c->cid.cid_num)) { + set_ext_pri(c, "T", 0); /* 0 will become 1 with the c->priority++; at the end */ + /* If the AbsoluteTimeout is not reset to 0, we'll get an infinite loop */ + c->whentohangup = 0; + c->_softhangup &= ~AST_SOFTHANGUP_TIMEOUT; + } else if (c->_softhangup) { + ast_log(LOG_DEBUG, "Extension %s, priority %d returned normally even though call was hung up\n", + c->exten, c->priority); + error = 1; + break; + } + c->priority++; + } /* end while - from here on we can use 'break' to go out */ + if (error) + break; + + /* XXX we get here on non-existing extension or a keypress or hangup ? */ + + if (!ast_exists_extension(c, c->context, c->exten, 1, c->cid.cid_num)) { + /* If there is no match at priority 1, it is not a valid extension anymore. + * Try to continue at "i", 1 or exit if the latter does not exist. + */ + if (ast_exists_extension(c, c->context, "i", 1, c->cid.cid_num)) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Sent into invalid extension '%s' in context '%s' on %s\n", c->exten, c->context, c->name); + pbx_builtin_setvar_helper(c, "INVALID_EXTEN", c->exten); + set_ext_pri(c, "i", 1); + } else { + ast_log(LOG_WARNING, "Channel '%s' sent into invalid extension '%s' in context '%s', but no invalid handler\n", + c->name, c->exten, c->context); + error = 1; /* we know what to do with it */ + break; + } + } else if (c->_softhangup == AST_SOFTHANGUP_TIMEOUT) { + /* If we get this far with AST_SOFTHANGUP_TIMEOUT, then we know that the "T" extension is next. */ + c->_softhangup = 0; + } else { /* keypress received, get more digits for a full extension */ + int waittime = 0; + if (digit) + waittime = c->pbx->dtimeout; + else if (!autofallthrough) + waittime = c->pbx->rtimeout; + if (!waittime) { + const char *status = pbx_builtin_getvar_helper(c, "DIALSTATUS"); + if (!status) + status = "UNKNOWN"; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_2 "Auto fallthrough, channel '%s' status is '%s'\n", c->name, status); + if (!strcasecmp(status, "CONGESTION")) + res = pbx_builtin_congestion(c, "10"); + else if (!strcasecmp(status, "CHANUNAVAIL")) + res = pbx_builtin_congestion(c, "10"); + else if (!strcasecmp(status, "BUSY")) + res = pbx_builtin_busy(c, "10"); + error = 1; /* XXX disable message */ + break; /* exit from the 'for' loop */ + } + + if (collect_digits(c, waittime, dst_exten, sizeof(dst_exten), pos)) + break; + if (ast_exists_extension(c, c->context, dst_exten, 1, c->cid.cid_num)) /* Prepare the next cycle */ + set_ext_pri(c, dst_exten, 1); + else { + /* No such extension */ + if (!ast_strlen_zero(dst_exten)) { + /* An invalid extension */ + if (ast_exists_extension(c, c->context, "i", 1, c->cid.cid_num)) { + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Invalid extension '%s' in context '%s' on %s\n", dst_exten, c->context, c->name); + pbx_builtin_setvar_helper(c, "INVALID_EXTEN", dst_exten); + set_ext_pri(c, "i", 1); + } else { + ast_log(LOG_WARNING, "Invalid extension '%s', but no rule 'i' in context '%s'\n", dst_exten, c->context); + found = 1; /* XXX disable message */ + break; + } + } else { + /* A simple timeout */ + if (ast_exists_extension(c, c->context, "t", 1, c->cid.cid_num)) { + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Timeout on %s\n", c->name); + set_ext_pri(c, "t", 1); + } else { + ast_log(LOG_WARNING, "Timeout, but no rule 't' in context '%s'\n", c->context); + found = 1; /* XXX disable message */ + break; + } + } + } + if (c->cdr) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_2 "CDR updated on %s\n",c->name); + ast_cdr_update(c); + } + } + } + if (!found && !error) + ast_log(LOG_WARNING, "Don't know what to do with '%s'\n", c->name); + if ((res != AST_PBX_KEEPALIVE) && ast_exists_extension(c, c->context, "h", 1, c->cid.cid_num)) { + if (c->cdr && ast_opt_end_cdr_before_h_exten) + ast_cdr_end(c->cdr); + set_ext_pri(c, "h", 1); + while(ast_exists_extension(c, c->context, c->exten, c->priority, c->cid.cid_num)) { + if ((res = ast_spawn_extension(c, c->context, c->exten, c->priority, c->cid.cid_num))) { + /* Something bad happened, or a hangup has been requested. */ + if (option_debug) + ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited non-zero on '%s'\n", c->context, c->exten, c->priority, c->name); + else if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n", c->context, c->exten, c->priority, c->name); + break; + } + c->priority++; + } + } + ast_set2_flag(c, autoloopflag, AST_FLAG_IN_AUTOLOOP); + + pbx_destroy(c->pbx); + c->pbx = NULL; + if (res != AST_PBX_KEEPALIVE) + ast_hangup(c); + return 0; +} + +/* Returns 0 on success, non-zero if call limit was reached */ +static int increase_call_count(const struct ast_channel *c) +{ + int failed = 0; + double curloadavg; + ast_mutex_lock(&maxcalllock); + if (option_maxcalls) { + if (countcalls >= option_maxcalls) { + ast_log(LOG_NOTICE, "Maximum call limit of %d calls exceeded by '%s'!\n", option_maxcalls, c->name); + failed = -1; + } + } + if (option_maxload) { + getloadavg(&curloadavg, 1); + if (curloadavg >= option_maxload) { + ast_log(LOG_NOTICE, "Maximum loadavg limit of %f load exceeded by '%s' (currently %f)!\n", option_maxload, c->name, curloadavg); + failed = -1; + } + } + if (!failed) + countcalls++; + ast_mutex_unlock(&maxcalllock); + + return failed; +} + +static void decrease_call_count(void) +{ + ast_mutex_lock(&maxcalllock); + if (countcalls > 0) + countcalls--; + ast_mutex_unlock(&maxcalllock); +} + +static void destroy_exten(struct ast_exten *e) +{ + if (e->priority == PRIORITY_HINT) + ast_remove_hint(e); + + if (e->datad) + e->datad(e->data); + free(e); +} + +static void *pbx_thread(void *data) +{ + /* Oh joyeous kernel, we're a new thread, with nothing to do but + answer this channel and get it going. + */ + /* NOTE: + The launcher of this function _MUST_ increment 'countcalls' + before invoking the function; it will be decremented when the + PBX has finished running on the channel + */ + struct ast_channel *c = data; + + __ast_pbx_run(c); + decrease_call_count(); + + pthread_exit(NULL); + + return NULL; +} + +enum ast_pbx_result ast_pbx_start(struct ast_channel *c) +{ + pthread_t t; + pthread_attr_t attr; + + if (!c) { + ast_log(LOG_WARNING, "Asked to start thread on NULL channel?\n"); + return AST_PBX_FAILED; + } + + if (increase_call_count(c)) + return AST_PBX_CALL_LIMIT; + + /* Start a new thread, and get something handling this channel. */ + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (ast_pthread_create(&t, &attr, pbx_thread, c)) { + ast_log(LOG_WARNING, "Failed to create new channel thread\n"); + return AST_PBX_FAILED; + } + + return AST_PBX_SUCCESS; +} + +enum ast_pbx_result ast_pbx_run(struct ast_channel *c) +{ + enum ast_pbx_result res = AST_PBX_SUCCESS; + + if (increase_call_count(c)) + return AST_PBX_CALL_LIMIT; + + res = __ast_pbx_run(c); + decrease_call_count(); + + return res; +} + +int ast_active_calls(void) +{ + return countcalls; +} + +int pbx_set_autofallthrough(int newval) +{ + int oldval = autofallthrough; + autofallthrough = newval; + return oldval; +} + +/* lookup for a context with a given name, + * return with conlock held if found, NULL if not found + */ +static struct ast_context *find_context_locked(const char *context) +{ + struct ast_context *c = NULL; + + ast_lock_contexts(); + while ( (c = ast_walk_contexts(c)) ) { + if (!strcmp(ast_get_context_name(c), context)) + return c; + } + ast_unlock_contexts(); + + return NULL; +} + +/* + * This function locks contexts list by &conlist, search for the right context + * structure, leave context list locked and call ast_context_remove_include2 + * which removes include, unlock contexts list and return ... + */ +int ast_context_remove_include(const char *context, const char *include, const char *registrar) +{ + int ret = -1; + struct ast_context *c = find_context_locked(context); + + if (c) { + /* found, remove include from this context ... */ + ret = ast_context_remove_include2(c, include, registrar); + ast_unlock_contexts(); + } + return ret; +} + +/* + * When we call this function, &conlock lock must be locked, because when + * we giving *con argument, some process can remove/change this context + * and after that there can be segfault. + * + * This function locks given context, removes include, unlock context and + * return. + */ +int ast_context_remove_include2(struct ast_context *con, const char *include, const char *registrar) +{ + struct ast_include *i, *pi = NULL; + int ret = -1; + + ast_mutex_lock(&con->lock); + + /* find our include */ + for (i = con->includes; i; pi = i, i = i->next) { + if (!strcmp(i->name, include) && + (!registrar || !strcmp(i->registrar, registrar))) { + /* remove from list */ + if (pi) + pi->next = i->next; + else + con->includes = i->next; + /* free include and return */ + free(i); + ret = 0; + break; + } + } + + ast_mutex_unlock(&con->lock); + return ret; +} + +/*! + * \note This function locks contexts list by &conlist, search for the rigt context + * structure, leave context list locked and call ast_context_remove_switch2 + * which removes switch, unlock contexts list and return ... + */ +int ast_context_remove_switch(const char *context, const char *sw, const char *data, const char *registrar) +{ + int ret = -1; /* default error return */ + struct ast_context *c = find_context_locked(context); + + if (c) { + /* remove switch from this context ... */ + ret = ast_context_remove_switch2(c, sw, data, registrar); + ast_unlock_contexts(); + } + return ret; +} + +/*! + * \brief This function locks given context, removes switch, unlock context and + * return. + * \note When we call this function, &conlock lock must be locked, because when + * we giving *con argument, some process can remove/change this context + * and after that there can be segfault. + * + */ +int ast_context_remove_switch2(struct ast_context *con, const char *sw, const char *data, const char *registrar) +{ + struct ast_sw *i; + int ret = -1; + + ast_mutex_lock(&con->lock); + + /* walk switches */ + AST_LIST_TRAVERSE_SAFE_BEGIN(&con->alts, i, list) { + if (!strcmp(i->name, sw) && !strcmp(i->data, data) && + (!registrar || !strcmp(i->registrar, registrar))) { + /* found, remove from list */ + AST_LIST_REMOVE_CURRENT(&con->alts, list); + free(i); /* free switch and return */ + ret = 0; + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + + ast_mutex_unlock(&con->lock); + + return ret; +} + +/* + * \note This functions lock contexts list, search for the right context, + * call ast_context_remove_extension2, unlock contexts list and return. + * In this function we are using + */ +int ast_context_remove_extension(const char *context, const char *extension, int priority, const char *registrar) +{ + int ret = -1; /* default error return */ + struct ast_context *c = find_context_locked(context); + + if (c) { /* ... remove extension ... */ + ret = ast_context_remove_extension2(c, extension, priority, registrar); + ast_unlock_contexts(); + } + return ret; +} + +/*! + * \brief This functionc locks given context, search for the right extension and + * fires out all peer in this extensions with given priority. If priority + * is set to 0, all peers are removed. After that, unlock context and + * return. + * \note When do you want to call this function, make sure that &conlock is locked, + * because some process can handle with your *con context before you lock + * it. + * + */ +int ast_context_remove_extension2(struct ast_context *con, const char *extension, int priority, const char *registrar) +{ + struct ast_exten *exten, *prev_exten = NULL; + struct ast_exten *peer; + + ast_mutex_lock(&con->lock); + + /* scan the extension list to find matching extension-registrar */ + for (exten = con->root; exten; prev_exten = exten, exten = exten->next) { + if (!strcmp(exten->exten, extension) && + (!registrar || !strcmp(exten->registrar, registrar))) + break; + } + if (!exten) { + /* we can't find right extension */ + ast_mutex_unlock(&con->lock); + return -1; + } + + /* should we free all peers in this extension? (priority == 0)? */ + if (priority == 0) { + /* remove this extension from context list */ + if (prev_exten) + prev_exten->next = exten->next; + else + con->root = exten->next; + + /* fire out all peers */ + while ( (peer = exten) ) { + exten = peer->peer; /* prepare for next entry */ + destroy_exten(peer); + } + } else { + /* scan the priority list to remove extension with exten->priority == priority */ + struct ast_exten *previous_peer = NULL; + + for (peer = exten; peer; previous_peer = peer, peer = peer->peer) { + if (peer->priority == priority && + (!registrar || !strcmp(peer->registrar, registrar) )) + break; /* found our priority */ + } + if (!peer) { /* not found */ + ast_mutex_unlock(&con->lock); + return -1; + } + /* we are first priority extension? */ + if (!previous_peer) { + /* + * We are first in the priority chain, so must update the extension chain. + * The next node is either the next priority or the next extension + */ + struct ast_exten *next_node = peer->peer ? peer->peer : peer->next; + + if (!prev_exten) /* change the root... */ + con->root = next_node; + else + prev_exten->next = next_node; /* unlink */ + if (peer->peer) /* XXX update the new head of the pri list */ + peer->peer->next = peer->next; + } else { /* easy, we are not first priority in extension */ + previous_peer->peer = peer->peer; + } + + /* now, free whole priority extension */ + destroy_exten(peer); + /* XXX should we return -1 ? */ + } + ast_mutex_unlock(&con->lock); + return 0; +} + + +/*! + * \note This function locks contexts list by &conlist, searches for the right context + * structure, and locks the macrolock mutex in that context. + * macrolock is used to limit a macro to be executed by one call at a time. + */ +int ast_context_lockmacro(const char *context) +{ + struct ast_context *c = NULL; + int ret = -1; + + ast_lock_contexts(); + + while ((c = ast_walk_contexts(c))) { + if (!strcmp(ast_get_context_name(c), context)) { + ret = 0; + break; + } + } + + ast_unlock_contexts(); + + /* if we found context, lock macrolock */ + if (ret == 0) + ret = ast_mutex_lock(&c->macrolock); + + return ret; +} + +/*! + * \note This function locks contexts list by &conlist, searches for the right context + * structure, and unlocks the macrolock mutex in that context. + * macrolock is used to limit a macro to be executed by one call at a time. + */ +int ast_context_unlockmacro(const char *context) +{ + struct ast_context *c = NULL; + int ret = -1; + + ast_lock_contexts(); + + while ((c = ast_walk_contexts(c))) { + if (!strcmp(ast_get_context_name(c), context)) { + ret = 0; + break; + } + } + + ast_unlock_contexts(); + + /* if we found context, unlock macrolock */ + if (ret == 0) + ret = ast_mutex_unlock(&c->macrolock); + + return ret; +} + +/*! \brief Dynamically register a new dial plan application */ +int ast_register_application(const char *app, int (*execute)(struct ast_channel *, void *), const char *synopsis, const char *description) +{ + struct ast_app *tmp, *cur = NULL; + char tmps[80]; + int length; + + AST_LIST_LOCK(&apps); + AST_LIST_TRAVERSE(&apps, tmp, list) { + if (!strcasecmp(app, tmp->name)) { + ast_log(LOG_WARNING, "Already have an application '%s'\n", app); + AST_LIST_UNLOCK(&apps); + return -1; + } + } + + length = sizeof(*tmp) + strlen(app) + 1; + + if (!(tmp = ast_calloc(1, length))) { + AST_LIST_UNLOCK(&apps); + return -1; + } + + strcpy(tmp->name, app); + tmp->execute = execute; + tmp->synopsis = synopsis; + tmp->description = description; + + /* Store in alphabetical order */ + AST_LIST_TRAVERSE_SAFE_BEGIN(&apps, cur, list) { + if (strcasecmp(tmp->name, cur->name) < 0) { + AST_LIST_INSERT_BEFORE_CURRENT(&apps, tmp, list); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + if (!cur) + AST_LIST_INSERT_TAIL(&apps, tmp, list); + + if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Registered application '%s'\n", term_color(tmps, tmp->name, COLOR_BRCYAN, 0, sizeof(tmps))); + + AST_LIST_UNLOCK(&apps); + + return 0; +} + +/* + * Append to the list. We don't have a tail pointer because we need + * to scan the list anyways to check for duplicates during insertion. + */ +int ast_register_switch(struct ast_switch *sw) +{ + struct ast_switch *tmp; + + AST_LIST_LOCK(&switches); + AST_LIST_TRAVERSE(&switches, tmp, list) { + if (!strcasecmp(tmp->name, sw->name)) { + AST_LIST_UNLOCK(&switches); + ast_log(LOG_WARNING, "Switch '%s' already found\n", sw->name); + return -1; + } + } + AST_LIST_INSERT_TAIL(&switches, sw, list); + AST_LIST_UNLOCK(&switches); + + return 0; +} + +void ast_unregister_switch(struct ast_switch *sw) +{ + AST_LIST_LOCK(&switches); + AST_LIST_REMOVE(&switches, sw, list); + AST_LIST_UNLOCK(&switches); +} + +/* + * Help for CLI commands ... + */ +static char show_application_help[] = +"Usage: show application [ [ [...]]]\n" +" Describes a particular application.\n"; + +static char show_functions_help[] = +"Usage: show functions [like ]\n" +" List builtin functions, optionally only those matching a given string\n"; + +static char show_function_help[] = +"Usage: show function \n" +" Describe a particular dialplan function.\n"; + +static char show_applications_help[] = +"Usage: show applications [{like|describing} ]\n" +" List applications which are currently available.\n" +" If 'like', will be a substring of the app name\n" +" If 'describing', will be a substring of the description\n"; + +static char show_dialplan_help[] = +"Usage: show dialplan [exten@][context]\n" +" Show dialplan\n"; + +static char show_switches_help[] = +"Usage: show switches\n" +" Show registered switches\n"; + +static char show_hints_help[] = +"Usage: show hints\n" +" Show registered hints\n"; + +static char show_globals_help[] = +"Usage: show globals\n" +" Show current global dialplan variables and their values\n"; + +static char set_global_help[] = +"Usage: set global \n" +" Set global dialplan variable to \n"; + + +/* + * IMPLEMENTATION OF CLI FUNCTIONS IS IN THE SAME ORDER AS COMMANDS HELPS + * + */ + +/* + * \brief 'show application' CLI command implementation functions ... + */ + +/* + * There is a possibility to show informations about more than one + * application at one time. You can type 'show application Dial Echo' and + * you will see informations about these two applications ... + */ +static char *complete_show_application(const char *line, const char *word, int pos, int state) +{ + struct ast_app *a; + char *ret = NULL; + int which = 0; + int wordlen = strlen(word); + + /* return the n-th [partial] matching entry */ + AST_LIST_LOCK(&apps); + AST_LIST_TRAVERSE(&apps, a, list) { + if (!strncasecmp(word, a->name, wordlen) && ++which > state) { + ret = strdup(a->name); + break; + } + } + AST_LIST_UNLOCK(&apps); + + return ret; +} + +static int handle_show_application(int fd, int argc, char *argv[]) +{ + struct ast_app *a; + int app, no_registered_app = 1; + + if (argc < 3) + return RESULT_SHOWUSAGE; + + /* ... go through all applications ... */ + AST_LIST_LOCK(&apps); + AST_LIST_TRAVERSE(&apps, a, list) { + /* ... compare this application name with all arguments given + * to 'show application' command ... */ + for (app = 2; app < argc; app++) { + if (!strcasecmp(a->name, argv[app])) { + /* Maximum number of characters added by terminal coloring is 22 */ + char infotitle[64 + AST_MAX_APP + 22], syntitle[40], destitle[40]; + char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL; + int synopsis_size, description_size; + + no_registered_app = 0; + + if (a->synopsis) + synopsis_size = strlen(a->synopsis) + 23; + else + synopsis_size = strlen("Not available") + 23; + synopsis = alloca(synopsis_size); + + if (a->description) + description_size = strlen(a->description) + 23; + else + description_size = strlen("Not available") + 23; + description = alloca(description_size); + + if (synopsis && description) { + snprintf(info, 64 + AST_MAX_APP, "\n -= Info about application '%s' =- \n\n", a->name); + term_color(infotitle, info, COLOR_MAGENTA, 0, 64 + AST_MAX_APP + 22); + term_color(syntitle, "[Synopsis]\n", COLOR_MAGENTA, 0, 40); + term_color(destitle, "[Description]\n", COLOR_MAGENTA, 0, 40); + term_color(synopsis, + a->synopsis ? a->synopsis : "Not available", + COLOR_CYAN, 0, synopsis_size); + term_color(description, + a->description ? a->description : "Not available", + COLOR_CYAN, 0, description_size); + + ast_cli(fd,"%s%s%s\n\n%s%s\n", infotitle, syntitle, synopsis, destitle, description); + } else { + /* ... one of our applications, show info ...*/ + ast_cli(fd,"\n -= Info about application '%s' =- \n\n" + "[Synopsis]\n %s\n\n" + "[Description]\n%s\n", + a->name, + a->synopsis ? a->synopsis : "Not available", + a->description ? a->description : "Not available"); + } + } + } + } + AST_LIST_UNLOCK(&apps); + + /* we found at least one app? no? */ + if (no_registered_app) { + ast_cli(fd, "Your application(s) is (are) not registered\n"); + return RESULT_FAILURE; + } + + return RESULT_SUCCESS; +} + +/*! \brief handle_show_hints: CLI support for listing registred dial plan hints */ +static int handle_show_hints(int fd, int argc, char *argv[]) +{ + struct ast_hint *hint; + int num = 0; + int watchers; + struct ast_state_cb *watcher; + + if (AST_LIST_EMPTY(&hints)) { + ast_cli(fd, "There are no registered dialplan hints\n"); + return RESULT_SUCCESS; + } + /* ... we have hints ... */ + ast_cli(fd, "\n -= Registered Asterisk Dial Plan Hints =-\n"); + AST_LIST_LOCK(&hints); + AST_LIST_TRAVERSE(&hints, hint, list) { + watchers = 0; + for (watcher = hint->callbacks; watcher; watcher = watcher->next) + watchers++; + ast_cli(fd, " %20s@%-20.20s: %-20.20s State:%-15.15s Watchers %2d\n", + ast_get_extension_name(hint->exten), + ast_get_context_name(ast_get_extension_context(hint->exten)), + ast_get_extension_app(hint->exten), + ast_extension_state2str(hint->laststate), watchers); + num++; + } + ast_cli(fd, "----------------\n"); + ast_cli(fd, "- %d hints registered\n", num); + AST_LIST_UNLOCK(&hints); + return RESULT_SUCCESS; +} + +/*! \brief handle_show_switches: CLI support for listing registred dial plan switches */ +static int handle_show_switches(int fd, int argc, char *argv[]) +{ + struct ast_switch *sw; + + AST_LIST_LOCK(&switches); + + if (AST_LIST_EMPTY(&switches)) { + AST_LIST_UNLOCK(&switches); + ast_cli(fd, "There are no registered alternative switches\n"); + return RESULT_SUCCESS; + } + + ast_cli(fd, "\n -= Registered Asterisk Alternative Switches =-\n"); + AST_LIST_TRAVERSE(&switches, sw, list) + ast_cli(fd, "%s: %s\n", sw->name, sw->description); + + AST_LIST_UNLOCK(&switches); + + return RESULT_SUCCESS; +} + +/* + * 'show applications' CLI command implementation functions ... + */ +static int handle_show_applications(int fd, int argc, char *argv[]) +{ + struct ast_app *a; + int like = 0, describing = 0; + int total_match = 0; /* Number of matches in like clause */ + int total_apps = 0; /* Number of apps registered */ + + AST_LIST_LOCK(&apps); + + if (AST_LIST_EMPTY(&apps)) { + ast_cli(fd, "There are no registered applications\n"); + AST_LIST_UNLOCK(&apps); + return -1; + } + + /* show applications like */ + if ((argc == 4) && (!strcmp(argv[2], "like"))) { + like = 1; + } else if ((argc > 3) && (!strcmp(argv[2], "describing"))) { + describing = 1; + } + + /* show applications describing [] [...] */ + if ((!like) && (!describing)) { + ast_cli(fd, " -= Registered Asterisk Applications =-\n"); + } else { + ast_cli(fd, " -= Matching Asterisk Applications =-\n"); + } + + AST_LIST_TRAVERSE(&apps, a, list) { + int printapp = 0; + total_apps++; + if (like) { + if (strcasestr(a->name, argv[3])) { + printapp = 1; + total_match++; + } + } else if (describing) { + if (a->description) { + /* Match all words on command line */ + int i; + printapp = 1; + for (i = 3; i < argc; i++) { + if (!strcasestr(a->description, argv[i])) { + printapp = 0; + } else { + total_match++; + } + } + } + } else { + printapp = 1; + } + + if (printapp) { + ast_cli(fd," %20s: %s\n", a->name, a->synopsis ? a->synopsis : ""); + } + } + if ((!like) && (!describing)) { + ast_cli(fd, " -= %d Applications Registered =-\n",total_apps); + } else { + ast_cli(fd, " -= %d Applications Matching =-\n",total_match); + } + + AST_LIST_UNLOCK(&apps); + + return RESULT_SUCCESS; +} + +static char *complete_show_applications(const char *line, const char *word, int pos, int state) +{ + static char* choices[] = { "like", "describing", NULL }; + + return (pos != 2) ? NULL : ast_cli_complete(word, choices, state); +} + +/* + * 'show dialplan' CLI command implementation functions ... + */ +static char *complete_show_dialplan_context(const char *line, const char *word, int pos, + int state) +{ + struct ast_context *c = NULL; + char *ret = NULL; + int which = 0; + int wordlen; + + /* we are do completion of [exten@]context on second position only */ + if (pos != 2) + return NULL; + + ast_lock_contexts(); + + wordlen = strlen(word); + + /* walk through all contexts and return the n-th match */ + while ( (c = ast_walk_contexts(c)) ) { + if (!strncasecmp(word, ast_get_context_name(c), wordlen) && ++which > state) { + ret = ast_strdup(ast_get_context_name(c)); + break; + } + } + + ast_unlock_contexts(); + + return ret; +} + +struct dialplan_counters { + int total_context; + int total_exten; + int total_prio; + int context_existence; + int extension_existence; +}; + +/*! \brief helper function to print an extension */ +static void print_ext(struct ast_exten *e, char * buf, int buflen) +{ + int prio = ast_get_extension_priority(e); + if (prio == PRIORITY_HINT) { + snprintf(buf, buflen, "hint: %s", + ast_get_extension_app(e)); + } else { + snprintf(buf, buflen, "%d. %s(%s)", + prio, ast_get_extension_app(e), + (char *)ast_get_extension_app_data(e)); + } +} + +/* XXX not verified */ +static int show_dialplan_helper(int fd, const char *context, const char *exten, struct dialplan_counters *dpc, struct ast_include *rinclude, int includecount, const char *includes[]) +{ + struct ast_context *c = NULL; + int res = 0, old_total_exten = dpc->total_exten; + + ast_lock_contexts(); + + /* walk all contexts ... */ + while ( (c = ast_walk_contexts(c)) ) { + struct ast_exten *e; + struct ast_include *i; + struct ast_ignorepat *ip; + char buf[256], buf2[256]; + int context_info_printed = 0; + + if (context && strcmp(ast_get_context_name(c), context)) + continue; /* skip this one, name doesn't match */ + + dpc->context_existence = 1; + + ast_lock_context(c); + + /* are we looking for exten too? if yes, we print context + * only if we find our extension. + * Otherwise print context even if empty ? + * XXX i am not sure how the rinclude is handled. + * I think it ought to go inside. + */ + if (!exten) { + dpc->total_context++; + ast_cli(fd, "[ Context '%s' created by '%s' ]\n", + ast_get_context_name(c), ast_get_context_registrar(c)); + context_info_printed = 1; + } + + /* walk extensions ... */ + e = NULL; + while ( (e = ast_walk_context_extensions(c, e)) ) { + struct ast_exten *p; + + if (exten && !ast_extension_match(ast_get_extension_name(e), exten)) + continue; /* skip, extension match failed */ + + dpc->extension_existence = 1; + + /* may we print context info? */ + if (!context_info_printed) { + dpc->total_context++; + if (rinclude) { /* TODO Print more info about rinclude */ + ast_cli(fd, "[ Included context '%s' created by '%s' ]\n", + ast_get_context_name(c), ast_get_context_registrar(c)); + } else { + ast_cli(fd, "[ Context '%s' created by '%s' ]\n", + ast_get_context_name(c), ast_get_context_registrar(c)); + } + context_info_printed = 1; + } + dpc->total_prio++; + + /* write extension name and first peer */ + snprintf(buf, sizeof(buf), "'%s' =>", ast_get_extension_name(e)); + + print_ext(e, buf2, sizeof(buf2)); + + ast_cli(fd, " %-17s %-45s [%s]\n", buf, buf2, + ast_get_extension_registrar(e)); + + dpc->total_exten++; + /* walk next extension peers */ + p = e; /* skip the first one, we already got it */ + while ( (p = ast_walk_extension_priorities(e, p)) ) { + const char *el = ast_get_extension_label(p); + dpc->total_prio++; + if (el) + snprintf(buf, sizeof(buf), " [%s]", el); + else + buf[0] = '\0'; + print_ext(p, buf2, sizeof(buf2)); + + ast_cli(fd," %-17s %-45s [%s]\n", buf, buf2, + ast_get_extension_registrar(p)); + } + } + + /* walk included and write info ... */ + i = NULL; + while ( (i = ast_walk_context_includes(c, i)) ) { + snprintf(buf, sizeof(buf), "'%s'", ast_get_include_name(i)); + if (exten) { + /* Check all includes for the requested extension */ + if (includecount >= AST_PBX_MAX_STACK) { + ast_log(LOG_NOTICE, "Maximum include depth exceeded!\n"); + } else { + int dupe=0; + int x; + for (x=0;x %-45s [%s]\n", + buf, ast_get_include_registrar(i)); + } + } + + /* walk ignore patterns and write info ... */ + ip = NULL; + while ( (ip = ast_walk_context_ignorepats(c, ip)) ) { + const char *ipname = ast_get_ignorepat_name(ip); + char ignorepat[AST_MAX_EXTENSION]; + snprintf(buf, sizeof(buf), "'%s'", ipname); + snprintf(ignorepat, sizeof(ignorepat), "_%s.", ipname); + if (!exten || ast_extension_match(ignorepat, exten)) { + ast_cli(fd, " Ignore pattern => %-45s [%s]\n", + buf, ast_get_ignorepat_registrar(ip)); + } + } + if (!rinclude) { + struct ast_sw *sw = NULL; + while ( (sw = ast_walk_context_switches(c, sw)) ) { + snprintf(buf, sizeof(buf), "'%s/%s'", + ast_get_switch_name(sw), + ast_get_switch_data(sw)); + ast_cli(fd, " Alt. Switch => %-45s [%s]\n", + buf, ast_get_switch_registrar(sw)); + } + } + + ast_unlock_context(c); + + /* if we print something in context, make an empty line */ + if (context_info_printed) + ast_cli(fd, "\r\n"); + } + ast_unlock_contexts(); + + return (dpc->total_exten == old_total_exten) ? -1 : res; +} + +static int handle_show_dialplan(int fd, int argc, char *argv[]) +{ + char *exten = NULL, *context = NULL; + /* Variables used for different counters */ + struct dialplan_counters counters; + + const char *incstack[AST_PBX_MAX_STACK]; + memset(&counters, 0, sizeof(counters)); + + if (argc != 2 && argc != 3) + return RESULT_SHOWUSAGE; + + /* we obtain [exten@]context? if yes, split them ... */ + if (argc == 3) { + if (strchr(argv[2], '@')) { /* split into exten & context */ + context = ast_strdupa(argv[2]); + exten = strsep(&context, "@"); + /* change empty strings to NULL */ + if (ast_strlen_zero(exten)) + exten = NULL; + } else { /* no '@' char, only context given */ + context = argv[2]; + } + if (ast_strlen_zero(context)) + context = NULL; + } + /* else Show complete dial plan, context and exten are NULL */ + show_dialplan_helper(fd, context, exten, &counters, NULL, 0, incstack); + + /* check for input failure and throw some error messages */ + if (context && !counters.context_existence) { + ast_cli(fd, "There is no existence of '%s' context\n", context); + return RESULT_FAILURE; + } + + if (exten && !counters.extension_existence) { + if (context) + ast_cli(fd, "There is no existence of %s@%s extension\n", + exten, context); + else + ast_cli(fd, + "There is no existence of '%s' extension in all contexts\n", + exten); + return RESULT_FAILURE; + } + + ast_cli(fd,"-= %d %s (%d %s) in %d %s. =-\n", + counters.total_exten, counters.total_exten == 1 ? "extension" : "extensions", + counters.total_prio, counters.total_prio == 1 ? "priority" : "priorities", + counters.total_context, counters.total_context == 1 ? "context" : "contexts"); + + /* everything ok */ + return RESULT_SUCCESS; +} + +/*! \brief CLI support for listing global variables in a parseable way */ +static int handle_show_globals(int fd, int argc, char *argv[]) +{ + int i = 0; + struct ast_var_t *newvariable; + + ast_mutex_lock(&globalslock); + AST_LIST_TRAVERSE (&globals, newvariable, entries) { + i++; + ast_cli(fd, " %s=%s\n", ast_var_name(newvariable), ast_var_value(newvariable)); + } + ast_mutex_unlock(&globalslock); + ast_cli(fd, "\n -- %d variables\n", i); + + return RESULT_SUCCESS; +} + +/*! \brief CLI support for setting global variables */ +static int handle_set_global(int fd, int argc, char *argv[]) +{ + if (argc != 4) + return RESULT_SHOWUSAGE; + + pbx_builtin_setvar_helper(NULL, argv[2], argv[3]); + ast_cli(fd, "\n -- Global variable %s set to %s\n", argv[2], argv[3]); + + return RESULT_SUCCESS; +} + + + +/* + * CLI entries for upper commands ... + */ +static struct ast_cli_entry pbx_cli[] = { + { { "show", "applications", NULL }, handle_show_applications, + "Shows registered dialplan applications", show_applications_help, complete_show_applications }, + { { "show", "functions", NULL }, handle_show_functions, + "Shows registered dialplan functions", show_functions_help }, + { { "show" , "function", NULL }, handle_show_function, + "Describe a specific dialplan function", show_function_help, complete_show_function }, + { { "show", "application", NULL }, handle_show_application, + "Describe a specific dialplan application", show_application_help, complete_show_application }, + { { "show", "dialplan", NULL }, handle_show_dialplan, + "Show dialplan", show_dialplan_help, complete_show_dialplan_context }, + { { "show", "switches", NULL }, handle_show_switches, + "Show alternative switches", show_switches_help }, + { { "show", "hints", NULL }, handle_show_hints, + "Show dialplan hints", show_hints_help }, + { { "show", "globals", NULL }, handle_show_globals, + "Show global dialplan variables", show_globals_help }, + { { "set", "global", NULL }, handle_set_global, + "Set global dialplan variable", set_global_help }, +}; + +int ast_unregister_application(const char *app) +{ + struct ast_app *tmp; + + AST_LIST_LOCK(&apps); + AST_LIST_TRAVERSE_SAFE_BEGIN(&apps, tmp, list) { + if (!strcasecmp(app, tmp->name)) { + AST_LIST_REMOVE_CURRENT(&apps, list); + if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Unregistered application '%s'\n", tmp->name); + free(tmp); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + AST_LIST_UNLOCK(&apps); + + return tmp ? 0 : -1; +} + +struct ast_context *ast_context_create(struct ast_context **extcontexts, const char *name, const char *registrar) +{ + struct ast_context *tmp, **local_contexts; + int length = sizeof(struct ast_context) + strlen(name) + 1; + + if (!extcontexts) { + ast_mutex_lock(&conlock); + local_contexts = &contexts; + } else + local_contexts = extcontexts; + + for (tmp = *local_contexts; tmp; tmp = tmp->next) { + if (!strcasecmp(tmp->name, name)) { + ast_log(LOG_WARNING, "Tried to register context '%s', already in use\n", name); + if (!extcontexts) + ast_mutex_unlock(&conlock); + return NULL; + } + } + if ((tmp = ast_calloc(1, length))) { + ast_mutex_init(&tmp->lock); + ast_mutex_init(&tmp->macrolock); + strcpy(tmp->name, name); + tmp->root = NULL; + tmp->registrar = registrar; + tmp->next = *local_contexts; + tmp->includes = NULL; + tmp->ignorepats = NULL; + *local_contexts = tmp; + if (option_debug) + ast_log(LOG_DEBUG, "Registered context '%s'\n", tmp->name); + else if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Registered extension context '%s'\n", tmp->name); + } + + if (!extcontexts) + ast_mutex_unlock(&conlock); + return tmp; +} + +void __ast_context_destroy(struct ast_context *con, const char *registrar); + +struct store_hint { + char *context; + char *exten; + struct ast_state_cb *callbacks; + int laststate; + AST_LIST_ENTRY(store_hint) list; + char data[1]; +}; + +AST_LIST_HEAD(store_hints, store_hint); + +/* XXX this does not check that multiple contexts are merged */ +void ast_merge_contexts_and_delete(struct ast_context **extcontexts, const char *registrar) +{ + struct ast_context *tmp, *lasttmp = NULL; + struct store_hints store = AST_LIST_HEAD_INIT_VALUE; + struct store_hint *this; + struct ast_hint *hint; + struct ast_exten *exten; + int length; + struct ast_state_cb *thiscb, *prevcb; + + /* it is very important that this function hold the hint list lock _and_ the conlock + during its operation; not only do we need to ensure that the list of contexts + and extensions does not change, but also that no hint callbacks (watchers) are + added or removed during the merge/delete process + + in addition, the locks _must_ be taken in this order, because there are already + other code paths that use this order + */ + ast_mutex_lock(&conlock); + AST_LIST_LOCK(&hints); + + /* preserve all watchers for hints associated with this registrar */ + AST_LIST_TRAVERSE(&hints, hint, list) { + if (hint->callbacks && !strcmp(registrar, hint->exten->parent->registrar)) { + length = strlen(hint->exten->exten) + strlen(hint->exten->parent->name) + 2 + sizeof(*this); + if (!(this = ast_calloc(1, length))) + continue; + this->callbacks = hint->callbacks; + hint->callbacks = NULL; + this->laststate = hint->laststate; + this->context = this->data; + strcpy(this->data, hint->exten->parent->name); + this->exten = this->data + strlen(this->context) + 1; + strcpy(this->exten, hint->exten->exten); + AST_LIST_INSERT_HEAD(&store, this, list); + } + } + + tmp = *extcontexts; + if (registrar) { + /* XXX remove previous contexts from same registrar */ + ast_log(LOG_DEBUG, "must remove any reg %s\n", registrar); + __ast_context_destroy(NULL,registrar); + while (tmp) { + lasttmp = tmp; + tmp = tmp->next; + } + } else { + /* XXX remove contexts with the same name */ + while (tmp) { + ast_log(LOG_WARNING, "must remove %s reg %s\n", tmp->name, tmp->registrar); + __ast_context_destroy(tmp,tmp->registrar); + lasttmp = tmp; + tmp = tmp->next; + } + } + if (lasttmp) { + lasttmp->next = contexts; + contexts = *extcontexts; + *extcontexts = NULL; + } else + ast_log(LOG_WARNING, "Requested contexts didn't get merged\n"); + + /* restore the watchers for hints that can be found; notify those that + cannot be restored + */ + while ((this = AST_LIST_REMOVE_HEAD(&store, list))) { + exten = ast_hint_extension(NULL, this->context, this->exten); + /* Find the hint in the list of hints */ + AST_LIST_TRAVERSE(&hints, hint, list) { + if (hint->exten == exten) + break; + } + if (!exten || !hint) { + /* this hint has been removed, notify the watchers */ + prevcb = NULL; + thiscb = this->callbacks; + while (thiscb) { + prevcb = thiscb; + thiscb = thiscb->next; + prevcb->callback(this->context, this->exten, AST_EXTENSION_REMOVED, prevcb->data); + free(prevcb); + } + } else { + thiscb = this->callbacks; + while (thiscb->next) + thiscb = thiscb->next; + thiscb->next = hint->callbacks; + hint->callbacks = this->callbacks; + hint->laststate = this->laststate; + } + free(this); + } + + AST_LIST_UNLOCK(&hints); + ast_mutex_unlock(&conlock); + + return; +} + +/* + * errno values + * EBUSY - can't lock + * ENOENT - no existence of context + */ +int ast_context_add_include(const char *context, const char *include, const char *registrar) +{ + int ret = -1; + struct ast_context *c = find_context_locked(context); + + if (c) { + ret = ast_context_add_include2(c, include, registrar); + ast_unlock_contexts(); + } + return ret; +} + +/*! \brief Helper for get_range. + * return the index of the matching entry, starting from 1. + * If names is not supplied, try numeric values. + */ +static int lookup_name(const char *s, char *const names[], int max) +{ + int i; + + if (names) { + for (i = 0; names[i]; i++) { + if (!strcasecmp(s, names[i])) + return i+1; + } + } else if (sscanf(s, "%d", &i) == 1 && i >= 1 && i <= max) { + return i; + } + return 0; /* error return */ +} + +/*! \brief helper function to return a range up to max (7, 12, 31 respectively). + * names, if supplied, is an array of names that should be mapped to numbers. + */ +static unsigned get_range(char *src, int max, char *const names[], const char *msg) +{ + int s, e; /* start and ending position */ + unsigned int mask = 0; + + /* Check for whole range */ + if (ast_strlen_zero(src) || !strcmp(src, "*")) { + s = 0; + e = max - 1; + } else { + /* Get start and ending position */ + char *c = strchr(src, '-'); + if (c) + *c++ = '\0'; + /* Find the start */ + s = lookup_name(src, names, max); + if (!s) { + ast_log(LOG_WARNING, "Invalid %s '%s', assuming none\n", msg, src); + return 0; + } + s--; + if (c) { /* find end of range */ + e = lookup_name(c, names, max); + if (!e) { + ast_log(LOG_WARNING, "Invalid end %s '%s', assuming none\n", msg, c); + return 0; + } + e--; + } else + e = s; + } + /* Fill the mask. Remember that ranges are cyclic */ + mask = 1 << e; /* initialize with last element */ + while (s != e) { + if (s >= max) { + s = 0; + mask |= (1 << s); + } else { + mask |= (1 << s); + s++; + } + } + return mask; +} + +/*! \brief store a bitmask of valid times, one bit each 2 minute */ +static void get_timerange(struct ast_timing *i, char *times) +{ + char *e; + int x; + int s1, s2; + int e1, e2; + /* int cth, ctm; */ + + /* start disabling all times, fill the fields with 0's, as they may contain garbage */ + memset(i->minmask, 0, sizeof(i->minmask)); + + /* 2-minutes per bit, since the mask has only 32 bits :( */ + /* Star is all times */ + if (ast_strlen_zero(times) || !strcmp(times, "*")) { + for (x=0; x<24; x++) + i->minmask[x] = 0x3fffffff; /* 30 bits */ + return; + } + /* Otherwise expect a range */ + e = strchr(times, '-'); + if (!e) { + ast_log(LOG_WARNING, "Time range is not valid. Assuming no restrictions based on time.\n"); + return; + } + *e++ = '\0'; + /* XXX why skip non digits ? */ + while (*e && !isdigit(*e)) + e++; + if (!*e) { + ast_log(LOG_WARNING, "Invalid time range. Assuming no restrictions based on time.\n"); + return; + } + if (sscanf(times, "%d:%d", &s1, &s2) != 2) { + ast_log(LOG_WARNING, "%s isn't a time. Assuming no restrictions based on time.\n", times); + return; + } + if (sscanf(e, "%d:%d", &e1, &e2) != 2) { + ast_log(LOG_WARNING, "%s isn't a time. Assuming no restrictions based on time.\n", e); + return; + } + /* XXX this needs to be optimized */ +#if 1 + s1 = s1 * 30 + s2/2; + if ((s1 < 0) || (s1 >= 24*30)) { + ast_log(LOG_WARNING, "%s isn't a valid start time. Assuming no time.\n", times); + return; + } + e1 = e1 * 30 + e2/2; + if ((e1 < 0) || (e1 >= 24*30)) { + ast_log(LOG_WARNING, "%s isn't a valid end time. Assuming no time.\n", e); + return; + } + /* Go through the time and enable each appropriate bit */ + for (x=s1;x != e1;x = (x + 1) % (24 * 30)) { + i->minmask[x/30] |= (1 << (x % 30)); + } + /* Do the last one */ + i->minmask[x/30] |= (1 << (x % 30)); +#else + for (cth=0; cth<24; cth++) { + /* Initialize masks to blank */ + i->minmask[cth] = 0; + for (ctm=0; ctm<30; ctm++) { + if ( + /* First hour with more than one hour */ + (((cth == s1) && (ctm >= s2)) && + ((cth < e1))) + /* Only one hour */ + || (((cth == s1) && (ctm >= s2)) && + ((cth == e1) && (ctm <= e2))) + /* In between first and last hours (more than 2 hours) */ + || ((cth > s1) && + (cth < e1)) + /* Last hour with more than one hour */ + || ((cth > s1) && + ((cth == e1) && (ctm <= e2))) + ) + i->minmask[cth] |= (1 << (ctm / 2)); + } + } +#endif + /* All done */ + return; +} + +static char *days[] = +{ + "sun", + "mon", + "tue", + "wed", + "thu", + "fri", + "sat", + NULL, +}; + +static char *months[] = +{ + "jan", + "feb", + "mar", + "apr", + "may", + "jun", + "jul", + "aug", + "sep", + "oct", + "nov", + "dec", + NULL, +}; + +int ast_build_timing(struct ast_timing *i, const char *info_in) +{ + char info_save[256]; + char *info; + + /* Check for empty just in case */ + if (ast_strlen_zero(info_in)) + return 0; + /* make a copy just in case we were passed a static string */ + ast_copy_string(info_save, info_in, sizeof(info_save)); + info = info_save; + /* Assume everything except time */ + i->monthmask = 0xfff; /* 12 bits */ + i->daymask = 0x7fffffffU; /* 31 bits */ + i->dowmask = 0x7f; /* 7 bits */ + /* on each call, use strsep() to move info to the next argument */ + get_timerange(i, strsep(&info, "|")); + if (info) + i->dowmask = get_range(strsep(&info, "|"), 7, days, "day of week"); + if (info) + i->daymask = get_range(strsep(&info, "|"), 31, NULL, "day"); + if (info) + i->monthmask = get_range(strsep(&info, "|"), 12, months, "month"); + return 1; +} + +int ast_check_timing(const struct ast_timing *i) +{ + struct tm tm; + time_t t = time(NULL); + + localtime_r(&t,&tm); + + /* If it's not the right month, return */ + if (!(i->monthmask & (1 << tm.tm_mon))) + return 0; + + /* If it's not that time of the month.... */ + /* Warning, tm_mday has range 1..31! */ + if (!(i->daymask & (1 << (tm.tm_mday-1)))) + return 0; + + /* If it's not the right day of the week */ + if (!(i->dowmask & (1 << tm.tm_wday))) + return 0; + + /* Sanity check the hour just to be safe */ + if ((tm.tm_hour < 0) || (tm.tm_hour > 23)) { + ast_log(LOG_WARNING, "Insane time...\n"); + return 0; + } + + /* Now the tough part, we calculate if it fits + in the right time based on min/hour */ + if (!(i->minmask[tm.tm_hour] & (1 << (tm.tm_min / 2)))) + return 0; + + /* If we got this far, then we're good */ + return 1; +} + +/* + * errno values + * ENOMEM - out of memory + * EBUSY - can't lock + * EEXIST - already included + * EINVAL - there is no existence of context for inclusion + */ +int ast_context_add_include2(struct ast_context *con, const char *value, + const char *registrar) +{ + struct ast_include *new_include; + char *c; + struct ast_include *i, *il = NULL; /* include, include_last */ + int length; + char *p; + + length = sizeof(struct ast_include); + length += 2 * (strlen(value) + 1); + + /* allocate new include structure ... */ + if (!(new_include = ast_calloc(1, length))) + return -1; + /* Fill in this structure. Use 'p' for assignments, as the fields + * in the structure are 'const char *' + */ + p = new_include->stuff; + new_include->name = p; + strcpy(p, value); + p += strlen(value) + 1; + new_include->rname = p; + strcpy(p, value); + /* Strip off timing info, and process if it is there */ + if ( (c = strchr(p, '|')) ) { + *c++ = '\0'; + new_include->hastime = ast_build_timing(&(new_include->timing), c); + } + new_include->next = NULL; + new_include->registrar = registrar; + + ast_mutex_lock(&con->lock); + + /* ... go to last include and check if context is already included too... */ + for (i = con->includes; i; i = i->next) { + if (!strcasecmp(i->name, new_include->name)) { + free(new_include); + ast_mutex_unlock(&con->lock); + errno = EEXIST; + return -1; + } + il = i; + } + + /* ... include new context into context list, unlock, return */ + if (il) + il->next = new_include; + else + con->includes = new_include; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Including context '%s' in context '%s'\n", new_include->name, ast_get_context_name(con)); + ast_mutex_unlock(&con->lock); + + return 0; +} + +/* + * errno values + * EBUSY - can't lock + * ENOENT - no existence of context + */ +int ast_context_add_switch(const char *context, const char *sw, const char *data, int eval, const char *registrar) +{ + int ret = -1; + struct ast_context *c = find_context_locked(context); + + if (c) { /* found, add switch to this context */ + ret = ast_context_add_switch2(c, sw, data, eval, registrar); + ast_unlock_contexts(); + } + return ret; +} + +/* + * errno values + * ENOMEM - out of memory + * EBUSY - can't lock + * EEXIST - already included + * EINVAL - there is no existence of context for inclusion + */ +int ast_context_add_switch2(struct ast_context *con, const char *value, + const char *data, int eval, const char *registrar) +{ + struct ast_sw *new_sw; + struct ast_sw *i; + int length; + char *p; + + length = sizeof(struct ast_sw); + length += strlen(value) + 1; + if (data) + length += strlen(data); + length++; + if (eval) { + /* Create buffer for evaluation of variables */ + length += SWITCH_DATA_LENGTH; + length++; + } + + /* allocate new sw structure ... */ + if (!(new_sw = ast_calloc(1, length))) + return -1; + /* ... fill in this structure ... */ + p = new_sw->stuff; + new_sw->name = p; + strcpy(new_sw->name, value); + p += strlen(value) + 1; + new_sw->data = p; + if (data) { + strcpy(new_sw->data, data); + p += strlen(data) + 1; + } else { + strcpy(new_sw->data, ""); + p++; + } + if (eval) + new_sw->tmpdata = p; + new_sw->eval = eval; + new_sw->registrar = registrar; + + /* ... try to lock this context ... */ + ast_mutex_lock(&con->lock); + + /* ... go to last sw and check if context is already swd too... */ + AST_LIST_TRAVERSE(&con->alts, i, list) { + if (!strcasecmp(i->name, new_sw->name) && !strcasecmp(i->data, new_sw->data)) { + free(new_sw); + ast_mutex_unlock(&con->lock); + errno = EEXIST; + return -1; + } + } + + /* ... sw new context into context list, unlock, return */ + AST_LIST_INSERT_TAIL(&con->alts, new_sw, list); + + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Including switch '%s/%s' in context '%s'\n", new_sw->name, new_sw->data, ast_get_context_name(con)); + + ast_mutex_unlock(&con->lock); + + return 0; +} + +/* + * EBUSY - can't lock + * ENOENT - there is not context existence + */ +int ast_context_remove_ignorepat(const char *context, const char *ignorepat, const char *registrar) +{ + int ret = -1; + struct ast_context *c = find_context_locked(context); + + if (c) { + ret = ast_context_remove_ignorepat2(c, ignorepat, registrar); + ast_unlock_contexts(); + } + return ret; +} + +int ast_context_remove_ignorepat2(struct ast_context *con, const char *ignorepat, const char *registrar) +{ + struct ast_ignorepat *ip, *ipl = NULL; + + ast_mutex_lock(&con->lock); + + for (ip = con->ignorepats; ip; ip = ip->next) { + if (!strcmp(ip->pattern, ignorepat) && + (!registrar || (registrar == ip->registrar))) { + if (ipl) { + ipl->next = ip->next; + free(ip); + } else { + con->ignorepats = ip->next; + free(ip); + } + ast_mutex_unlock(&con->lock); + return 0; + } + ipl = ip; + } + + ast_mutex_unlock(&con->lock); + errno = EINVAL; + return -1; +} + +/* + * EBUSY - can't lock + * ENOENT - there is no existence of context + */ +int ast_context_add_ignorepat(const char *context, const char *value, const char *registrar) +{ + int ret = -1; + struct ast_context *c = find_context_locked(context); + + if (c) { + ret = ast_context_add_ignorepat2(c, value, registrar); + ast_unlock_contexts(); + } + return ret; +} + +int ast_context_add_ignorepat2(struct ast_context *con, const char *value, const char *registrar) +{ + struct ast_ignorepat *ignorepat, *ignorepatc, *ignorepatl = NULL; + int length; + length = sizeof(struct ast_ignorepat); + length += strlen(value) + 1; + if (!(ignorepat = ast_calloc(1, length))) + return -1; + /* The cast to char * is because we need to write the initial value. + * The field is not supposed to be modified otherwise + */ + strcpy((char *)ignorepat->pattern, value); + ignorepat->next = NULL; + ignorepat->registrar = registrar; + ast_mutex_lock(&con->lock); + for (ignorepatc = con->ignorepats; ignorepatc; ignorepatc = ignorepatc->next) { + ignorepatl = ignorepatc; + if (!strcasecmp(ignorepatc->pattern, value)) { + /* Already there */ + ast_mutex_unlock(&con->lock); + errno = EEXIST; + return -1; + } + } + if (ignorepatl) + ignorepatl->next = ignorepat; + else + con->ignorepats = ignorepat; + ast_mutex_unlock(&con->lock); + return 0; + +} + +int ast_ignore_pattern(const char *context, const char *pattern) +{ + struct ast_context *con = ast_context_find(context); + if (con) { + struct ast_ignorepat *pat; + for (pat = con->ignorepats; pat; pat = pat->next) { + if (ast_extension_match(pat->pattern, pattern)) + return 1; + } + } + + return 0; +} + +/* + * EBUSY - can't lock + * ENOENT - no existence of context + * + */ +int ast_add_extension(const char *context, int replace, const char *extension, + int priority, const char *label, const char *callerid, + const char *application, void *data, void (*datad)(void *), const char *registrar) +{ + int ret = -1; + struct ast_context *c = find_context_locked(context); + + if (c) { + ret = ast_add_extension2(c, replace, extension, priority, label, callerid, + application, data, datad, registrar); + ast_unlock_contexts(); + } + return ret; +} + +int ast_explicit_goto(struct ast_channel *chan, const char *context, const char *exten, int priority) +{ + if (!chan) + return -1; + + if (!ast_strlen_zero(context)) + ast_copy_string(chan->context, context, sizeof(chan->context)); + if (!ast_strlen_zero(exten)) + ast_copy_string(chan->exten, exten, sizeof(chan->exten)); + if (priority > -1) { + chan->priority = priority; + /* see flag description in channel.h for explanation */ + if (ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP)) + chan->priority--; + } + + return 0; +} + +int ast_async_goto(struct ast_channel *chan, const char *context, const char *exten, int priority) +{ + int res = 0; + + ast_channel_lock(chan); + + if (chan->pbx) { /* This channel is currently in the PBX */ + ast_explicit_goto(chan, context, exten, priority); + ast_softhangup_nolock(chan, AST_SOFTHANGUP_ASYNCGOTO); + } else { + /* In order to do it when the channel doesn't really exist within + the PBX, we have to make a new channel, masquerade, and start the PBX + at the new location */ + struct ast_channel *tmpchan = ast_channel_alloc(0); + if (!tmpchan) + res = -1; + else { + ast_string_field_build(tmpchan, name, "AsyncGoto/%s", chan->name); + ast_setstate(tmpchan, chan->_state); + /* Make formats okay */ + tmpchan->readformat = chan->readformat; + tmpchan->writeformat = chan->writeformat; + /* Setup proper location */ + ast_explicit_goto(tmpchan, + S_OR(context, chan->context), S_OR(exten, chan->exten), priority); + + /* Masquerade into temp channel */ + ast_channel_masquerade(tmpchan, chan); + + /* Grab the locks and get going */ + ast_channel_lock(tmpchan); + ast_do_masquerade(tmpchan); + ast_channel_unlock(tmpchan); + /* Start the PBX going on our stolen channel */ + if (ast_pbx_start(tmpchan)) { + ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmpchan->name); + ast_hangup(tmpchan); + res = -1; + } + } + } + ast_channel_unlock(chan); + return res; +} + +int ast_async_goto_by_name(const char *channame, const char *context, const char *exten, int priority) +{ + struct ast_channel *chan; + int res = -1; + + chan = ast_get_channel_by_name_locked(channame); + if (chan) { + res = ast_async_goto(chan, context, exten, priority); + ast_channel_unlock(chan); + } + return res; +} + +/*! \brief copy a string skipping whitespace */ +static int ext_strncpy(char *dst, const char *src, int len) +{ + int count=0; + + while (*src && (count < len - 1)) { + switch(*src) { + case ' ': + /* otherwise exten => [a-b],1,... doesn't work */ + /* case '-': */ + /* Ignore */ + break; + default: + *dst = *src; + dst++; + } + src++; + count++; + } + *dst = '\0'; + + return count; +} + +static void null_datad(void *foo) +{ +} + +/*! \brief add the extension in the priority chain. + * returns 0 on success, -1 on failure + */ +static int add_pri(struct ast_context *con, struct ast_exten *tmp, + struct ast_exten *el, struct ast_exten *e, int replace) +{ + struct ast_exten *ep; + + for (ep = NULL; e ; ep = e, e = e->peer) { + if (e->priority >= tmp->priority) + break; + } + if (!e) { /* go at the end, and ep is surely set because the list is not empty */ + ep->peer = tmp; + return 0; /* success */ + } + if (e->priority == tmp->priority) { + /* Can't have something exactly the same. Is this a + replacement? If so, replace, otherwise, bonk. */ + if (!replace) { + ast_log(LOG_WARNING, "Unable to register extension '%s', priority %d in '%s', already in use\n", tmp->exten, tmp->priority, con->name); + tmp->datad(tmp->data); + free(tmp); + return -1; + } + /* we are replacing e, so copy the link fields and then update + * whoever pointed to e to point to us + */ + tmp->next = e->next; /* not meaningful if we are not first in the peer list */ + tmp->peer = e->peer; /* always meaningful */ + if (ep) /* We're in the peer list, just insert ourselves */ + ep->peer = tmp; + else if (el) /* We're the first extension. Take over e's functions */ + el->next = tmp; + else /* We're the very first extension. */ + con->root = tmp; + if (tmp->priority == PRIORITY_HINT) + ast_change_hint(e,tmp); + /* Destroy the old one */ + e->datad(e->data); + free(e); + } else { /* Slip ourselves in just before e */ + tmp->peer = e; + tmp->next = e->next; /* extension chain, or NULL if e is not the first extension */ + if (ep) /* Easy enough, we're just in the peer list */ + ep->peer = tmp; + else { /* we are the first in some peer list, so link in the ext list */ + if (el) + el->next = tmp; /* in the middle... */ + else + con->root = tmp; /* ... or at the head */ + e->next = NULL; /* e is no more at the head, so e->next must be reset */ + } + /* And immediately return success. */ + if (tmp->priority == PRIORITY_HINT) + ast_add_hint(tmp); + } + return 0; +} + +/*! \brief + * Main interface to add extensions to the list for out context. + * + * We sort extensions in order of matching preference, so that we can + * stop the search as soon as we find a suitable match. + * This ordering also takes care of wildcards such as '.' (meaning + * "one or more of any character") and '!' (which is 'earlymatch', + * meaning "zero or more of any character" but also impacts the + * return value from CANMATCH and EARLYMATCH. + * + * The extension match rules defined in the devmeeting 2006.05.05 are + * quite simple: WE SELECT THE LONGEST MATCH. + * In detail, "longest" means the number of matched characters in + * the extension. In case of ties (e.g. _XXX and 333) in the length + * of a pattern, we give priority to entries with the smallest cardinality + * (e.g, [5-9] comes before [2-8] before the former has only 5 elements, + * while the latter has 7, etc. + * In case of same cardinality, the first element in the range counts. + * If we still have a tie, any final '!' will make this as a possibly + * less specific pattern. + * + * EBUSY - can't lock + * EEXIST - extension with the same priority exist and no replace is set + * + */ +int ast_add_extension2(struct ast_context *con, + int replace, const char *extension, int priority, const char *label, const char *callerid, + const char *application, void *data, void (*datad)(void *), + const char *registrar) +{ + /* + * Sort extensions (or patterns) according to the rules indicated above. + * These are implemented by the function ext_cmp()). + * All priorities for the same ext/pattern/cid are kept in a list, + * using the 'peer' field as a link field.. + */ + struct ast_exten *tmp, *e, *el = NULL; + int res; + int length; + char *p; + char expand_buf[VAR_BUF_SIZE] = { 0, }; + + /* if we are adding a hint, and there are global variables, and the hint + contains variable references, then expand them + */ + ast_mutex_lock(&globalslock); + if (priority == PRIORITY_HINT && AST_LIST_FIRST(&globals) && strstr(application, "${")) { + pbx_substitute_variables_varshead(&globals, application, expand_buf, sizeof(expand_buf)); + application = expand_buf; + } + ast_mutex_unlock(&globalslock); + + length = sizeof(struct ast_exten); + length += strlen(extension) + 1; + length += strlen(application) + 1; + if (label) + length += strlen(label) + 1; + if (callerid) + length += strlen(callerid) + 1; + else + length ++; /* just the '\0' */ + + /* Be optimistic: Build the extension structure first */ + if (datad == NULL) + datad = null_datad; + if (!(tmp = ast_calloc(1, length))) + return -1; + + /* use p as dst in assignments, as the fields are const char * */ + p = tmp->stuff; + if (label) { + tmp->label = p; + strcpy(p, label); + p += strlen(label) + 1; + } + tmp->exten = p; + p += ext_strncpy(p, extension, strlen(extension) + 1) + 1; + tmp->priority = priority; + tmp->cidmatch = p; /* but use p for assignments below */ + if (callerid) { + p += ext_strncpy(p, callerid, strlen(callerid) + 1) + 1; + tmp->matchcid = 1; + } else { + *p++ = '\0'; + tmp->matchcid = 0; + } + tmp->app = p; + strcpy(p, application); + tmp->parent = con; + tmp->data = data; + tmp->datad = datad; + tmp->registrar = registrar; + + ast_mutex_lock(&con->lock); + res = 0; /* some compilers will think it is uninitialized otherwise */ + for (e = con->root; e; el = e, e = e->next) { /* scan the extension list */ + res = ext_cmp(e->exten, extension); + if (res == 0) { /* extension match, now look at cidmatch */ + if (!e->matchcid && !tmp->matchcid) + res = 0; + else if (tmp->matchcid && !e->matchcid) + res = 1; + else if (e->matchcid && !tmp->matchcid) + res = -1; + else + res = strcasecmp(e->cidmatch, tmp->cidmatch); + } + if (res >= 0) + break; + } + if (e && res == 0) { /* exact match, insert in the pri chain */ + res = add_pri(con, tmp, el, e, replace); + ast_mutex_unlock(&con->lock); + if (res < 0) { + errno = EEXIST; /* XXX do we care ? */ + return 0; /* XXX should we return -1 maybe ? */ + } + } else { + /* + * not an exact match, this is the first entry with this pattern, + * so insert in the main list right before 'e' (if any) + */ + tmp->next = e; + if (el) + el->next = tmp; + else + con->root = tmp; + ast_mutex_unlock(&con->lock); + if (tmp->priority == PRIORITY_HINT) + ast_add_hint(tmp); + } + if (option_debug) { + if (tmp->matchcid) { + ast_log(LOG_DEBUG, "Added extension '%s' priority %d (CID match '%s') to %s\n", + tmp->exten, tmp->priority, tmp->cidmatch, con->name); + } else { + ast_log(LOG_DEBUG, "Added extension '%s' priority %d to %s\n", + tmp->exten, tmp->priority, con->name); + } + } else if (option_verbose > 2) { + if (tmp->matchcid) { + ast_verbose( VERBOSE_PREFIX_3 "Added extension '%s' priority %d (CID match '%s')to %s\n", + tmp->exten, tmp->priority, tmp->cidmatch, con->name); + } else { + ast_verbose( VERBOSE_PREFIX_3 "Added extension '%s' priority %d to %s\n", + tmp->exten, tmp->priority, con->name); + } + } + return 0; +} + +struct async_stat { + pthread_t p; + struct ast_channel *chan; + char context[AST_MAX_CONTEXT]; + char exten[AST_MAX_EXTENSION]; + int priority; + int timeout; + char app[AST_MAX_EXTENSION]; + char appdata[1024]; +}; + +static void *async_wait(void *data) +{ + struct async_stat *as = data; + struct ast_channel *chan = as->chan; + int timeout = as->timeout; + int res; + struct ast_frame *f; + struct ast_app *app; + + while (timeout && (chan->_state != AST_STATE_UP)) { + res = ast_waitfor(chan, timeout); + if (res < 1) + break; + if (timeout > -1) + timeout = res; + f = ast_read(chan); + if (!f) + break; + if (f->frametype == AST_FRAME_CONTROL) { + if ((f->subclass == AST_CONTROL_BUSY) || + (f->subclass == AST_CONTROL_CONGESTION) ) { + ast_frfree(f); + break; + } + } + ast_frfree(f); + } + if (chan->_state == AST_STATE_UP) { + if (!ast_strlen_zero(as->app)) { + app = pbx_findapp(as->app); + if (app) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Launching %s(%s) on %s\n", as->app, as->appdata, chan->name); + pbx_exec(chan, app, as->appdata); + } else + ast_log(LOG_WARNING, "No such application '%s'\n", as->app); + } else { + if (!ast_strlen_zero(as->context)) + ast_copy_string(chan->context, as->context, sizeof(chan->context)); + if (!ast_strlen_zero(as->exten)) + ast_copy_string(chan->exten, as->exten, sizeof(chan->exten)); + if (as->priority > 0) + chan->priority = as->priority; + /* Run the PBX */ + if (ast_pbx_run(chan)) { + ast_log(LOG_ERROR, "Failed to start PBX on %s\n", chan->name); + } else { + /* PBX will have taken care of this */ + chan = NULL; + } + } + } + free(as); + if (chan) + ast_hangup(chan); + return NULL; +} + +/*! Function to post an empty cdr after a spool call fails. + * + * This function posts an empty cdr for a failed spool call + * + */ +static int ast_pbx_outgoing_cdr_failed(void) +{ + /* allocate a channel */ + struct ast_channel *chan = ast_channel_alloc(0); + + if (!chan) + return -1; /* failure */ + + chan->cdr = ast_cdr_alloc(); /* allocate a cdr for the channel */ + + if (!chan->cdr) { + /* allocation of the cdr failed */ + ast_channel_free(chan); /* free the channel */ + return -1; /* return failure */ + } + + /* allocation of the cdr was successful */ + ast_cdr_init(chan->cdr, chan); /* initilize our channel's cdr */ + ast_cdr_start(chan->cdr); /* record the start and stop time */ + ast_cdr_end(chan->cdr); + ast_cdr_failed(chan->cdr); /* set the status to failed */ + ast_cdr_detach(chan->cdr); /* post and free the record */ + ast_channel_free(chan); /* free the channel */ + + return 0; /* success */ +} + +int ast_pbx_outgoing_exten(const char *type, int format, void *data, int timeout, const char *context, const char *exten, int priority, int *reason, int sync, const char *cid_num, const char *cid_name, struct ast_variable *vars, const char *account, struct ast_channel **channel) +{ + struct ast_channel *chan; + struct async_stat *as; + int res = -1, cdr_res = -1; + struct outgoing_helper oh; + pthread_attr_t attr; + + if (sync) { + LOAD_OH(oh); + chan = __ast_request_and_dial(type, format, data, timeout, reason, cid_num, cid_name, &oh); + if (channel) { + *channel = chan; + if (chan) + ast_channel_lock(chan); + } + if (chan) { + if (chan->cdr) { /* check if the channel already has a cdr record, if not give it one */ + ast_log(LOG_WARNING, "%s already has a call record??\n", chan->name); + } else { + chan->cdr = ast_cdr_alloc(); /* allocate a cdr for the channel */ + if (!chan->cdr) { + /* allocation of the cdr failed */ + free(chan->pbx); + res = -1; + goto outgoing_exten_cleanup; + } + /* allocation of the cdr was successful */ + ast_cdr_init(chan->cdr, chan); /* initilize our channel's cdr */ + ast_cdr_start(chan->cdr); + } + if (chan->_state == AST_STATE_UP) { + res = 0; + if (option_verbose > 3) + ast_verbose(VERBOSE_PREFIX_4 "Channel %s was answered.\n", chan->name); + + if (sync > 1) { + if (channel) + ast_channel_unlock(chan); + if (ast_pbx_run(chan)) { + ast_log(LOG_ERROR, "Unable to run PBX on %s\n", chan->name); + if (channel) + *channel = NULL; + ast_hangup(chan); + res = -1; + } + } else { + if (ast_pbx_start(chan)) { + ast_log(LOG_ERROR, "Unable to start PBX on %s\n", chan->name); + if (channel) { + *channel = NULL; + ast_channel_unlock(chan); + } + ast_hangup(chan); + res = -1; + } + } + } else { + if (option_verbose > 3) + ast_verbose(VERBOSE_PREFIX_4 "Channel %s was never answered.\n", chan->name); + + if(chan->cdr) { /* update the cdr */ + /* here we update the status of the call, which sould be busy. + * if that fails then we set the status to failed */ + if (ast_cdr_disposition(chan->cdr, chan->hangupcause)) + ast_cdr_failed(chan->cdr); + } + + if (channel) { + *channel = NULL; + ast_channel_unlock(chan); + } + ast_hangup(chan); + } + } + + if (res < 0) { /* the call failed for some reason */ + if (*reason == 0) { /* if the call failed (not busy or no answer) + * update the cdr with the failed message */ + cdr_res = ast_pbx_outgoing_cdr_failed(); + if (cdr_res != 0) { + res = cdr_res; + goto outgoing_exten_cleanup; + } + } + + /* create a fake channel and execute the "failed" extension (if it exists) within the requested context */ + /* check if "failed" exists */ + if (ast_exists_extension(chan, context, "failed", 1, NULL)) { + chan = ast_channel_alloc(0); + if (chan) { + ast_string_field_set(chan, name, "OutgoingSpoolFailed"); + if (!ast_strlen_zero(context)) + ast_copy_string(chan->context, context, sizeof(chan->context)); + set_ext_pri(chan, "failed", 1); + ast_set_variables(chan, vars); + if (account) + ast_cdr_setaccount(chan, account); + ast_pbx_run(chan); + } + } + } + } else { + if (!(as = ast_calloc(1, sizeof(*as)))) { + res = -1; + goto outgoing_exten_cleanup; + } + chan = ast_request_and_dial(type, format, data, timeout, reason, cid_num, cid_name); + if (channel) { + *channel = chan; + if (chan) + ast_channel_lock(chan); + } + if (!chan) { + free(as); + res = -1; + goto outgoing_exten_cleanup; + } + as->chan = chan; + ast_copy_string(as->context, context, sizeof(as->context)); + set_ext_pri(as->chan, exten, priority); + as->timeout = timeout; + ast_set_variables(chan, vars); + if (account) + ast_cdr_setaccount(chan, account); + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (ast_pthread_create(&as->p, &attr, async_wait, as)) { + ast_log(LOG_WARNING, "Failed to start async wait\n"); + free(as); + if (channel) { + *channel = NULL; + ast_channel_unlock(chan); + } + ast_hangup(chan); + res = -1; + goto outgoing_exten_cleanup; + } + res = 0; + } +outgoing_exten_cleanup: + ast_variables_destroy(vars); + return res; +} + +struct app_tmp { + char app[256]; + char data[256]; + struct ast_channel *chan; + pthread_t t; +}; + +/*! \brief run the application and free the descriptor once done */ +static void *ast_pbx_run_app(void *data) +{ + struct app_tmp *tmp = data; + struct ast_app *app; + app = pbx_findapp(tmp->app); + if (app) { + if (option_verbose > 3) + ast_verbose(VERBOSE_PREFIX_4 "Launching %s(%s) on %s\n", tmp->app, tmp->data, tmp->chan->name); + pbx_exec(tmp->chan, app, tmp->data); + } else + ast_log(LOG_WARNING, "No such application '%s'\n", tmp->app); + ast_hangup(tmp->chan); + free(tmp); + return NULL; +} + +int ast_pbx_outgoing_app(const char *type, int format, void *data, int timeout, const char *app, const char *appdata, int *reason, int sync, const char *cid_num, const char *cid_name, struct ast_variable *vars, const char *account, struct ast_channel **locked_channel) +{ + struct ast_channel *chan; + struct app_tmp *tmp; + int res = -1, cdr_res = -1; + struct outgoing_helper oh; + pthread_attr_t attr; + + memset(&oh, 0, sizeof(oh)); + oh.vars = vars; + oh.account = account; + + if (locked_channel) + *locked_channel = NULL; + if (ast_strlen_zero(app)) { + res = -1; + goto outgoing_app_cleanup; + } + if (sync) { + chan = __ast_request_and_dial(type, format, data, timeout, reason, cid_num, cid_name, &oh); + if (chan) { + if (chan->cdr) { /* check if the channel already has a cdr record, if not give it one */ + ast_log(LOG_WARNING, "%s already has a call record??\n", chan->name); + } else { + chan->cdr = ast_cdr_alloc(); /* allocate a cdr for the channel */ + if(!chan->cdr) { + /* allocation of the cdr failed */ + free(chan->pbx); + res = -1; + goto outgoing_app_cleanup; + } + /* allocation of the cdr was successful */ + ast_cdr_init(chan->cdr, chan); /* initilize our channel's cdr */ + ast_cdr_start(chan->cdr); + } + ast_set_variables(chan, vars); + if (account) + ast_cdr_setaccount(chan, account); + if (chan->_state == AST_STATE_UP) { + res = 0; + if (option_verbose > 3) + ast_verbose(VERBOSE_PREFIX_4 "Channel %s was answered.\n", chan->name); + tmp = ast_calloc(1, sizeof(*tmp)); + if (!tmp) + res = -1; + else { + ast_copy_string(tmp->app, app, sizeof(tmp->app)); + if (appdata) + ast_copy_string(tmp->data, appdata, sizeof(tmp->data)); + tmp->chan = chan; + if (sync > 1) { + if (locked_channel) + ast_channel_unlock(chan); + ast_pbx_run_app(tmp); + } else { + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (locked_channel) + ast_channel_lock(chan); + if (ast_pthread_create(&tmp->t, &attr, ast_pbx_run_app, tmp)) { + ast_log(LOG_WARNING, "Unable to spawn execute thread on %s: %s\n", chan->name, strerror(errno)); + free(tmp); + if (locked_channel) + ast_channel_unlock(chan); + ast_hangup(chan); + res = -1; + } else { + if (locked_channel) + *locked_channel = chan; + } + } + } + } else { + if (option_verbose > 3) + ast_verbose(VERBOSE_PREFIX_4 "Channel %s was never answered.\n", chan->name); + if (chan->cdr) { /* update the cdr */ + /* here we update the status of the call, which sould be busy. + * if that fails then we set the status to failed */ + if (ast_cdr_disposition(chan->cdr, chan->hangupcause)) + ast_cdr_failed(chan->cdr); + } + ast_hangup(chan); + } + } + + if (res < 0) { /* the call failed for some reason */ + if (*reason == 0) { /* if the call failed (not busy or no answer) + * update the cdr with the failed message */ + cdr_res = ast_pbx_outgoing_cdr_failed(); + if (cdr_res != 0) { + res = cdr_res; + goto outgoing_app_cleanup; + } + } + } + + } else { + struct async_stat *as; + if (!(as = ast_calloc(1, sizeof(*as)))) { + res = -1; + goto outgoing_app_cleanup; + } + chan = __ast_request_and_dial(type, format, data, timeout, reason, cid_num, cid_name, &oh); + if (!chan) { + free(as); + res = -1; + goto outgoing_app_cleanup; + } + as->chan = chan; + ast_copy_string(as->app, app, sizeof(as->app)); + if (appdata) + ast_copy_string(as->appdata, appdata, sizeof(as->appdata)); + as->timeout = timeout; + ast_set_variables(chan, vars); + if (account) + ast_cdr_setaccount(chan, account); + /* Start a new thread, and get something handling this channel. */ + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (locked_channel) + ast_channel_lock(chan); + if (ast_pthread_create(&as->p, &attr, async_wait, as)) { + ast_log(LOG_WARNING, "Failed to start async wait\n"); + free(as); + if (locked_channel) + ast_channel_unlock(chan); + ast_hangup(chan); + res = -1; + goto outgoing_app_cleanup; + } else { + if (locked_channel) + *locked_channel = chan; + } + res = 0; + } +outgoing_app_cleanup: + ast_variables_destroy(vars); + return res; +} + +void __ast_context_destroy(struct ast_context *con, const char *registrar) +{ + struct ast_context *tmp, *tmpl=NULL; + struct ast_include *tmpi; + struct ast_sw *sw; + struct ast_exten *e, *el, *en; + struct ast_ignorepat *ipi; + + ast_mutex_lock(&conlock); + for (tmp = contexts; tmp; ) { + struct ast_context *next; /* next starting point */ + for (; tmp; tmpl = tmp, tmp = tmp->next) { + ast_log(LOG_DEBUG, "check ctx %s %s\n", tmp->name, tmp->registrar); + if ( (!registrar || !strcasecmp(registrar, tmp->registrar)) && + (!con || !strcasecmp(tmp->name, con->name)) ) + break; /* found it */ + } + if (!tmp) /* not found, we are done */ + break; + ast_mutex_lock(&tmp->lock); + ast_log(LOG_DEBUG, "delete ctx %s %s\n", tmp->name, tmp->registrar); + next = tmp->next; + if (tmpl) + tmpl->next = next; + else + contexts = next; + /* Okay, now we're safe to let it go -- in a sense, we were + ready to let it go as soon as we locked it. */ + ast_mutex_unlock(&tmp->lock); + for (tmpi = tmp->includes; tmpi; ) { /* Free includes */ + struct ast_include *tmpil = tmpi; + tmpi = tmpi->next; + free(tmpil); + } + for (ipi = tmp->ignorepats; ipi; ) { /* Free ignorepats */ + struct ast_ignorepat *ipl = ipi; + ipi = ipi->next; + free(ipl); + } + while ((sw = AST_LIST_REMOVE_HEAD(&tmp->alts, list))) + free(sw); + for (e = tmp->root; e;) { + for (en = e->peer; en;) { + el = en; + en = en->peer; + destroy_exten(el); + } + el = e; + e = e->next; + destroy_exten(el); + } + ast_mutex_destroy(&tmp->lock); + free(tmp); + /* if we have a specific match, we are done, otherwise continue */ + tmp = con ? NULL : next; + } + ast_mutex_unlock(&conlock); +} + +void ast_context_destroy(struct ast_context *con, const char *registrar) +{ + __ast_context_destroy(con,registrar); +} + +static void wait_for_hangup(struct ast_channel *chan, void *data) +{ + int res; + struct ast_frame *f; + int waittime; + + if (ast_strlen_zero(data) || (sscanf(data, "%d", &waittime) != 1) || (waittime < 0)) + waittime = -1; + if (waittime > -1) { + ast_safe_sleep(chan, waittime * 1000); + } else do { + res = ast_waitfor(chan, -1); + if (res < 0) + return; + f = ast_read(chan); + if (f) + ast_frfree(f); + } while(f); +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_progress(struct ast_channel *chan, void *data) +{ + ast_indicate(chan, AST_CONTROL_PROGRESS); + return 0; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_ringing(struct ast_channel *chan, void *data) +{ + ast_indicate(chan, AST_CONTROL_RINGING); + return 0; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_busy(struct ast_channel *chan, void *data) +{ + ast_indicate(chan, AST_CONTROL_BUSY); + /* Don't change state of an UP channel, just indicate + busy in audio */ + if (chan->_state != AST_STATE_UP) + ast_setstate(chan, AST_STATE_BUSY); + wait_for_hangup(chan, data); + return -1; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_congestion(struct ast_channel *chan, void *data) +{ + ast_indicate(chan, AST_CONTROL_CONGESTION); + /* Don't change state of an UP channel, just indicate + congestion in audio */ + if (chan->_state != AST_STATE_UP) + ast_setstate(chan, AST_STATE_BUSY); + wait_for_hangup(chan, data); + return -1; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_answer(struct ast_channel *chan, void *data) +{ + int delay = 0; + int res; + + if (chan->_state == AST_STATE_UP) + delay = 0; + else if (!ast_strlen_zero(data)) + delay = atoi(data); + + res = ast_answer(chan); + if (res) + return res; + + if (delay) + res = ast_safe_sleep(chan, delay); + + return res; +} + +AST_APP_OPTIONS(resetcdr_opts, { + AST_APP_OPTION('w', AST_CDR_FLAG_POSTED), + AST_APP_OPTION('a', AST_CDR_FLAG_LOCKED), + AST_APP_OPTION('v', AST_CDR_FLAG_KEEP_VARS), +}); + +/*! + * \ingroup applications + */ +static int pbx_builtin_resetcdr(struct ast_channel *chan, void *data) +{ + char *args; + struct ast_flags flags = { 0 }; + + if (!ast_strlen_zero(data)) { + args = ast_strdupa(data); + ast_app_parse_options(resetcdr_opts, &flags, NULL, args); + } + + ast_cdr_reset(chan->cdr, &flags); + + return 0; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_setamaflags(struct ast_channel *chan, void *data) +{ + /* Copy the AMA Flags as specified */ + ast_cdr_setamaflags(chan, data ? data : ""); + return 0; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_hangup(struct ast_channel *chan, void *data) +{ + if (!ast_strlen_zero(data)) { + int cause; + char *endptr; + + if ((cause = ast_str2cause(data)) > -1) { + chan->hangupcause = cause; + return -1; + } + + cause = strtol((const char *) data, &endptr, 10); + if (cause != 0 || (data != endptr)) { + chan->hangupcause = cause; + return -1; + } + + ast_log(LOG_NOTICE, "Invalid cause given to Hangup(): \"%s\"\n", (char *) data); + } + + if (!chan->hangupcause) { + chan->hangupcause = AST_CAUSE_NORMAL_CLEARING; + } + + return -1; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_gotoiftime(struct ast_channel *chan, void *data) +{ + int res=0; + char *s, *ts; + struct ast_timing timing; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "GotoIfTime requires an argument:\n