diff options
author | An-Cheng Huang <ancheng@vyatta.com> | 2010-07-28 14:30:32 -0700 |
---|---|---|
committer | An-Cheng Huang <ancheng@vyatta.com> | 2010-07-28 14:30:32 -0700 |
commit | 639c835bc2730a4fbffd915f5b2028a68375ee7a (patch) | |
tree | 203d61e1d5e8ef422d6aba3851d2f83a1f838b6b | |
parent | 0247864ef578ac05bbac8dc5175e674ce7b82714 (diff) | |
download | vyatta-cfg-639c835bc2730a4fbffd915f5b2028a68375ee7a.tar.gz vyatta-cfg-639c835bc2730a4fbffd915f5b2028a68375ee7a.zip |
add new cstore library
52 files changed, 7331 insertions, 3863 deletions
@@ -9,7 +9,6 @@ libtool /aclocal.m4 /autom4te.cache -/build-stamp /config /config.log /config.guess @@ -17,12 +16,15 @@ libtool /config.sub /configure /debian/files +/debian/tmp /debian/vyatta-cfg /debian/vyatta-cfg.postinst /debian/vyatta-cfg.postrm +/debian/libvyatta-cfg-dev /debian/*.log /debian/*.substvars /debian/*.debhelper +/debian/stamp-* /INSTALL /Makefile.in /Makefile diff --git a/Makefile.am b/Makefile.am index 41dbdb8..b38f0fd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,3 +1,5 @@ +SUBDIRS = . perl_dmod + share_perl5dir = /opt/vyatta/share/perl5/Vyatta completiondir = /etc/bash_completion.d initddir = /etc/init.d @@ -8,6 +10,7 @@ dhcphookdir = /etc/dhcp3/dhclient-exit-hooks.d enumdir = $(datadir)/enumeration AM_CFLAGS = -I src -Wall -I /usr/include/glib-2.0 -I /usr/lib/glib-2.0/include +AM_CXXFLAGS = -I src -Wall -Werror AM_YFLAGS = -d --name-prefix=yy_`basename $* .y`_ AM_LFLAGS = --prefix=yy_`basename $* .l`_ -olex.yy.c @@ -18,32 +21,50 @@ dhcphook_SCRIPTS = scripts/vyatta-dhclient-hook lib_LTLIBRARIES = src/libvyatta-cfg.la src_libvyatta_cfg_la_LIBADD = /usr/lib/libglib-2.0.la src_libvyatta_cfg_la_LIBADD += /usr/lib/libgio-2.0.la +src_libvyatta_cfg_la_LIBADD += -lboost_system +src_libvyatta_cfg_la_LIBADD += -lboost_filesystem src_libvyatta_cfg_la_LDFLAGS = -version-info 1:0:0 -src_libvyatta_cfg_la_SOURCES = src/cli_parse.y src/cli_def.l src/cli_val.l \ - src/cli_new.c src/cli_path_utils.c \ - src/common/common.c src/common/unionfs.c \ - src/cli_val_engine.c src/cli_objects.c +src_libvyatta_cfg_la_SOURCES = src/cli_parse.y src/cli_def.l src/cli_val.l +src_libvyatta_cfg_la_SOURCES += src/cli_new.c src/cli_path_utils.c +src_libvyatta_cfg_la_SOURCES += src/common/common.c src/common/unionfs.c +src_libvyatta_cfg_la_SOURCES += src/cli_val_engine.c src/cli_objects.c +src_libvyatta_cfg_la_SOURCES += src/cstore/cstore-c.cpp +src_libvyatta_cfg_la_SOURCES += src/cstore/cstore.cpp +src_libvyatta_cfg_la_SOURCES += src/cstore/cstore-varref.cpp +src_libvyatta_cfg_la_SOURCES += src/cstore/unionfs/cstore-unionfs.cpp CLEANFILES = src/cli_parse.c src/cli_parse.h src/cli_def.c src/cli_val.c LDADD = src/libvyatta-cfg.la LDADD += /usr/lib/libglib-2.0.la +vincludedir = $(includedir)/vyatta-cfg +vinclude_HEADERS = src/cli_val.h +vinclude_HEADERS += src/cli_val_engine.h +vinclude_HEADERS += src/cli_path_utils.h + +vcincdir = $(vincludedir)/cstore +vcinc_HEADERS = src/cstore/cstore-c.h +vcinc_HEADERS += src/cstore/cstore.hpp + +vcuincdir = $(vcincdir)/unionfs +vcuinc_HEADERS = src/cstore/unionfs/cstore-unionfs.hpp + sbin_PROGRAMS = src/priority -sbin_PROGRAMS += src/my_commit2 +sbin_PROGRAMS += src/my_commit sbin_PROGRAMS += src/exe_action sbin_PROGRAMS += src/dump -sbin_PROGRAMS += src/my_delete -sbin_PROGRAMS += src/my_set sbin_PROGRAMS += src/check_tmpl sbin_PROGRAMS += src/net_set +sbin_PROGRAMS += src/my_cli_bin +sbin_PROGRAMS += src/my_cli_shell_api src_priority_SOURCES = src/priority.c -src_my_commit2_SOURCES = src/commit2.c +src_my_commit_SOURCES = src/commit2.c src_exe_action_SOURCES = src/exe_action.c src_dump_SOURCES = src/dump_session.c -src_my_delete_SOURCES = src/delete.c -src_my_set_SOURCES = src/set.c src_check_tmpl_SOURCES = src/check_tmpl.c -src_net_set = src/net_set.c +src_net_set_SOURCES = src/net_set.c +src_my_cli_bin_SOURCES = src/cli_bin.cpp +src_my_cli_shell_api_SOURCES = src/cli_shell_api.cpp sbin_SCRIPTS = scripts/vyatta-cfg-cmd-wrapper sbin_SCRIPTS += scripts/vyatta-validate-type.pl @@ -55,8 +76,6 @@ sbin_SCRIPTS += scripts/vyatta-cli-expand-var.pl sbin_SCRIPTS += scripts/vyatta-output-config.pl sbin_SCRIPTS += scripts/vyatta-save-config.pl sbin_SCRIPTS += scripts/vyatta-load-config.pl -sbin_SCRIPTS += scripts/vyatta-activate-config.pl -sbin_SCRIPTS += scripts/vyatta-comment-config.pl sbin_SCRIPTS += scripts/vyatta-cfg-notify sbin_SCRIPTS += scripts/vyatta-irqaffin sbin_SCRIPTS += scripts/vyatta-auto-irqaffin.pl @@ -68,7 +87,6 @@ share_perl5_DATA = lib/Vyatta/Config.pm share_perl5_DATA += lib/Vyatta/Misc.pm share_perl5_DATA += lib/Vyatta/Interface.pm share_perl5_DATA += lib/Vyatta/TypeChecker.pm -share_perl5_DATA += lib/Vyatta/ConfigDOMTree.pm share_perl5_DATA += lib/Vyatta/ConfigOutput.pm share_perl5_DATA += lib/Vyatta/ConfigLoad.pm share_perl5_DATA += lib/Vyatta/Keepalived.pm @@ -87,4 +105,14 @@ install-exec-hook: mkdir -p $(DESTDIR)$(commit_run_dir) mkdir -p $(DESTDIR)$(etc_shell_leveldir) cd etc/shell/level; $(cpiop) $(DESTDIR)$(etc_shell_leveldir) - cd $(DESTDIR)/opt/vyatta/sbin; ln -s my_commit2 my_commit + cd $(DESTDIR)/opt/vyatta/sbin; \ + ln -sf my_cli_bin my_set; \ + ln -sf my_cli_bin my_delete; \ + ln -sf my_cli_bin my_activate; \ + ln -sf my_cli_bin my_deactivate; \ + ln -sf my_cli_bin my_rename; \ + ln -sf my_cli_bin my_copy; \ + ln -sf my_cli_bin my_comment; \ + ln -sf my_cli_bin my_discard; \ + ln -sf my_cli_bin my_move + diff --git a/configure.ac b/configure.ac index cc7a15a..0a8b77b 100644 --- a/configure.ac +++ b/configure.ac @@ -16,6 +16,7 @@ AM_INIT_AUTOMAKE([gnu no-dist-gzip dist-bzip2 subdir-objects]) AC_PREFIX_DEFAULT([/opt/vyatta]) AC_PROG_CC +AC_PROG_CXX AM_PROG_AS AM_PROG_CC_C_O AC_PROG_LIBTOOL @@ -29,6 +30,7 @@ AC_ARG_ENABLE([nostrip], AC_CONFIG_FILES( [Makefile] + [perl_dmod/Makefile] [debian/vyatta-cfg.postinst] [debian/vyatta-cfg.postrm]) diff --git a/debian/autogen.sh b/debian/autogen.sh deleted file mode 100755 index e8c94af..0000000 --- a/debian/autogen.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - - -rm -rf config -rm -f aclocal.m4 config.guess config.statusconfig.sub configure INSTALL - -autoreconf --force --install - -rm -f config.sub config.guess -ln -s /usr/share/misc/config.sub . -ln -s /usr/share/misc/config.guess . diff --git a/debian/control b/debian/control index d467832..19e2c76 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,8 @@ Source: vyatta-cfg Section: contrib/net Priority: extra Maintainer: Vyatta Package Maintainers <maintainers@vyatta.com> -Build-Depends: debhelper (>= 5), autotools-dev, libglib2.0-dev +Build-Depends: debhelper (>= 5), autotools-dev, libglib2.0-dev, + libboost-filesystem1.40-dev Standards-Version: 3.7.2 Package: vyatta-cfg @@ -26,7 +27,8 @@ Depends: sed (>= 4.1.5), ethtool, iproute, libglib2.0-0, - curl + curl, + libboost-filesystem1.40.0 Replaces: vyatta-cfg-firewall, vyatta-cfg-quagga Suggests: util-linux (>= 2.13-5), @@ -35,5 +37,15 @@ Suggests: util-linux (>= 2.13-5), ntpdate Description: Vyatta configuration system This package has the Vyatta configuration system, including the configuration - back-end, the base configuration templates, and the config-mode CLI completion - mechanism. + back-end library, the base configuration templates, and the config-mode CLI + completion mechanism. + +Package: libvyatta-cfg-dev +Architecture: any +Priority: optional +Section: libdevel +Depends: vyatta-cfg (=${binary:Version}) +Description: vyatta-cfg development package + Development header and library files for the Vyatta configuration back-end + library. + diff --git a/debian/libvyatta-cfg-dev.install b/debian/libvyatta-cfg-dev.install new file mode 100644 index 0000000..450792e --- /dev/null +++ b/debian/libvyatta-cfg-dev.install @@ -0,0 +1,3 @@ +usr/include +usr/lib/*.so +usr/lib/*.*a diff --git a/debian/linda b/debian/linda deleted file mode 100644 index 0381d9d..0000000 --- a/debian/linda +++ /dev/null @@ -1 +0,0 @@ -Tag: file-in-opt diff --git a/debian/lintian b/debian/lintian deleted file mode 100644 index 70110bf..0000000 --- a/debian/lintian +++ /dev/null @@ -1,6 +0,0 @@ -vyatta-cfg: file-in-unusual-dir -vyatta-cfg: dir-or-file-in-opt -vyatta-cfg: binary-or-shlib-defines-rpath ./opt/vyatta/sbin/my_set /opt/vyatta/lib -vyatta-cfg: binary-or-shlib-defines-rpath ./opt/vyatta/sbin/my_commit /opt/vyatta/lib -vyatta-cfg: binary-or-shlib-defines-rpath ./opt/vyatta/sbin/my_delete /opt/vyatta/lib -vyatta-cfg: binary-or-shlib-defines-rpath ./opt/vyatta/sbin/check_tmpl /opt/vyatta/lib diff --git a/debian/rules b/debian/rules index 6b9bdf7..e5353c9 100755 --- a/debian/rules +++ b/debian/rules @@ -1,108 +1,45 @@ #!/usr/bin/make -f -# -*- makefile -*- -# Sample debian/rules that uses debhelper. -# This file was originally written by Joey Hess and Craig Small. -# As a special exception, when this file is copied by dh-make into a -# dh-make output file, you may use that output file without restriction. -# This special exception was added by Craig Small in version 0.37 of dh-make. -# Uncomment this to turn on verbose mode. -#export DH_VERBOSE=1 - -# Use hardening options (in future) +## uncomment to enable hardening #export DEB_BUILD_HARDENING=1 -# These are used for cross-compiling and for saving the configure script -# from having to guess our platform (since we know it already) -DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) -DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) -PACKAGE=vyatta-cfg -PKGDIR=$(CURDIR)/debian/$(PACKAGE) - -CFLAGS = -Wall -g - -configure = ./configure -configure += --host=$(DEB_HOST_GNU_TYPE) -configure += --build=$(DEB_BUILD_GNU_TYPE) -configure += --prefix=/opt/vyatta -configure += --mandir=\$${prefix}/share/man -configure += --infodir=\$${prefix}/share/info -configure += CFLAGS="$(CFLAGS)" -configure += LDFLAGS="-Wl,-z,defs" - -ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) - CFLAGS += -O0 -else - CFLAGS += -Os -endif - -configure: configure.ac Makefile.am - chmod +x debian/autogen.sh - debian/autogen.sh - -config.status: configure - dh_testdir - rm -f config.cache - $(configure) - -build: build-stamp - -build-stamp: config.status - dh_testdir - $(MAKE) - touch $@ - -clean: clean-patched +cfg_opts := --prefix=/opt/vyatta +cfg_opts += --libdir=/usr/lib +cfg_opts += --includedir=/usr/include +cfg_opts += --mandir=\$${prefix}/share/man +cfg_opts += --infodir=\$${prefix}/share/info +cfg_opts += CFLAGS="$(CFLAGS)" +cfg_opts += LDFLAGS="-Wl,-z,defs" +inst_opts := --sourcedir=debian/tmp -# Clean everything up, including everything auto-generated -# at build time that needs not to be kept around in the Debian diff -clean-patched: - dh_testdir - dh_testroot - if test -f Makefile ; then $(MAKE) clean distclean ; fi - rm -f build-stamp - rm -f config.status config.sub config.guess config.log - rm -f aclocal.m4 configure Makefile.in Makefile INSTALL - rm -rf config - dh_clean +clean: + dh clean -install: build - dh_testdir - dh_testroot - dh_clean -k - dh_installdirs - - $(MAKE) DESTDIR=$(PKGDIR) install - - install -D --mode=0644 debian/lintian $(PKGDIR)/usr/share/lintian/overrides/$(PACKAGE) - install -D --mode=0644 debian/linda $(PKGDIR)/usr/share/linda/overrides/$(PACKAGE) - -# Build architecture-independent files here. -binary-indep: build install +binary binary-arch binary-indep: install rm -f debian/files - dh_testdir - dh_testroot - dh_installchangelogs ChangeLog - dh_installdocs - dh_install - dh_installdebconf - dh_link - dh_strip - dh_compress - dh_fixperms - dh_installdeb + dh binary --before dh_gencontrol + rm -f debian/*/DEBIAN/conffiles if [ -f "../.VYATTA_DEV_BUILD" ]; then \ dh_gencontrol -- -v999.dev; \ else \ dh_gencontrol; \ fi - dh_md5sums - dh_builddeb + dh binary --after dh_gencontrol -# Build architecture-dependent files here. -binary-arch: build install -# This is an architecture independent package -# so; we have nothing to do by default. +build: Makefile +build: + rm -f debian/*.debhelper* + dh build --before configure + dh build --after configure --before dh_auto_test + dh build --after dh_auto_test + +Makefile: Makefile.in + ./configure $(cfg_opts) + +Makefile.in: Makefile.am configure.ac + autoreconf -i --force + +install: build + dh install --before dh_install + dh_install $(inst_opts) -binary: binary-indep binary-arch -.PHONY: build clean binary-indep binary-arch binary install diff --git a/debian/vyatta-cfg.install b/debian/vyatta-cfg.install new file mode 100644 index 0000000..a41d29e --- /dev/null +++ b/debian/vyatta-cfg.install @@ -0,0 +1,3 @@ +usr/lib/*.so.* +opt +etc diff --git a/debian/vyatta-cfg.lintian-overrides b/debian/vyatta-cfg.lintian-overrides new file mode 100644 index 0000000..ee5e9e1 --- /dev/null +++ b/debian/vyatta-cfg.lintian-overrides @@ -0,0 +1,5 @@ +vyatta-cfg: file-in-unusual-dir +vyatta-cfg: dir-or-file-in-opt +vyatta-cfg: package-name-doesnt-match-sonames +vyatta-cfg: file-in-etc-not-marked-as-conffile +vyatta-cfg: init.d-script-not-marked-as-conffile diff --git a/debian/vyatta-cfg.postinst.in b/debian/vyatta-cfg.postinst.in index 48b889d..c307803 100644 --- a/debian/vyatta-cfg.postinst.in +++ b/debian/vyatta-cfg.postinst.in @@ -1,10 +1,24 @@ #!/bin/bash prefix=@prefix@ +exec_prefix=@exec_prefix@ sysconfdir=@sysconfdir@ +sbindir=@sbindir@ -mkdir -m 0775 -p $sysconfdir/config $prefix/config -chgrp vyattacfg $sysconfdir/config $prefix/config 2>/dev/null +if [ "$1" = "configure" ]; then + ldconfig +fi + +for dir in $sysconfdir/config $prefix/config; do + if [ -d "$dir" ]; then + # already exists + chmod 2775 $dir + else + # create it + mkdir -m 2775 -p $dir + fi + chgrp vyattacfg $dir 2>/dev/null +done update-rc.d vyatta-router defaults 90 >/dev/null @@ -16,3 +30,10 @@ if [ "$sysconfdir" != "/etc" ]; then touch /etc/$conf done fi + +# capability stuff +for bin in my_cli_bin my_cli_shell_api; do + touch -ac $sbindir/$bin + setcap cap_sys_admin=pe $sbindir/$bin +done + diff --git a/debian/vyatta-cfg.postrm.in b/debian/vyatta-cfg.postrm.in index c211589..2e50b32 100644 --- a/debian/vyatta-cfg.postrm.in +++ b/debian/vyatta-cfg.postrm.in @@ -1,5 +1,9 @@ #!/bin/bash +if [ "$1" = "remove" ]; then + ldconfig +fi + if [ "$1" = "purge" ]; then update-rc.d vyatta-router remove >/dev/null || exit $? fi diff --git a/etc/bash_completion.d/20vyatta-cfg b/etc/bash_completion.d/20vyatta-cfg index b36c3cf..56c1272 100755 --- a/etc/bash_completion.d/20vyatta-cfg +++ b/etc/bash_completion.d/20vyatta-cfg @@ -17,7 +17,7 @@ # Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, # MA 02110-1301, USA. # -# Author: An-Cheng Huang +# Author: Vyatta # Description: bash completion for Vyatta configuration commands # # **** End License **** @@ -35,12 +35,42 @@ if [ "$_OFR_CONFIGURE" != "ok" ]; then return 0 fi -umask 0002 - if [ -r /etc/default/vyatta ]; then source /etc/default/vyatta fi +# function for shell api +vyatta_cli_shell_api () +{ + local noeval='' + if [ "$1" == NOEVAL ]; then + noeval=true + shift + fi + local outstr + if ! outstr=$(${vyatta_sbindir}/my_cli_shell_api "$@"); then + # display the error output (if any) and then fail + if [ -n "$outstr" ]; then + echo "$outstr" + fi + return 1 + fi + # eval the output (if any) + if [ -n "$outstr" ]; then + if [ -n "$noeval" ]; then + echo "$outstr" + else + eval "$outstr" + fi + fi + return 0 +} + +# set up the session environment +## note: this can not use vyatta_cli_shell_api() above since it "declares" +## env vars. +eval "$(${vyatta_sbindir}/my_cli_shell_api getSessionEnv $$)" + declare is_set=0 declare last_idx=0 declare -a comp_words=() @@ -63,48 +93,29 @@ show () args[${#args[@]}]="$arg" fi done + local level=$(vyatta_cli_shell_api NOEVAL getEditLevelStr) ${vyatta_sbindir}/vyatta-output-config.pl ${show_all} \ - ${VYATTA_EDIT_LEVEL//\// } ${args[@]} \ + $level ${args[@]} \ | eval "${VYATTA_PAGER:-cat}" } commit () { - /opt/vyatta/sbin/my_commit $@ - if [ $? == 0 ]; then - touch $VYATTA_CHANGES_ONLY_DIR/.unsaved - fi + if /opt/vyatta/sbin/my_commit "$@"; then + vyatta_cli_shell_api markSessionUnsaved + fi } save () { - eval "sudo sg vyattacfg \"umask 0002 ; ${vyatta_sbindir}/vyatta-save-config.pl $@\"" - rm -f $VYATTA_CHANGES_ONLY_DIR/.unsaved -} - -discard () -{ - local changes - if [ -f "$VYATTA_TEMP_CONFIG_DIR/$VYATTA_MOD_NAME" ]; then - changes=1 - else - changes=0 - fi - - sudo umount $VYATTA_TEMP_CONFIG_DIR - sudo rm -fr $VYATTA_CHANGES_ONLY_DIR $VYATTA_TEMP_CONFIG_DIR - make_vyatta_config_dir $VYATTA_CHANGES_ONLY_DIR - make_vyatta_config_dir $VYATTA_TEMP_CONFIG_DIR - sudo mount -t $UNIONFS \ - -o dirs=${VYATTA_CHANGES_ONLY_DIR}=rw:${VYATTA_ACTIVE_CONFIGURATION_DIR}=ro \ - $UNIONFS ${VYATTA_TEMP_CONFIG_DIR} - - if (( changes )); then - echo "Changes have been discarded" - else - echo "No changes have been discarded" - fi - + # transform individual args into quoted strings + local arg='' + local save_cmd="${vyatta_sbindir}/vyatta-save-config.pl" + for arg in "$@"; do + save_cmd+=" '$arg'" + done + eval "sudo sg vyattacfg \"umask 0002 ; $save_cmd\"" + vyatta_cli_shell_api unmarkSessionUnsaved } reboot () @@ -117,216 +128,68 @@ shutdown () echo "Exit from configure mode before shutting down system." } -declare vyatta_cfg_prompt_level='' -set_config_ps1 () +reset_edit_level () { - local level=$1 - vyatta_unescape level level - if [ -z "$level" ]; then - export PS1="[edit]\n\u@\h# " - vyatta_cfg_prompt_level='' - else - export PS1="[edit $level]\n\u@\h# " - vyatta_cfg_prompt_level="$level" - fi + vyatta_cli_shell_api getEditResetEnv + return $? } load () { # don't load if there are uncommitted changes. - if [ -f "$VYATTA_TEMP_CONFIG_DIR/$VYATTA_MOD_NAME" ]; then + if vyatta_cli_shell_api sessionChanged; then echo "Cannot load: configuration modified." echo "Commit or discard the changes before loading a config file." return 1 fi # return to top level. - export VYATTA_EDIT_LEVEL="/" - export VYATTA_TEMPLATE_LEVEL="/" - set_config_ps1 '' - eval "${vyatta_sbindir}/vyatta-load-config.pl $@" + reset_edit_level + ${vyatta_sbindir}/vyatta-load-config.pl "$@" } merge () { # don't load if there are uncommitted changes. - if [ -f "$VYATTA_TEMP_CONFIG_DIR/$VYATTA_MOD_NAME" ]; then + if vyatta_cli_shell_api sessionChanged; then echo "Cannot load: configuration modified." echo "Commit or discard the changes before loading a config file." return 1 fi # return to top level. - export VYATTA_EDIT_LEVEL="/" - export VYATTA_TEMPLATE_LEVEL="/" - set_config_ps1 '' - eval "${vyatta_sbindir}/vyatta-load-config.pl $@ --merge" + reset_edit_level + ${vyatta_sbindir}/vyatta-load-config.pl "$@" --merge } top () { - if [ "$VYATTA_EDIT_LEVEL" == "/" ]; then + if vyatta_cli_shell_api editLevelAtRoot; then echo "Already at the top level" return 0 fi # go to the top level. - export VYATTA_EDIT_LEVEL="/" - export VYATTA_TEMPLATE_LEVEL="/" - set_config_ps1 '' -} - -rename() -{ - mvcp rename Rename mv "$@" -} - -copy() -{ - mvcp copy Copy "cp -a" "$@" -} - -mvcp () -{ - local str=$1 - shift - local Str=$1 - shift - local cmd=$1 - shift - local _otag=$1 - local _ovalu=$2 - local _to=$3 - local _ntag=$4 - local _nvalu=$5 - local _oval='' - local _nval='' - local _mpath=${VYATTA_TEMP_CONFIG_DIR}/${VYATTA_EDIT_LEVEL} - local _tpath=${VYATTA_CONFIG_TEMPLATE}/${VYATTA_TEMPLATE_LEVEL} - vyatta_escape _ovalu _oval - vyatta_escape _nvalu _nval - if [ "$_to" != 'to' ] || [ -z "$_ntag" ] || [ -z "$_nval" ]; then - echo "Invalid $str command" - return 1 - fi - if [ "$_otag" != "$_ntag" ]; then - echo "Cannot $str from \"$_otag\" to \"$_ntag\"" - return 1 - fi - if [ ! -d "$_tpath/$_otag/$VYATTA_TAG_NAME" ]; then - echo "Cannot $str under \"$_otag\"" - return 1 - fi - if [ ! -d "$_mpath/$_otag/$_oval" ]; then - echo "Configuration \"$_otag $_ovalu\" does not exist" - return 1 - fi - if [ -d "$_mpath/$_ntag/$_nval" ]; then - echo "Configuration \"$_ntag $_nvalu\" already exists" - return 1 - fi - if ! /opt/vyatta/sbin/my_set $_ntag "$_nvalu"; then - echo "$Str failed" - return 1 - fi - /opt/vyatta/sbin/my_delete $_ntag "$_nvalu" >&/dev/null 3>&1 - - $cmd "$_mpath/$_otag/$_oval" "$_mpath/$_ntag/$_nval" - - return 0 + reset_edit_level } edit () { - local num_comp=${#@} - local _mpath=${VYATTA_TEMP_CONFIG_DIR}/${VYATTA_EDIT_LEVEL} - local _tpath=${VYATTA_CONFIG_TEMPLATE}/${VYATTA_TEMPLATE_LEVEL} - local idx - if ! /opt/vyatta/sbin/my_set $* >&/dev/null 3>&1; then - echo "Invalid node \"$*\" for the 'edit' command" - return 1 - fi - for (( idx=1; idx <= num_comp; idx++ )); do - local comp - eval "comp=\$$idx" - vyatta_escape comp comp - push_path _mpath $comp - push_path _tpath $comp - if [ ! -d $_mpath ]; then - # "edit" only allows existing node - break - fi - - # check if it's not tag value - if [ -d $_tpath ]; then - continue - fi - - # check if it's tag value - pop_path _tpath - push_path _tpath $VYATTA_TAG_NAME - if [ -d $_tpath ]; then - continue - fi - pop_path _tpath - pop_path _mpath - break - done - # "edit" only valid for - # * "node.tag" level - # * "node.def" level without "type:" - if (( idx != ( num_comp + 1) )); then - echo "Invalid node \"$*\" for the 'edit' command" - return 1 - fi - if [ "${_tpath:((-9))}" != "/node.tag" ]; then - # we are not at "node.tag" level. look for "type:". - if [ ! -r "$_tpath/node.def" ]; then - vyatta_cfg_type="" - else - vyatta_parse_tmpl "$_tpath/node.def" - fi - if [ -n "$vyatta_cfg_type" ]; then - # "type:" present - echo "The 'edit' command cannot be issued at the \"$*\" level" - return 1 - fi - fi - export VYATTA_EDIT_LEVEL="${_mpath#$VYATTA_TEMP_CONFIG_DIR}/" - export VYATTA_TEMPLATE_LEVEL="${_tpath#$VYATTA_CONFIG_TEMPLATE}/" - - declare -a path_arr - path_str2arr VYATTA_EDIT_LEVEL path_arr - local path_str="${path_arr[*]}" - set_config_ps1 "$path_str" + vyatta_cli_shell_api getEditEnv "$@" + return $? } up () { - if [ "$VYATTA_EDIT_LEVEL" == "/" ]; then - echo "Already at the top level" - return 0 - fi - if [[ $VYATTA_TEMPLATE_LEVEL == */node.tag/ ]]; then - export VYATTA_EDIT_LEVEL="${VYATTA_EDIT_LEVEL%/*/*/}/" - export VYATTA_TEMPLATE_LEVEL="${VYATTA_TEMPLATE_LEVEL%/*/*/}/" - else - export VYATTA_EDIT_LEVEL="${VYATTA_EDIT_LEVEL%/*/}/" - export VYATTA_TEMPLATE_LEVEL="${VYATTA_TEMPLATE_LEVEL%/*/}/" - fi - - declare -a path_arr - path_str2arr VYATTA_EDIT_LEVEL path_arr - local path_str="${path_arr[*]}" - set_config_ps1 "$path_str" + vyatta_cli_shell_api getEditUpEnv "$@" + return $? } really_exit() { - if [ -f $VYATTA_CHANGES_ONLY_DIR/.unsaved ]; then - echo "Warning: configuration changes have not been saved." + + if vyatta_cli_shell_api sessionUnsaved; then + echo "Warning: configuration changes have not been saved." fi - sudo umount $VYATTA_TEMP_CONFIG_DIR - sudo rm -rf $VYATTA_TEMP_CONFIG_DIR $VYATTA_CHANGES_ONLY_DIR \ - $VYATTA_CONFIG_TMP + vyatta_cli_shell_api teardownSession unset _OFR_CONFIGURE builtin exit 0 } @@ -343,9 +206,9 @@ exit () return 1 fi - if [ "$VYATTA_EDIT_LEVEL" == "/" ]; then + if vyatta_cli_shell_api editLevelAtRoot; then # we are at the root level. check if we can really exit. - if [ -f "$VYATTA_TEMP_CONFIG_DIR/$VYATTA_MOD_NAME" ]; then + if vyatta_cli_shell_api sessionChanged; then if (( ! discard )); then echo "Cannot exit: configuration modified." echo "Use 'exit discard' to discard the changes and exit." @@ -356,9 +219,7 @@ exit () fi # "exit" to the root level. - export VYATTA_EDIT_LEVEL="/" - export VYATTA_TEMPLATE_LEVEL="/" - set_config_ps1 '' + reset_edit_level } # run op mode commands @@ -431,36 +292,14 @@ vyatta_loadsave_complete() loadkey() { # don't load if there are uncommitted changes. - if [ -f "$VYATTA_TEMP_CONFIG_DIR/$VYATTA_MOD_NAME" ]; then + if vyatta_cli_shell_api sessionChanged; then echo "Cannot load: configuration modified." echo "Commit or discard the changes before loading a config file." return 1 fi # return to top level. - export VYATTA_EDIT_LEVEL="/" - export VYATTA_TEMPLATE_LEVEL="/" - set_config_ps1 '' - eval "${vyatta_sbindir}/vyatta-load-user-key.pl $@" -} - -comment() -{ - if [ "$#" -eq "0" ]; then - return 0 - fi - ${vyatta_sbindir}/vyatta-comment-config.pl "$@" -} - -activate() -{ - #create or remove activate file - eval "${vyatta_sbindir}/vyatta-activate-config.pl activate $@" -} - -deactivate() -{ - #create or remove activate file - eval "${vyatta_sbindir}/vyatta-activate-config.pl deactivate $@" + reset_edit_level + ${vyatta_sbindir}/vyatta-load-user-key.pl "$@" } vyatta_loadkey_complete() @@ -486,96 +325,6 @@ vyatta_loadkey_complete() esac } -declare v_cfg_completion_debug=0 -decho () -{ - if (( v_cfg_completion_debug )); then - echo -n "$*" - fi -} - -push_path_arr () -{ - # $1: \@path_arr - # $2: component - eval "$1=( \"\${$1[@]}\" '$2' )" -} - -pop_path_arr () -{ - # $1: \@path_arr - eval "$1=( \"\${$1[@]:0:((\${#$1[@]}-1))}\" )" -} - -path_arr2str () -{ - # $1: \@path_arr - # $2: \$path_str - eval "$2=\"\${$1[*]}\"" - eval "$2=/\${$2// //}" -} - -path_str2arr () -{ - # $1: \$path_str - # $2: \@path_arr - local tmp - eval "tmp=\${$1:1}" - eval "$2=( \${tmp//\// } )" -} - -push_path () -{ - # $1: \$path_str - # $2: component - declare -a path_arr - eval "path_str2arr $1 path_arr" - eval "push_path_arr path_arr '$2'" - eval "path_arr2str path_arr $1" -} - -pop_path () -{ - # $1: \$path_str - declare -a path_arr - eval "path_str2arr $1 path_arr" - pop_path_arr path_arr - eval "path_arr2str path_arr $1" -} - -get_filtered_dir_listing () -{ - # $1: path - # $2: \@listing - if [ ! -d $1 ]; then - eval "$2=()" - return - fi - local pattern='^node\.def$|^node\.tag$|^node\.val$|^\.modified$' - patterh=$pattern'|^\.commit\.lck$|^\.wh\.' - local cmd="ls $1 |egrep -v '$pattern'" - declare -a listing=( $(eval $cmd) ) - for enode in "${listing[@]}"; do - local unode - vyatta_unescape enode unode - eval "$2[\${#$2[@]}]=$unode" - done -} - -filter_existing_nodes () -{ - # $1: mpath - # $2: \@orig - # $3: \@filtered - declare -a orig - eval "orig=( \"\${$2[@]}\" )" - for node in "${orig[@]}"; do - if [ -d "$1/$node" ]; then - eval "$3[\${#$3[@]}]=$node" - fi - done -} - get_prefix_filtered_list () { # $1: prefix @@ -623,24 +372,6 @@ get_prefix_filtered_list2 () done } -vyatta_parse_tmpl_comp_fields () -{ - # $1: tmpl - # $2: field name - sed -n ' - /^'"$2"':/,$ { - s/^'"$2"':[ ]*// - h - :b - $ { x; p; q } - n - /^\([-_a-z]\+:\|#\)/ { x; p; q } - H - bb - } - ' $1 -} - declare vyatta_cfg_help="" declare vyatta_cfg_type="" declare vyatta_cfg_tag=0 @@ -649,93 +380,22 @@ declare -a vyatta_cfg_allowed=() declare vyatta_cfg_comp_help="" declare -a vyatta_cfg_val_type=() declare -a vyatta_cfg_val_help=() -vyatta_parse_tmpl () -{ - # $1: tmpl - vyatta_cfg_help="" - vyatta_cfg_type="" - vyatta_cfg_enum='' - vyatta_cfg_tag=0 - vyatta_cfg_multi=0 - vyatta_cfg_allowed=() - vyatta_cfg_comp_help='' - vyatta_cfg_val_type=() - vyatta_cfg_val_help=() - if [ ! -r $1 ]; then - return - fi - eval `sed -n ' - /^syntax:expression:[ ]\+\$VAR(@)[ ]\+in[ ]\+/ { - s/^syntax:expression:[ ]\+\$VAR(@)[ ]\+in[ ]\+/vyatta_cfg_allowed=( / - s/^\([^;]\+\);.*$/\1 )/ - s/[ ]*,[ ]*/ /gp - } - s/^tag:.*/vyatta_cfg_tag=1/p - s/^multi:.*/vyatta_cfg_multi=1/p - s/^type:[ ]*\([^;]\+[^; ]\)[ ]*\(;.*\)\?$/vyatta_cfg_type="\1"/p - s/^enumeration:[ ]\+\([^ ]\+\)/vyatta_cfg_enum=\1/p - ' $1` - - vyatta_cfg_help=$(vyatta_parse_tmpl_comp_fields $1 "help") - - local acmd=$(vyatta_parse_tmpl_comp_fields $1 "allowed") - if [ -n "$vyatta_cfg_enum" ]; then - local enum_script="/opt/vyatta/share/enumeration/$vyatta_cfg_enum" - if [ -f "$enum_script" ] && [ -e "$enum_script" ]; then - acmd="$enum_script" - fi - fi - vyatta_cfg_comp_help=$(vyatta_parse_tmpl_comp_fields $1 "comp_help") - local vhstr=$(grep '^val_help:' $1 | sed 's/^val_help:[ ]*//; - s/[ ]*;[ ]*/;/' \ - | while read line; do - if [[ "$line" == *";"* ]]; then - echo "vyatta_cfg_val_type+=( \"${line%%;*}\" )" - echo "vyatta_cfg_val_help+=( \"${line##*;}\" )" - else - echo "vyatta_cfg_val_help+=( \"$line\" )" - fi - done) - eval "$vhstr" - - if (( ${#vyatta_cfg_allowed[@]} == 0 )); then - astr=$(eval "$acmd") - astr=${astr//</\\<} - astr=${astr//>/\\>} - eval "ares=( $astr )" - for (( i=0 ; i<${#ares[@]} ; i++ )); do - if [[ "${ares[i]}" != \<*\> ]]; then - vyatta_cfg_allowed+=( "${ares[i]}" ) - else - vyatta_cfg_allowed+=( "" ) - fi - done - fi - if [ -z "$vyatta_cfg_help" ]; then - vyatta_cfg_help='<No help text available>' - fi -} -# this fills in $vyatta_help_text -generate_help_text () +declare -a _get_help_text_items=() +declare -a _get_help_text_helps=() +get_help_text () { - # $1: \@items - # $2: \@help_strs - declare -a items - declare -a helps - eval "items=( \"\${$1[@]}\" )" - eval "helps=( \"\${$2[@]}\" )" vyatta_help_text="\\nPossible completions:" - for (( idx = 0; idx < ${#items[@]}; idx++ )); do - vyatta_help_text="${vyatta_help_text}\\n\\x20\\x20" - if [ ${#items[$idx]} -lt 6 ]; then - vyatta_help_text="${vyatta_help_text}${items[$idx]}\\t\\t" - elif [ ${#items[$idx]} -lt 14 ]; then - vyatta_help_text="${vyatta_help_text}${items[$idx]}\\t" + for (( idx = 0; idx < ${#_get_help_text_items[@]}; idx++ )); do + vyatta_help_text+="\\n\\x20\\x20" + if [ ${#_get_help_text_items[idx]} -lt 6 ]; then + vyatta_help_text+="${_get_help_text_items[idx]}\\t\\t" + elif [ ${#_get_help_text_items[idx]} -lt 14 ]; then + vyatta_help_text+="${_get_help_text_items[idx]}\\t" else - vyatta_help_text="${vyatta_help_text}${items[$idx]}\\n\\x20\\x20\\t\\t" + vyatta_help_text+="${_get_help_text_items[idx]}\\n\\x20\\x20\\t\\t" fi - vyatta_help_text="${vyatta_help_text}${helps[$idx]}" + vyatta_help_text+="${_get_help_text_helps[idx]}" done if [ -n "$vyatta_cfg_comp_help" ]; then local hstr=${vyatta_cfg_comp_help//\'/\'\\\\\\\'\'} @@ -751,80 +411,6 @@ generate_help_text () fi } -# this fills in $vyatta_help_text -get_tmpl_subdir_help () -{ - # $1: path - # $2: \@subdirs - declare -a subdirs - eval "subdirs=( \"\${$2[@]}\" )" - if [ ${#subdirs[@]} == 0 ]; then - vyatta_help_text="" - return - fi - declare -a hitems=() - declare -a hstrs=() - for subdir in "${subdirs[@]}"; do - if [ ! -r $1/$subdir/node.def ]; then - vyatta_cfg_help="<No help text available>" - else - vyatta_parse_tmpl "$1/$subdir/node.def" - # comp_help overrides the current help, so we reset it here since - # it is from the subdir. - vyatta_cfg_comp_help='' - fi - hitems[${#hitems[@]}]=$subdir - hstrs[${#hstrs[@]}]=$vyatta_cfg_help - done - generate_help_text hitems hstrs -} - -# return 0 if yes. 1 if no. -item_in_list () -{ - # $1: item - # $2: \@list - declare -a olist - local item - eval "olist=( \"\${$2[@]}\" )" - for item in "${olist[@]}"; do - if [ "$1" == "$item" ]; then - return 0 - fi - done - return 1 -} - -append_allowed_values () -{ - # $1: tmpl_path - # $2: \@values - if [ ! -r "$1/node.def" ]; then - return - fi - vyatta_parse_tmpl "$1/node.def" - local item - for item in "${vyatta_cfg_allowed[@]}"; do - if ! item_in_list "$item" $2; then - eval "$2[\${#$2[@]}]=\"$item\"" - fi - done -} - -# return 0 if yes. 1 if no. -is_setting_new_leaf () -{ - # $1: tmpl_path - if [ $is_set == 0 ]; then - return 1 - fi - vyatta_parse_tmpl "$1/node.def" - if [ -z "$vyatta_cfg_type" ]; then - return 1 - fi - return 0 -} - get_value_format_string () { local vtype=$1 @@ -878,121 +464,12 @@ get_value_format_string () esac } -# this fills in $vyatta_help_text -get_node_value_help () -{ - # $1: path - # $2: \@values - declare -a vals - eval "vals=( \"\${$2[@]}\" )" - if [ $is_set == 0 -a ${#vals[@]} == 0 ]; then - vyatta_help_text="" - return - fi - if [ ! -r "$1/node.def" ]; then - vyatta_cfg_help="<No help text available>" - vyatta_cfg_type="" - else - vyatta_parse_tmpl "$1/node.def" - fi - if (( ${#vyatta_cfg_val_type[@]} == 0 )); then - # didn't get val_type, use type (with support for multi-typed nodes) - vyatta_cfg_val_type=( ${vyatta_cfg_type//,/ } ) - fi - if (( ${#vyatta_cfg_val_help[@]} == 0 )); then - # didn't get val_help, use help - vyatta_cfg_val_help=( "$vyatta_cfg_help" ) - fi - - declare -a hitems=() - declare -a hstrs=() - for ((i = 0; i < ${#vyatta_cfg_val_type[@]}; i++)); do - local t=$(get_value_format_string "${vyatta_cfg_val_type[i]}") - hitems+=( "$t" ) - hstrs+=( "${vyatta_cfg_val_help[i]}" ) - done - generate_help_text hitems hstrs -} - -get_value_list () -{ - # $1: path - # $2: \@listing - local vfile=$1/node.val - if [ ! -r $vfile ]; then - eval "$2=()" - return - fi - declare -a listing=() - local cmd=$(sed 's/^\(.*\)$/listing[\${#listing[@]}]='\''\1'\''/' $vfile) - eval "$cmd" - eval "$2=( \"\${listing[@]}\" )" -} - -vyatta_escape () -{ - # $1: \$original - # $2: \$escaped - eval "$2=\${$1//\%/%25}" - eval "$2=\${$2//\*/%2A}" - eval "$2=\${$2//\//%2F}" -} - -vyatta_unescape () -{ - # $1: \$escaped - # $2: \$original - eval "$2=\${$1//\%2F/\/}" - eval "$2=\${$2//\%2A/*}" - eval "$2=\${$2//\%25/%}" -} - declare -a vyatta_completions declare vyatta_help_text="\\nNo help text available" -declare vyatta_do_help=0 +declare vyatta_do_help=false vyatta_do_complete () { - # when this function is called, it is expected that: - # * "vyatta_help_text" is filled with the help text. - # * "vyatta_completions" is an array of "filtered" possible completions - # (i.e., only those starting with the current last component). - local do_help=$vyatta_do_help - - # we may not want to do the following -<<'ENDCOMMENT' - if [ ${#vyatta_completions[@]} == 1 ]; then - # no ambiguous completions. do completion instead of help. - do_help=0 - fi - - # now check if we can auto-complete at least 1 more character. - if (( do_help )); then - local schar="" - for comp in "${vyatta_completions[@]}"; do - local sub=$comp - if [ ! -z "${COMP_WORDS[COMP_CWORD]}" ]; then - sub=${comp#${comp_words[$last_idx]}} - if [ "$comp" == "$sub" ]; then - # should not happen since vyatta_completions should be filtered. - continue - fi - fi - if [ -z "$schar" ]; then - schar=${sub:0:1} - else - if [ "$schar" != "${sub:0:1}" ]; then - schar="" - break - fi - fi - done - if [ ! -z "$schar" ]; then - do_help=0 - fi - fi -ENDCOMMENT - - if (( do_help )); then + if $vyatta_do_help; then printf "$vyatta_help_text" COMPREPLY=( "" " " ) else @@ -1012,17 +489,39 @@ ENDCOMMENT vyatta_help_text="\\nNo help text available" } +vyatta_simple_complete () +{ + # when this function is called, it is expected that: + # * "vyatta_help_text" is filled with the help text. + # * "vyatta_completions" is an array of "filtered" possible completions + # (i.e., only those starting with the current last component). + if $vyatta_do_help; then + printf "$vyatta_help_text" + COMPREPLY=( "" " " ) + else + COMPREPLY=( "${vyatta_completions[@]}" ) + fi + vyatta_help_text="\\nNo help text available" +} + generate_pipe_help () { - local -a hcomps=( "${_vyatta_pipe_completions[@]}" \ - "${_vyatta_pipe_noncompletions[@]}" ) - local -a hstrs=() - for comp in "${hcomps[@]}"; do - hstrs+=("$(_vyatta_pipe_help "$comp")") + _get_help_text_items=( "${_vyatta_pipe_completions[@]}" \ + "${_vyatta_pipe_noncompletions[@]}" ) + _get_help_text_helps=() + for comp in "${_get_help_text_items[@]}"; do + _get_help_text_helps+=("$(_vyatta_pipe_help "$comp")") done - generate_help_text hcomps hstrs + get_help_text } +# env variables for shell api completion +declare _cli_shell_api_last_comp_val='' +declare _cli_shell_api_comp_help='' +declare -a _cli_shell_api_comp_values=() +declare -a _cli_shell_api_hitems=() +declare -a _cli_shell_api_hstrs=() + vyatta_config_complete () { local restore_shopts=$( shopt -p extglob nullglob | tr \\n \; ) @@ -1030,10 +529,10 @@ vyatta_config_complete () if [ "$COMP_LINE" == "$VYATTA_COMP_LINE" ]; then VYATTA_COMP_LINE='' - vyatta_do_help=1 + vyatta_do_help=true else VYATTA_COMP_LINE=$COMP_LINE - vyatta_do_help=0 + vyatta_do_help=false fi # handle pipe @@ -1046,24 +545,24 @@ vyatta_config_complete () fi if (( ${#COMP_WORDS[@]} < 2 )); then - declare -a hitems=( "activate" \ - "comment" \ - "commit" \ - "copy" \ - "deactivate" \ - "delete" \ - "discard" \ - "edit" \ - "exit" \ - "load" \ - "loadkey" \ - "merge" \ - "rename" \ - "run" \ - "save" \ - "set" \ - "show" ) - declare -a hstrs=( \ + _get_help_text_items=( "activate" \ + "comment" \ + "commit" \ + "copy" \ + "deactivate" \ + "delete" \ + "discard" \ + "edit" \ + "exit" \ + "load" \ + "loadkey" \ + "merge" \ + "rename" \ + "run" \ + "save" \ + "set" \ + "show" ) + _get_help_text_helps=( \ "Activate this element" \ "Add comment to this configuration element" \ "Commit the current set of changes" \ @@ -1085,315 +584,124 @@ vyatta_config_complete () if (( ${#COMP_WORDS[@]} == 1 )); then declare -a fitems=() declare -a fstrs=() - get_prefix_filtered_list2 "${COMP_WORDS[0]}" hitems fitems hstrs fstrs - hitems=( "${fitems[@]}" ) - hstrs=( "${fstrs[@]}" ) + get_prefix_filtered_list2 "${COMP_WORDS[0]}" \ + _get_help_text_items fitems _get_help_text_helps fstrs + _get_help_text_items=( "${fitems[@]}" ) + _get_help_text_helps=( "${fstrs[@]}" ) fi - generate_help_text hitems hstrs - vyatta_completions=( "${hitems[@]}" ) + get_help_text + vyatta_completions=( "${_get_help_text_items[@]}" ) vyatta_do_complete eval $restore_shopts return fi local command=${COMP_WORDS[0]} - # completion for "set"/"edit" is different from other commands - is_set=0 - if [ "$command" == "set" -o "$command" == "edit" ]; then - is_set=1 - fi - local end_space=0 - local num_comp=$COMP_CWORD - if [ -z "${COMP_WORDS[$COMP_CWORD]}" ]; then - end_space=1 - (( num_comp -= 1 )) - fi - - (( last_idx = num_comp - 1 )) - comp_words=( ${COMP_WORDS[@]:1:$num_comp} ) + local last_comp="${COMP_WORDS[COMP_CWORD]}" # handle "exit" if [ "$command" == "exit" ]; then - if (( num_comp > 1 || ( end_space && num_comp > 0 ) )); then + if (( COMP_CWORD > 1 )); then COMPREPLY=() eval $restore_shopts return fi - declare -a hitems=( "discard" ) - declare -a hstrs=( "Discard any changes" ) - generate_help_text hitems hstrs - vyatta_completions=( "discard" ) + _get_help_text_items=("discard") + _get_help_text_helps=("Discard any changes") + get_help_text + vyatta_completions=("discard") vyatta_do_complete eval $restore_shopts return fi - local start_idx=0 + local -a api_args=("${COMP_WORDS[@]}") # handle "copy" and "rename" if [ "$command" == "copy" -o "$command" == "rename" ]; then - # Syntax of copy and rename commands are: - # - # copy/rename <param1> <sub-param1> to <param2> <sub-param2> - # - # where <param1> and <param2> are configuration parameters - # in the tree at the current edit level. - # - # If parsing index 1 or 2 (i.e. <param1> or <sub-param1>), - # fall through this test to the parameter parsing code below. - # - if (( ( end_space && num_comp == 2 ) || - ( !end_space && num_comp == 3 ) )); then - # If parsing index 3, there's only one option. - declare -a hitems=( "to" ) - declare -a hstrs=( "Set destination" ) - generate_help_text hitems hstrs - vyatta_completions=( "to" ) - vyatta_do_complete - eval $restore_shopts - return - elif (( ( num_comp > 2 ) && - ( ( num_comp < 5 ) || ( !end_space && num_comp == 5 ) ) )); then - # If parsing index 4 or 5, we set start_idx so that - # the parameter parsing code will start parsing at - # <param2>. Parsing these parameters should follow - # the same rules as the "set" command. - start_idx=3 - is_set=1 - elif (( ( end_space && num_comp == 5 ) || - ( num_comp > 5 ) )); then - # If parsing after index 5, there are no more valid parameters - COMPREPLY=() - eval $restore_shopts - return - fi - fi - - local _mpath=${VYATTA_TEMP_CONFIG_DIR}/${VYATTA_EDIT_LEVEL} - local _tpath=${VYATTA_CONFIG_TEMPLATE}/${VYATTA_TEMPLATE_LEVEL} - local last_tag=0 - local idx=$start_idx - for (( idx=$start_idx; idx < num_comp; idx++ )); do - last_tag=0 - local comp=${comp_words[$idx]} - vyatta_escape comp comp - push_path _mpath $comp - push_path _tpath $comp - if [ -d $_tpath ]; then - if (( ! is_set )); then - # we are not in "set" => only allow existing node - if [ ! -d $_mpath ]; then - break - fi - fi - continue - fi - pop_path _tpath - push_path _tpath $VYATTA_TAG_NAME - if [ -d $_tpath ]; then - if (( ! is_set && end_space )); then - # we are not in "set" && last component is complete. - # => only allow existing tag value. - if [ ! -d $_mpath ]; then - break - fi - fi - if (( idx != last_idx )); then - # TODO validate value - # break if not valid - # XXX is this validation necessary? (set will validate anyway) - true - fi - last_tag=1 - continue - fi - pop_path _tpath - pop_path _mpath - break - done - # at the end of the loop, 3 possibilities: - # 1. (idx < last_idx): some component before the last is invalid - # => invalid command - # 2. (idx == last_idx): last component matches neither template nor node.tag - # => if end_space, then invalid command - # otherwise, may be an incomplete (non-tag) component, or incomplete - # "leaf value" - # => try matching dirs in _tpath or value(s) in _mpath/node.val - # 3. (idx == num_comp): the whole command matches templates/tags - if (( idx < last_idx || ( idx == last_idx && end_space ) )); then - # TODO error message? - COMPREPLY=() - eval $restore_shopts - return - fi - - declare -a matches - if (( idx == last_idx )); then - # generate possibile matches (dirs in _tpath, and "help" from - # node.def in each dir, or values in _mpath/node.val) - declare -a fmatches - if [ -f $_mpath/node.val ]; then - decho {1a} - get_value_list $_mpath matches - get_prefix_filtered_list ${comp_words[$last_idx]} matches fmatches - append_allowed_values $_tpath fmatches - get_node_value_help $_tpath fmatches - else - decho {1b} - # see if the last component is a new leaf node - fmatches=() - if is_setting_new_leaf $_tpath; then - append_allowed_values $_tpath fmatches - get_node_value_help $_tpath fmatches - else - # last component is a non-value node. look for child nodes. - if (( ! is_set )); then - # not "set". only complete existing nodes. - declare -a amatches=() - get_filtered_dir_listing $_tpath amatches - filter_existing_nodes $_mpath amatches matches - else - get_filtered_dir_listing $_tpath matches - fi - get_prefix_filtered_list ${comp_words[$last_idx]} matches fmatches - get_tmpl_subdir_help $_tpath fmatches - fi - fi - vyatta_completions=( "${fmatches[@]}" ) - vyatta_do_complete - eval $restore_shopts - return - fi - - if (( last_tag && end_space )); then - # if not "set", check _mpath (last component is the tag) is valid - # generate possible matches (dirs in _tpath, and "help" from node.def - # in each dir) - decho {2} - if [ $is_set == 1 -o -d $_mpath ]; then - if (( ! is_set )); then - # not "set". only complete existing nodes. - declare -a fmatches=() - get_filtered_dir_listing $_tpath fmatches - filter_existing_nodes $_mpath fmatches matches - else - get_filtered_dir_listing $_tpath matches - fi - get_tmpl_subdir_help $_tpath matches - vyatta_completions=( "${matches[@]}" ) + # Syntax of copy and rename commands are: + # + # copy/rename <param1> <sub-param1> to <param2> <sub-param2> + # + # where <param1> and <param2> are configuration parameters + # in the tree at the current edit level. + # + # If parsing index 1 or 2 (i.e. <param1> or <sub-param1>), + # fall through this test to the parameter parsing code below. + if (( COMP_CWORD == 3 )); then + # If parsing index 3, there's only one option. + _get_help_text_items=("to") + _get_help_text_helps=("Set destination") + get_help_text + vyatta_completions=("to") vyatta_do_complete eval $restore_shopts return + elif (( COMP_CWORD > 3 && COMP_CWORD < 6 )); then + # If parsing index 4 or 5, start completion at <param2>. + api_args=("$command" "${COMP_WORDS[@]:4}") + elif (( COMP_CWORD > 5 )); then + # If parsing after index 5, there are no more valid parameters + COMPREPLY=() + eval $restore_shopts + return fi - eval $restore_shopts - return fi - - if (( last_tag && !end_space )); then - # generate possible matches (dirs in _mpath, and "help" from node.def - # in dirs in _tpath) - decho {3} - pop_path _mpath - pop_path _tpath - get_filtered_dir_listing $_mpath matches - declare -a fmatches - get_prefix_filtered_list ${comp_words[$last_idx]} matches fmatches - append_allowed_values $_tpath fmatches - get_node_value_help $_tpath fmatches - vyatta_completions=( "${fmatches[@]}" ) - vyatta_do_complete + + if ! vyatta_cli_shell_api getCompletionEnv "${api_args[@]}"; then + # invalid completion eval $restore_shopts return fi - - if (( !last_tag && end_space )); then - # generate possible matches - # 1. dirs in _tpath, and "help" from node.def in each dir - # 2. value(s) in _mpath/node.val (only if _tpath/node.def is "multi:") - # 3. dirs in _mpath (only if _tpath/node.def is "tag:") - if [ -d $_tpath/node.tag ]; then - # last component is a "tag name". look for tag values. - decho {4a} - get_filtered_dir_listing $_mpath matches - append_allowed_values $_tpath matches - get_node_value_help $_tpath matches - elif [ -f $_mpath/node.val ]; then - # last component is a leaf node. look for values. - decho {4b} - get_value_list $_mpath matches - append_allowed_values $_tpath matches - get_node_value_help $_tpath matches - else - decho {4c} - # see if the last component is a new leaf node - matches=() - if is_setting_new_leaf $_tpath; then - append_allowed_values $_tpath matches - get_node_value_help $_tpath matches - else - # last component is a non-value node. look for child nodes. - if (( ! is_set )); then - # not "set". only complete existing nodes. - declare -a fmatches=() - get_filtered_dir_listing $_tpath fmatches - filter_existing_nodes $_mpath fmatches matches - else - get_filtered_dir_listing $_tpath matches - fi - get_tmpl_subdir_help $_tpath matches + vyatta_cfg_comp_help=$_cli_shell_api_comp_help + _get_help_text_helps=( "${_cli_shell_api_hstrs[@]}" ) + if $_cli_shell_api_last_comp_val; then + # last component is a "value". need to do the following: + # use comp_help if exists + # prefix filter comp_values + # replace any <*> in comp_values with "" + # convert help items to <...> representation + _get_help_text_items=() + for ((i = 0; i < ${#_cli_shell_api_hitems[@]}; i++)); do + local t=$(get_value_format_string "${_cli_shell_api_hitems[i]}") + _get_help_text_items+=("$t") + done + vyatta_completions=() + for ((i = 0; i < ${#_cli_shell_api_comp_values[@]}; i++)); do + if [ -z "$last_comp" ] \ + && [[ "${_cli_shell_api_comp_values[i]}" = \<*\> ]]; then + vyatta_completions+=("") + elif [ -z "$last_comp" ] \ + || [[ "${_cli_shell_api_comp_values[i]}" = "$last_comp"* ]]; then + vyatta_completions+=("${_cli_shell_api_comp_values[i]}") fi - fi - vyatta_completions=( "${matches[@]}" ) - vyatta_do_complete - eval $restore_shopts - return - fi - - if (( !last_tag && !end_space )); then - # generate possible matches (dirs in _tpath, and "help" from node.def - # in each dir) - decho {5} - pop_path _tpath - get_filtered_dir_listing $_tpath matches - declare -a fmatches - get_prefix_filtered_list ${comp_words[$last_idx]} matches fmatches - get_tmpl_subdir_help $_tpath fmatches - vyatta_completions=( "${fmatches[@]}" ) - vyatta_do_complete - eval $restore_shopts - return + done + else + _get_help_text_items=( "${_cli_shell_api_hitems[@]}" ) + vyatta_completions=( "${_cli_shell_api_comp_values[@]}" ) fi - + get_help_text + vyatta_simple_complete eval $restore_shopts } -DEF_GROUP=vyattacfg -make_vyatta_config_dir () -{ - sudo mkdir -m 0775 -p $1 - sudo chgrp ${DEF_GROUP} $1 -} - -if grep -q union=aufs /proc/cmdline || grep -q aufs /proc/filesystems ; then - export UNIONFS=aufs -else - export UNIONFS=unionfs -fi - -make_vyatta_config_dir $VYATTA_ACTIVE_CONFIGURATION_DIR -make_vyatta_config_dir $VYATTA_CHANGES_ONLY_DIR -make_vyatta_config_dir $VYATTA_CONFIG_TMP -if [ ! -d $VYATTA_TEMP_CONFIG_DIR ]; then - make_vyatta_config_dir $VYATTA_TEMP_CONFIG_DIR - sudo mount -t $UNIONFS -o dirs=${VYATTA_CHANGES_ONLY_DIR}=rw:/opt/vyatta/config/active=ro $UNIONFS ${VYATTA_TEMP_CONFIG_DIR} +if ! vyatta_cli_shell_api setupSession; then + echo 'Failed to set up config session' + exit 1 fi # disallow 'Ctrl-D' exit, since we need special actions on 'exit' set -o ignoreeof 1 -set_config_ps1 '' +reset_edit_level alias set=/opt/vyatta/sbin/my_set alias delete=/opt/vyatta/sbin/my_delete +alias activate=/opt/vyatta/sbin/my_activate +alias deactivate=/opt/vyatta/sbin/my_deactivate +alias rename=/opt/vyatta/sbin/my_rename +alias copy=/opt/vyatta/sbin/my_copy +alias comment=/opt/vyatta/sbin/my_comment +alias discard=/opt/vyatta/sbin/my_discard export VYATTA_COMP_LINE="" diff --git a/etc/default/vyatta-cfg b/etc/default/vyatta-cfg index 8dbf00e..0a5c593 100644 --- a/etc/default/vyatta-cfg +++ b/etc/default/vyatta-cfg @@ -1,21 +1,16 @@ # Vyatta shell environment variables for config mode # should be sourced from /etc/default/vyatta +# note that session environment is now handled separately. +# (see /etc/bash_completion.d/20vyatta-cfg) + { -declare -x -r VYATTA_ACTIVE_CONFIGURATION_DIR=${vyatta_configdir}/active -declare -x -r VYATTA_CHANGES_ONLY_DIR=/tmp/changes_only_$$ -declare -x -r VYATTA_TEMP_CONFIG_DIR=${vyatta_configdir}/tmp/new_config_$$ -declare -x -r VYATTA_CONFIG_TMP=${vyatta_configdir}/tmp/tmp_$$ -declare -x -r VYATTA_CONFIG_TEMPLATE=$vyatta_cfg_templates declare -x -r VYATTA_TAG_NAME=node.tag declare -x -r VYATTA_MOD_NAME=.modified declare -x -r VYATTA_CFG_GROUP_NAME=vyattacfg declare -x -r HISTIGNORE="*password*:*-secret*" } 2>/dev/null || : -declare -x VYATTA_EDIT_LEVEL=/ -declare -x VYATTA_TEMPLATE_LEVEL=/ - # don't set level if already set if [ -n "$VYATTA_USER_LEVEL_DIR" ]; then return diff --git a/lib/Vyatta/Config.pm b/lib/Vyatta/Config.pm index 8bcd84f..8067e05 100755 --- a/lib/Vyatta/Config.pm +++ b/lib/Vyatta/Config.pm @@ -21,16 +21,14 @@ package Vyatta::Config; use strict; -use Vyatta::ConfigDOMTree; use File::Find; +use lib '/opt/vyatta/share/perl5'; +use Cstore; + my %fields = ( - _changes_only_dir_base => $ENV{VYATTA_CHANGES_ONLY_DIR}, - _new_config_dir_base => $ENV{VYATTA_TEMP_CONFIG_DIR}, - _active_dir_base => $ENV{VYATTA_ACTIVE_CONFIGURATION_DIR}, - _vyatta_template_dir => $ENV{VYATTA_CONFIG_TEMPLATE}, - _current_dir_level => "/", _level => undef, + _cstore => undef, ); sub new { @@ -39,892 +37,546 @@ sub new { my $self = { %fields, }; - bless $self, $class; + $self->{_cstore} = new Cstore(); return $self; } -sub _set_current_dir_level { - my ($self) = @_; - my $level = $self->{_level}; - - $level =~ s/\//%2F/g; - $level =~ s/\s+/\//g; - - $self->{_current_dir_level} = "/$level"; - return $self->{_current_dir_level}; +sub get_path_comps { + my ($self, $pstr) = @_; + $pstr = '' if (!defined($pstr)); + $pstr = "$self->{_level} $pstr" if (defined($self->{_level})); + $pstr =~ s/^\s+//; + $pstr =~ s/\s+$//; + my @path_comps = split /\s+/, $pstr; + return \@path_comps; +} + +############################################################ +# low-level API functions that have been converted to use +# the cstore library. +############################################################ + +###### +# observers of current working config or active config during a commit. +# * MOST users of this API should use these functions. +# * these functions MUST NOT worry about the "deactivated" state, i.e., +# deactivated nodes are equivalent to having been deleted for these +# functions. in other words, these functions are NOT "deactivate-aware". +# * functions that can be used to observe "active config" can be used +# outside a commit as well (only when observing active config, of course). +# +# note: these functions accept a third argument "$include_deactivated", but +# it is for error checking purposes to ensure that all legacy +# invocations have been fixed. the functions MUST NOT be called +# with this argument. +my $DIE_DEACT_MSG = 'This function is NOT deactivate-aware'; + +## exists("path to node") +# Returns true if specified node exists in working config. +sub exists { + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return 1 + if ($self->{_cstore}->cfgPathExists($self->get_path_comps($path), undef)); + return; # note: this return is needed. can't just return the return value + # of the above function since some callers expect "undef" + # as false. } -## setLevel("level") -# if "level" is supplied, set the current level of the hierarchy we are working on -# return the current level -sub setLevel { - my ($self, $level) = @_; - - $self->{_level} = $level if defined($level); - $self->_set_current_dir_level(); - - return $self->{_level}; +## existsOrig("path to node") +# Returns true if specified node exists in active config. +sub existsOrig { + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return 1 + if ($self->{_cstore}->cfgPathExists($self->get_path_comps($path), 1)); + return; # note: this return is needed. } ## listNodes("level") -# return array of all nodes at "level" -# level is relative +# return array of all child nodes at "level" in working config. sub listNodes { - my ($self, $path, $disable) = @_; - my @nodes = (); - - my $rpath = ""; - if ($path) { - $path =~ s/\//%2F/g; - $path =~ s/\s+/\//g; - $rpath = $self->{_current_dir_level} . "/" . $path; - } else { - $rpath = $self->{_current_dir_level}; - } - $path = $self->{_new_config_dir_base} . $rpath; - - #print "DEBUG Vyatta::Config->listNodes(): path = $path\n"; - opendir my $dir, $path or return (); - @nodes = grep !/^\./, readdir $dir; - closedir $dir; - - my @nodes_modified = (); - while (@nodes) { - my $tmp = pop (@nodes); - $tmp =~ s/\n//g; - #print "DEBUG Vyatta::Config->listNodes(): node = $tmp\n"; - my $ttmp = $rpath . "/" . $tmp; - $tmp =~ s/%2F/\//g; - $ttmp =~ s/\// /g; - if (!defined $disable) { - my ($status, undef) = $self->getDeactivated($ttmp); - if (!defined($status) || $status eq 'active') { - push @nodes_modified, $tmp; - } - } - else { - push @nodes_modified, $tmp; - } - } - - return @nodes_modified; -} - -## isActive("path") -# return true|false based on whether node path has -# been processed or is active -sub isActive { - my ($self, $path, $disable) = @_; - my @nodes = (); - - my @comp_node = split " ", $path; - - my $comp_node = pop(@comp_node); - if (!defined $comp_node) { - return 1; - } - - my $rel_path = join(" ",@comp_node); - - my @nodes_modified = $self->listOrigPlusComNodes($rel_path,$disable); - foreach my $node (@nodes_modified) { - if ($node eq $comp_node) { - return 0; - } - } - return 1; -} - -## listNodes("level") -# return array of all nodes (active plus currently committed) at "level" -# level is relative -sub listOrigPlusComNodes { - my ($self, $path, $disable) = @_; - my @nodes = (); - - my @nodes_modified = $self->listNodes($path,$disable); - - #convert array to hash - my %coll; - my $coll; - @coll{@nodes_modified} = @nodes_modified; - - my $level = $self->{_level}; - if (! defined $level) { - $level = ""; - } - - my $dir_path = $level; - if (defined $path) { - $dir_path .= " " . $path; - } - $dir_path =~ s/ /\//g; - $dir_path = "/".$dir_path; - - #now test against the inprocess file in the system -# my $com_file = "/tmp/.changes_$$"; - my $com_file = "/tmp/.changes"; - if (-e $com_file) { - open my $file, "<", $com_file; - foreach my $line (<$file>) { - my @node = split " ", $line; #split on space - #$node[1] is of the form: system/login/blah - #$coll is of the form: blah - -# print("comparing: $dir_path and $level to $node[1]\n"); - - #first only consider $path matches against $node[1] - if (!defined $dir_path || $node[1] =~ m/^$dir_path/) { - #or does $node[1] match the beginning of the line for $path - - #if yes, then split the right side and find against the hash for the value... - my $tmp; - if (defined $dir_path) { - $tmp = substr($node[1],length($dir_path)); - } - else { - $tmp = $node[1]; - } - - if (!defined $tmp || $tmp eq '') { - next; - } - - my @child = split "/",$tmp; - my $child; - -# print("tmp: $tmp, $child[0], $child[1]\n"); - if ($child[0] =~ /^\s*$/ || !defined $child[0] || $child[0] eq '') { - shift(@child); - } - -# print("child value is: >$child[0]<\n"); - - #now can we find this entry in the hash? - #if '-' this is a delete and need to remove from hash - if ($node[0] eq "-") { - if (!defined $child[1]) { - delete($coll{$child[0]}); - } - } - #if '+' this is a set and need to add to hash - elsif ($node[0] eq "+" && $child[0] ne '') { - $coll{$child[0]} = '1'; - } - } - } - close $file; - close $com_file; - } - -#print "coll count: ".keys(%coll); - - #now convert hash to array and return - @nodes_modified = (); - @nodes_modified = keys(%coll); - return @nodes_modified; + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + my $ref = $self->{_cstore}->cfgPathGetChildNodes( + $self->get_path_comps($path), undef); + return @{$ref}; } ## listOrigNodes("level") -# return array of all original nodes (i.e., before any current change; i.e., -# in "working") at "level" -# level is relative +# return array of all child nodes at "level" in active config. sub listOrigNodes { - my ($self, $path, $disable) = @_; - my @nodes = (); - - my $rpath = ""; - if (defined $path) { - $path =~ s/\//%2F/g; - $path =~ s/\s+/\//g; - $rpath = $self->{_current_dir_level} . "/" . $path; - } - else { - $rpath = $self->{_current_dir_level}; - } - $path = $self->{_active_dir_base} . $rpath; - - #print "DEBUG Vyatta::Config->listNodes(): path = $path\n"; - opendir my $dir, "$path" or return (); - @nodes = grep !/^\./, readdir $dir; - closedir $dir; - - my @nodes_modified = (); - while (@nodes) { - my $tmp = pop (@nodes); - $tmp =~ s/\n//g; - #print "DEBUG Vyatta::Config->listNodes(): node = $tmp\n"; - my $ttmp = $rpath . "/" . $tmp; - $tmp =~ s/%2F/\//g; - $ttmp =~ s/\// /g; - if (!defined $disable) { - my ($status, undef) = $self->getDeactivated($ttmp); - if (!defined($status) || $status eq 'local') { - push @nodes_modified, $tmp; - } - } - else { - push @nodes_modified, $tmp; - } - } - - return @nodes_modified; -} - -## returnParent("level") -# return the name of parent node relative to the current hierarchy -# in this case "level" is set to the parent dir ".. .." -# for example -sub returnParent { - my ($self, $node) = @_; - my @x, my $tmp; - - # split our hierarchy into vars on a stack - my @level = split /\s+/, $self->{_level}; - - # count the number of parents we need to lose - # and then pop 1 less - @x = split /\s+/, $node; - for ($tmp = 1; $tmp < @x; $tmp++) { - pop @level; - } - - # return the parent - $tmp = pop @level; - return $tmp; + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + my $ref = $self->{_cstore}->cfgPathGetChildNodes( + $self->get_path_comps($path), 1); + return @{$ref}; } ## returnValue("node") -# returns the value of "node" or undef if the node doesn't exist . -# node is relative +# return value of specified single-value node in working config. +# return undef if fail to get value (invalid node, node doesn't exist, +# not a single-value node, etc.). sub returnValue { - my ( $self, $node, $disable ) = @_; - my $tmp; - - $node =~ s/\//%2F/g; - $node =~ s/\s+/\//g; - - #getDeactivated - my $ttmp = $self->{_current_dir_level} . "/" . $node; - $ttmp =~ s/\// /g; - #only return value if status is not disabled (i.e. local or both) - if (!defined $disable) { - my ($status, undef) = $self->getDeactivated($ttmp); - if (!defined($status) || $status eq 'active') { - return unless - open my $file, '<', - "$self->{_new_config_dir_base}$self->{_current_dir_level}/$node/node.val"; - - read $file, $tmp, 16384; - close $file; - - $tmp =~ s/\n$//; - } - } - else { - return unless - open my $file, '<', - "$self->{_new_config_dir_base}$self->{_current_dir_level}/$node/node.val"; - - read $file, $tmp, 16384; - close $file; - - $tmp =~ s/\n$//; - } - return $tmp; -} - -## returnComment("node") -# returns the value of "node" or undef if the node doesn't exist . -# node is relative -sub returnComment { - my ( $self, $node ) = @_; - my $tmp = undef; - - $node =~ s/\//%2F/g; - $node =~ s/\s+/\//g; - - return unless - open my $file, '<', - "$self->{_new_config_dir_base}/$node/.comment"; - - read $file, $tmp, 16384; - close $file; - - $tmp =~ s/\n$//; - return $tmp; -} - -## returnOrigPlusComValue("node") -# returns the value of "node" or undef if the node doesn't exist . -# node is relative -sub returnOrigPlusComValue { - my ( $self, $path, $disable ) = @_; - - my $tmp = returnValue($self,$path,$disable); - - my $level = $self->{_level}; - if (! defined $level) { - $level = ""; - } - my $dir_path = $level; - if (defined $path) { - $dir_path .= " " . $path; - } - $dir_path =~ s/ /\//g; - $dir_path = "/".$dir_path."/value"; - - #now need to compare this against what I've done - my $com_file = "/tmp/.changes"; - if (-e $com_file) { - open my $file, "<", $com_file; - foreach my $line (<$file>) { - my @node = split " ", $line; #split on space - if (index($node[1],$dir_path) != -1) { - #found, now figure out if this a set or delete - if ($node[0] eq '+') { - my $pos = rindex($node[1],"/value:"); - $tmp = substr($node[1],$pos+7); - } - else { - $tmp = ""; - } - last; - } - } - close $file; - close $com_file; - } - return $tmp; + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return $self->{_cstore}->cfgPathGetValue($self->get_path_comps($path), + undef); } - ## returnOrigValue("node") -# returns the original value of "node" (i.e., before the current change; i.e., -# in "working") or undef if the node doesn't exist. -# node is relative +# return value of specified single-value node in active config. +# return undef if fail to get value (invalid node, node doesn't exist, +# not a single-value node, etc.). sub returnOrigValue { - my ( $self, $node, $disable ) = @_; - my $tmp; - - $node =~ s/\//%2F/g; - $node =~ s/\s+/\//g; - - - #getDeactivated - my $ttmp = $self->{_current_dir_level} . "/" . $node; - $ttmp =~ s/\// /g; - #only return value if status is not disabled (i.e. local or both) - if (!defined $disable) { - my ($status, undef) = $self->getDeactivated($ttmp); - if (!defined($status) || $status eq 'local') { - my $filepath = "$self->{_active_dir_base}$self->{_current_dir_level}/$node"; - - return unless open my $file, '<', "$filepath/node.val"; - - read $file, $tmp, 16384; - close $file; - - $tmp =~ s/\n$//; - } - } - else { - my $filepath = "$self->{_active_dir_base}$self->{_current_dir_level}/$node"; - - return unless open my $file, '<', "$filepath/node.val"; - - read $file, $tmp, 16384; - close $file; - - $tmp =~ s/\n$//; - } - return $tmp; + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return $self->{_cstore}->cfgPathGetValue($self->get_path_comps($path), 1); } ## returnValues("node") -# returns an array of all the values of "node", or an empty array if the values do not exist. -# node is relative +# return array of values of specified multi-value node in working config. +# return empty array if fail to get value (invalid node, node doesn't exist, +# not a multi-value node, etc.). sub returnValues { - my $val = returnValue(@_); - my @values = (); - if (defined($val)) { - @values = split("\n", $val); - } - return @values; + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + my $ref = $self->{_cstore}->cfgPathGetValues($self->get_path_comps($path), + undef); + return @{$ref}; } -## returnValues("node") -# returns an array of all the values of "node", or an empty array if the values do not exist. -# node is relative -sub returnOrigPlusComValues { - my ( $self, $path, $disable ) = @_; - my @values = returnOrigValues($self,$path,$disable); +## returnOrigValues("node") +# return array of values of specified multi-value node in active config. +# return empty array if fail to get value (invalid node, node doesn't exist, +# not a multi-value node, etc.). +sub returnOrigValues { + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + my $ref = $self->{_cstore}->cfgPathGetValues($self->get_path_comps($path), + 1); + return @{$ref}; +} + +###### +# observers of the "effective" config. +# they can be used +# (1) outside a config session (e.g., op mode, daemons, callbacks, etc.). +# OR +# (2) during a config session +# +# HOWEVER, NOTE that the definition of "effective" is different under these +# two scenarios. +# (1) when used outside a config session, "effective" == "active". +# in other words, in such cases the effective config is the same +# as the running config. +# +# (2) when used during a config session, a config path (leading to either +# a "node" or a "value") is "effective" if it is "in effect" at the +# time when these observers are called. more detailed info can be +# found in the library code. +# +# originally, these functions are exclusively for use during config +# sessions. however, for some usage scenarios, it is useful to have a set +# of API functions that can be used both during and outside config +# sessions. therefore, definition (1) is added above for convenience. +# +# for example, a developer can use these functions in a script that can +# be used both during a commit action and outside config mode, as long as +# the developer is clearly aware of the difference between the above two +# definitions. +# +# note that when used outside a config session (i.e., definition (1)), +# these functions are equivalent to the observers for the "active" config. +# +# to avoid any confusiton, when possible (e.g., in a script that is +# exclusively used in op mode), developers should probably use those +# "active" observers explicitly when outside a config session instead +# of these "effective" observers. +# +# it is also important to note that when used outside a config session, +# due to race conditions, it is possible that the "observed" active config +# becomes out-of-sync with the config that is actually "in effect". +# specifically, this happens when two things occur simultaneously: +# (a) an observer function is called from outside a config session. +# AND +# (b) someone invokes "commit" inside a config session (any session). +# +# this is because "commit" only updates the active config at the end after +# all commit actions have been executed, so before the update happens, +# some config nodes have already become "effective" but are not yet in the +# "active config" and therefore are not observed by these functions. +# +# note that this is only a problem when the caller is outside config mode. +# in such cases, the caller (which could be an op-mode command, a daemon, +# a callback script, etc.) already must be able to handle config changes +# that can happen at any time. if "what's configured" is more important, +# using the "active config" should be fine as long as it is relatively +# up-to-date. if the actual "system state" is more important, then the +# caller should probably just check the system state in the first place +# (instead of using these config observers). + +## isEffective("path") +# return whether "path" is in "active" config when used outside config +# session, +# OR +# return whether "path" is "effective" during current commit. +# see above discussion about the two different definitions. +# +# "effective" means the path is in effect, i.e., any of the following is true: +# (1) active && working +# path is in both active and working configs, i.e., unchanged. +# (2) !active && working && committed +# path is not in active, has been set in working, AND has already +# been committed, i.e., "commit" has already processed the +# addition/update of the path. +# (3) active && !working && !committed +# path is in active, has been deleted from working, AND +# has NOT been committed yet, i.e., "commit" (per priority) has not +# processed the deletion of the path yet (or has processed it but +# the action failed). +# +# note: during commit, deactivate has the same effect as delete. so as +# far as this function (and any other commit observer functions) is +# concerned, deactivated nodes don't exist. +sub isEffective { + my ($self, $path) = @_; + return 1 + if ($self->{_cstore}->cfgPathEffective($self->get_path_comps($path))); + return; # note: this return is needed. +} - #now parse the commit accounting file. - my $level = $self->{_level}; - if (! defined $level) { - $level = ""; - } - my $dir_path = $level; - if (defined $path) { - $dir_path .= " " . $path; - } - $dir_path =~ s/ /\//g; - $dir_path = "/".$dir_path."/value"; - - #now need to compare this against what I've done - my $com_file = "/tmp/.changes"; - if (-e $com_file) { - open my $file, "<", $com_file; - foreach my $line (<$file>) { - my @node = split " ", $line; #split on space - if (index($node[1],$dir_path) != -1) { - #found, now figure out if this a set or delete - my $pos = rindex($node[1],"/value:"); - my $tmp = substr($node[1],$pos+7); - my $i = 0; - my $match = 0; - foreach my $v (@values) { - if ($v eq $tmp) { - $match = 1; - last; - } - $i = $i + 1; - } - if ($node[0] eq '+') { - #add value - if ($match == 0) { - push(@values,$tmp); - } - } - else { - #remove value - if ($match == 1) { - splice(@values,$i); - } - } - } - } - close $file; - close $com_file; - } - return @values; +## isActive("path") +# XXX this is the original API function. name is confusing ("active" could +# be confused with "orig") but keep it for compatibility. +# just call isEffective(). +# also, original function accepts "$disable" flag, which doesn't make +# sense. for commit purposes, deactivated should be equivalent to +# deleted. +sub isActive { + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return $self->isEffective($path); +} + +## listEffectiveNodes("level") +# return array of "effective" child nodes at "level" during current commit. +# see isEffective() for definition of "effective". +sub listEffectiveNodes { + my ($self, $path) = @_; + my $ref = $self->{_cstore}->cfgPathGetEffectiveChildNodes( + $self->get_path_comps($path)); + return @{$ref}; +} + +## listOrigPlusComNodes("level") +# XXX this is the original API function. name is confusing (it's neither +# necessarily "orig" nor "plus") but keep it for compatibility. +# just call listEffectiveNodes(). +# also, original function accepts "$disable" flag, which doesn't make +# sense. for commit purposes, deactivated should be equivalent to +# deleted. +sub listOrigPlusComNodes { + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return $self->listEffectiveNodes($path); } -## returnOrigValues("node") -# returns an array of all the original values of "node" (i.e., before the -# current change; i.e., in "working"), or an empty array if the values do not -# exist. -# node is relative -sub returnOrigValues { - my $val = returnOrigValue(@_); - my @values = (); - if (defined($val)) { - @values = split("\n", $val); - } - return @values; +## returnEffectiveValue("node") +# return "effective" value of specified "node" during current commit. +sub returnEffectiveValue { + my ($self, $path) = @_; + return $self->{_cstore}->cfgPathGetEffectiveValue( + $self->get_path_comps($path)); } -## exists("node") -# Returns true if the "node" exists. -sub exists { - my ( $self, $node, $disable ) = @_; - $node =~ s/\//%2F/g; - $node =~ s/\s+/\//g; - #getDeactivated() - my $ttmp = $self->{_current_dir_level} . "/" . $node; - $ttmp =~ s/\// /g; - if (!defined $disable) { - my ($status, undef) = $self->getDeactivated($ttmp); - #only return value if status is not disabled (i.e. local or both) - if (defined($status) && ($status eq 'both' || $status eq 'local')) { #if a .disable is in local or active or both then return false - return; - } - } - return ( -d "$self->{_new_config_dir_base}$self->{_current_dir_level}/$node" ); +## returnOrigPlusComValue("node") +# XXX this is the original API function. just call returnEffectiveValue(). +# also, original function accepts "$disable" flag. +sub returnOrigPlusComValue { + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return $self->returnEffectiveValue($path); } -## existsOrig("node") -# Returns true if the "original node" exists. -sub existsOrig { - my ( $self, $node, $disable ) = @_; - $node =~ s/\//%2F/g; - $node =~ s/\s+/\//g; - - #getDeactivated() - my $ttmp = $self->{_current_dir_level} . "/" . $node; - $ttmp =~ s/\// /g; - if (!defined $disable) { - my ($status, undef) = $self->getDeactivated($ttmp); - #only return value if status is not disabled (i.e. local or both) - if (defined($status) && ($status eq 'both' || $status eq 'active')) { #if a .disable is in local or active or both then return false - return; - } - } +## returnEffectiveValues("node") +# return "effective" values of specified "node" during current commit. +sub returnEffectiveValues { + my ($self, $path) = @_; + my $ref = $self->{_cstore}->cfgPathGetEffectiveValues( + $self->get_path_comps($path)); + return @{$ref}; +} - return ( -d "$self->{_active_dir_base}$self->{_current_dir_level}/$node" ); +## returnOrigPlusComValues("node") +# XXX this is the original API function. just call returnEffectiveValues(). +# also, original function accepts "$disable" flag. +sub returnOrigPlusComValues { + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return $self->returnEffectiveValues($path); } ## isDeleted("node") -# is the "node" deleted. node is relative. returns true or false +# whether specified node has been deleted in working config sub isDeleted { - my ($self, $node, $disable) = @_; - $node =~ s/\//%2F/g; - $node =~ s/\s+/\//g; - - my $filepathAct - = "$self->{_active_dir_base}$self->{_current_dir_level}/$node"; - my $filepathNew - = "$self->{_new_config_dir_base}$self->{_current_dir_level}/$node"; - - #getDeactivated() - my $ttmp = $self->{_current_dir_level} . "/" . $node; - $ttmp =~ s/\// /g; - if (!defined $disable) { - my ($status, undef) = $self->getDeactivated($ttmp); - #only return value if status is not disabled (i.e. local or both) - if (defined($status) && $status eq 'local') { - return (-e $filepathAct); - } - } - - return ((-e $filepathAct) && !(-e $filepathNew)); + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return 1 if ($self->{_cstore}->cfgPathDeleted($self->get_path_comps($path))); + return; # note: this return is needed. } ## listDeleted("level") -# return array of deleted nodes in the "level" -# "level" defaults to current +# return array of deleted nodes at specified "level" sub listDeleted { - my ($self, $path, $disable) = @_; - my @new_nodes = $self->listNodes($path,$disable); - my @orig_nodes = $self->listOrigNodes($path,$disable); - my %new_hash = map { $_ => 1 } @new_nodes; - my @deleted = grep { !defined($new_hash{$_}) } @orig_nodes; - return @deleted; + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + my $ref = $self->{_cstore}->cfgPathGetDeletedChildNodes( + $self->get_path_comps($path)); + return @{$ref}; } -## getAllDeactivated() -# returns array of all deactivated nodes. -# -my @all_deactivated_nodes; -sub getAllDeactivated { - my ($self, $path) = @_; - my $start_dir = $self->{_active_dir_base}; - find ( \&wanted, $start_dir ); - return @all_deactivated_nodes; -} -sub wanted { - if ( $_ eq '.disable' ) { - my $f = $File::Find::name; - #now strip off leading path and trailing file - $f = substr($f, length($ENV{VYATTA_ACTIVE_CONFIGURATION_DIR})); - $f = substr($f, 0, length($f)-length("/.disable")); - $f =~ s/\// /g; - push @all_deactivated_nodes, $f; - } -} - - -## isDeactivated("node") -# returns back whether this node is in an active (false) or -# deactivated (true) state. -sub getDeactivated { - my ($self, $node) = @_; - - if (!defined $node) { - $node = $self->{_level}; - } - - # let's setup the filepath for the change_dir - $node =~ s/\//%2F/g; - $node =~ s/\s+/\//g; - #now walk up parent in local and in active looking for '.disable' file - $node =~ s/ /\//g; - - while (1) { - my $filepath = "$self->{_new_config_dir_base}/$node"; - my $filepathActive = "$self->{_active_dir_base}/$node"; - - my $local = $filepath . "/.disable"; - my $active = $filepathActive . "/.disable"; - - if (-e $local && -e $active) { - return ("both",$node); - } - elsif (-e $local && !(-e $active)) { - return ("local",$node); - } - elsif (!(-e $local) && -e $active) { - return ("active",$node); - } - my $pos = rindex($node, "/"); - if ($pos == -1) { - last; - } - $node = substr($node,0,$pos); - } - return (undef,undef); +## isAdded("node") +# whether specified node has been added in working config +sub isAdded { + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return 1 if ($self->{_cstore}->cfgPathAdded($self->get_path_comps($path))); + return; # note: this return is needed. } ## isChanged("node") -# will check the change_dir to see if the "node" has been changed from a previous -# value. returns true or false. +# whether specified node has been changed in working config +# XXX behavior is different from original implementation, which was +# inconsistent between deleted nodes and deactivated nodes. +# see cstore library source for details. +# basically, a node is "changed" if it's "added", "deleted", or +# "marked changed" (i.e., if any descendant changed). sub isChanged { - my ($self, $node, $disable) = @_; - - # let's setup the filepath for the change_dir - $node =~ s/\//%2F/g; - $node =~ s/\s+/\//g; - my $filepath = "$self->{_changes_only_dir_base}$self->{_current_dir_level}/$node"; - - if (!defined $disable) { - my ($status,undef) = $self->getDeactivated($self->{_level}." ".$node); - if (defined $status && ($status eq 'active' || $status eq 'local')) { - return (defined $status); - } - } - - - # if the node exists in the change dir, it's modified. - return (-e $filepath); -} - -## isAdded("node") -# will compare the new_config_dir to the active_dir to see if the "node" has -# been added. returns true or false. -sub isAdded { - my ($self, $node, $disable) = @_; - - #print "DEBUG Vyatta::Config->isAdded(): node $node\n"; - # let's setup the filepath for the modify dir - $node =~ s/\//%2F/g; - $node =~ s/\s+/\//g; - my $filepathNewConfig = "$self->{_new_config_dir_base}$self->{_current_dir_level}/$node"; - - #print "DEBUG Vyatta::Config->isAdded(): filepath $filepathNewConfig\n"; - - # if the node doesn't exist in the modify dir, it's not - # been added. so short circuit and return false. - return unless (-e $filepathNewConfig); - - # now let's setup the path for the working dir - my $filepathActive = "$self->{_active_dir_base}$self->{_current_dir_level}/$node"; - - if (!defined $disable) { - my ($status,undef) = $self->getDeactivated($self->{_level}." ".$node); - if (defined $status && ($status eq 'active')) { - return (defined $status); - } - } - - # if the node is in the active_dir it's not new - return (! -e $filepathActive); + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return 1 if ($self->{_cstore}->cfgPathChanged($self->get_path_comps($path))); + return; # note: this return is needed. } ## listNodeStatus("level") -# return a hash of the status of nodes at the current config level +# return a hash of status of child nodes at specified level. # node name is the hash key. node status is the hash value. -# node status can be one of deleted, added, changed, or static +# node status can be one of "deleted", "added", "changed", or "static". sub listNodeStatus { - my ($self, $path, $disable) = @_; - my @nodes = (); - my %nodehash = (); - - # find deleted nodes first - @nodes = $self->listDeleted($path,$disable); - foreach my $node (@nodes) { - if ($node =~ /.+/) { $nodehash{$node} = "deleted" }; - } - - @nodes = (); - @nodes = $self->listNodes($path,$disable); - foreach my $node (@nodes) { - if ($node =~ /.+/) { - my $status = undef; - if (!defined $disable) { - ($status,undef) = $self->getDeactivated($self->{_level}." ".$node); - } - my $nodepath = $node; - $nodepath = "$path $node" if ($path); - #print "DEBUG Vyatta::Config->listNodeStatus(): node $node\n"; - # No deleted nodes -- added, changed, ot static only. - if (defined $status && $status eq 'local') { $nodehash{$node} = "deleted"; } - elsif (defined $status && $status eq 'active') { $nodehash{$node} = "added"; } - elsif ($self->isAdded($nodepath,'true')) { $nodehash{$node} = "added"; } - elsif ($self->isChanged($nodepath,'true')) { $nodehash{$node} = "changed"; } - else { $nodehash{$node} = "static"; } - } - } - - return %nodehash; -} - -############ DOM Tree ################ - -#Create active DOM Tree -sub createActiveDOMTree { - - my $self = shift; - - my $tree = new Vyatta::ConfigDOMTree($self->{_active_dir_base} . $self->{_current_dir_level},"active"); - - return $tree; + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + my $ref = $self->{_cstore}->cfgPathGetChildNodesStatus( + $self->get_path_comps($path)); + return %{$ref}; } -#Create changes only DOM Tree -sub createChangesOnlyDOMTree { - - my $self = shift; - - my $tree = new Vyatta::ConfigDOMTree($self->{_changes_only_dir_base} . $self->{_current_dir_level}, - "changes_only"); - - return $tree; +## getTmplChildren("level") +# return list of child nodes in the template hierarchy at specified level. +sub getTmplChildren { + my ($self, $path) = @_; + my $ref = $self->{_cstore}->tmplGetChildNodes($self->get_path_comps($path)); + return @{$ref}; } -#Create new config DOM Tree -sub createNewConfigDOMTree { - - my $self = shift; - my $level = $self->{_new_config_dir_base} . $self->{_current_dir_level}; - - return new Vyatta::ConfigDOMTree($level, "new_config"); +## validateTmplPath("path") +# return whether specified path is a valid template path +sub validateTmplPath { + my ($self, $path, $validate_vals) = @_; + return 1 if ($self->{_cstore}->validateTmplPath($self->get_path_comps($path), + $validate_vals)); + return; # note: this return is needed. } - -###### functions for templates ###### - -# $1: array representing the config node path. -# returns the filesystem path to the template of the specified node, -# or undef if the specified node path is not valid. -sub getTmplPath { - my $self = shift; - my @cfg_path = @{$_[0]}; - my $tpath = $self->{_vyatta_template_dir}; - for my $p (@cfg_path) { - if (-d "$tpath/$p") { - $tpath .= "/$p"; - next; +## parseTmplAll("path") +# return hash ref of parsed template of specified path, undef if path is +# invalid. note: if !allow_val, path must terminate at a "node", not "value". +sub parseTmplAll { + my ($self, $path, $allow_val) = @_; + my $href = $self->{_cstore}->getParsedTmpl($self->get_path_comps($path), + $allow_val); + if (defined($href)) { + # some conversions are needed + if (defined($href->{is_value}) and $href->{is_value} eq '1') { + $href->{is_value} = 1; } - if (-d "$tpath/node.tag") { - $tpath .= "/node.tag"; - next; + if (defined($href->{multi}) and $href->{multi} eq '1') { + $href->{multi} = 1; + } + if (defined($href->{tag}) and $href->{tag} eq '1') { + $href->{tag} = 1; + } + if (defined($href->{limit})) { + $href->{limit} = int($href->{limit}); } - # the path is not valid! - return; } - return $tpath; + return $href; } -sub isTagNode { - my $self = shift; - my $cfg_path_ref = shift; - my $tpath = $self->getTmplPath($cfg_path_ref); - return unless $tpath; +sub hasTmplChildren { + my ($self, $path) = @_; + my $ref = $self->{_cstore}->tmplGetChildNodes($self->get_path_comps($path)); + return if (!defined($ref)); + return (scalar(@{$ref}) > 0); +} + + +###### +# "deactivate-aware" observers of current working config or active config. +# * MUST ONLY be used by operations that NEED to distinguish between +# deactivated nodes and deleted nodes. below is the list of operations +# that are allowed to use these functions: +# * configuration output (show, save, load) +# +# operations that are not on the above list MUST NOT use these +# "deactivate-aware" functions. + +## deactivated("node") +# return whether specified node is deactivated in working config. +# note that this is different from "marked deactivated". if a node is +# "marked deactivated", then the node itself and any descendants are +# "deactivated". +sub deactivated { + my ($self, $path) = @_; + return 1 + if ($self->{_cstore}->cfgPathDeactivated($self->get_path_comps($path), + undef)); + return; # note: this return is needed. +} + +## deactivatedOrig("node") +# return whether specified node is deactivated in active config. +sub deactivatedOrig { + my ($self, $path) = @_; + return 1 + if ($self->{_cstore}->cfgPathDeactivated($self->get_path_comps($path), 1)); + return; # note: this return is needed. +} + +## returnValuesDA("node") +# DA version of returnValues() +sub returnValuesDA { + my ($self, $path) = @_; + my $ref = $self->{_cstore}->cfgPathGetValuesDA($self->get_path_comps($path), + undef); + return @{$ref}; +} + +## returnOrigValuesDA("node") +# DA version of returnOrigValues() +sub returnOrigValuesDA { + my ($self, $path) = @_; + my $ref = $self->{_cstore}->cfgPathGetValuesDA($self->get_path_comps($path), + 1); + return @{$ref}; +} + +## returnValueDA("node") +# DA version of returnValue() +sub returnValueDA { + my ($self, $path) = @_; + return $self->{_cstore}->cfgPathGetValueDA($self->get_path_comps($path), + undef); +} + +## returnOrigValueDA("node") +# DA version of returnOrigValue() +sub returnOrigValueDA { + my ($self, $path) = @_; + return $self->{_cstore}->cfgPathGetValueDA($self->get_path_comps($path), 1); +} + +## listOrigNodesDA("level") +# DA version of listOrigNodes() +sub listOrigNodesDA { + my ($self, $path) = @_; + my $ref = $self->{_cstore}->cfgPathGetChildNodesDA( + $self->get_path_comps($path), 1); + return @{$ref}; +} + +## listNodeStatusDA("level") +# DA version of listNodeStatus() +sub listNodeStatusDA { + my ($self, $path) = @_; + my $ref = $self->{_cstore}->cfgPathGetChildNodesStatusDA( + $self->get_path_comps($path)); + return %{$ref}; +} - return (-d "$tpath/node.tag"); +## returnComment("node") +# return comment of "node" or undef if comment doesn't exist +sub returnComment { + my ($self, $path) = @_; + return $self->{_cstore}->cfgPathGetComment($self->get_path_comps($path), + undef); } -sub hasTmplChildren { - my $self = shift; - my $cfg_path_ref = shift; - my $tpath = $self->getTmplPath($cfg_path_ref); - return unless $tpath; - opendir (my $tdir, $tpath) or return; - my @tchildren = grep !/^node\.def$/, (grep !/^\./, (readdir $tdir)); - closedir $tdir; +############################################################ +# high-level API functions (not using the cstore library directly) +############################################################ - return (scalar(@tchildren) > 0); +## setLevel("level") +# set the current level of config hierarchy to specified level (if defined). +# return the current level. +sub setLevel { + my ($self, $level) = @_; + $self->{_level} = $level if defined($level); + return $self->{_level}; } -# $cfg_path_ref: ref to array containing the node path. -# returns ($is_multi, $is_text, $default), -# or undef if specified node is not valid. +## returnParent("..( ..)*") +# return the name of ancestor node relative to the current level. +# each level up is represented by a ".." in the argument. +sub returnParent { + my ($self, $ppath) = @_; + my @pcomps = @{$self->get_path_comps()}; + # we could call split in scalar context but that generates a warning + my @dummy = split(/\s+/, $ppath); + my $num = scalar(@dummy); + return if ($num > scalar(@pcomps)); + return $pcomps[-$num]; +} + +## parseTmpl("path") +# parse template of specified path and return ($is_multi, $is_text, $default) +# or undef if specified path is not valid. sub parseTmpl { - my $self = shift; - my $cfg_path_ref = shift; - my ($is_multi, $is_text, $default) = (0, 0, undef); - my $tpath = $self->getTmplPath($cfg_path_ref); - return unless $tpath; - - if (! -r "$tpath/node.def") { - return ($is_multi, $is_text); - } - - open (my $tmpl, '<', "$tpath/node.def") - or return ($is_multi, $is_text); - foreach (<$tmpl>) { - if (/^multi:/) { - $is_multi = 1; - } - if (/^type:\s+txt\s*$/) { - $is_text = 1; - } - if (/^default:\s+(\S+)\s*$/) { - $default = $1; - } - } - close $tmpl; + my ($self, $path) = @_; + my $href = $self->parseTmplAll($path); + return if (!defined($href)); + my $is_multi = $href->{multi}; + my $is_text = (defined($href->{type}) and $href->{type} eq 'txt'); + my $default = $href->{default}; return ($is_multi, $is_text, $default); } -# $cfg_path: config path of the node. -# returns a hash ref containing attributes in the template -# or undef if specified node is not valid. -sub parseTmplAll { - my ($self, $cfg_path) = @_; - my @pdirs = split(/ +/, $cfg_path); - my %ret = (); - my $tpath = $self->getTmplPath(\@pdirs); - return unless $tpath; - - open(my $tmpl, '<', "$tpath/node.def") - or return; - foreach (<$tmpl>) { - if (/^multi:\s*(\S+)?$/) { - $ret{multi} = 1; - $ret{limit} = $1; - } elsif (/^tag:\s*(\S+)?$/) { - $ret{tag} = 1; - $ret{limit} = $1; - } elsif (/^type:\s*(\S+),\s*(\S+)\s*$/) { - $ret{type} = $1; - $ret{type2} = $2; - } elsif (/^type:\s*(\S+)\s*$/) { - $ret{type} = $1; - } elsif (/^default:\s*(\S.*)\s*$/) { - $ret{default} = $1; - if ($ret{default} =~ /^"(.*)"$/) { - $ret{default} = $1; - } - } elsif (/^help:\s*(\S.*)$/) { - $ret{help} = $1; - } elsif (/^enumeration:\s*(\S+)$/) { - $ret{enum} = $1; - } - } - close($tmpl); - return \%ret; +## isTagNode("path") +# whether specified path is a tag node. +sub isTagNode { + my ($self, $path) = @_; + my $href = $self->parseTmplAll($path); + return (defined($href) and $href->{tag}); } -# $cfg_path: config path of the node. -# returns the list of the node's children in the template hierarchy. -sub getTmplChildren { - my ($self, $cfg_path) = @_; - my @pdirs = split(/ +/, $cfg_path); - my $tpath = $self->getTmplPath(\@pdirs); - return () unless $tpath; - - opendir (my $tdir, $tpath) or return; - my @tchildren = grep !/^node\.def$/, (grep !/^\./, (readdir $tdir)); - closedir $tdir; - - return @tchildren; +## isLeafNode("path") +# whether specified path is a "leaf node", i.e., single-/multi-value node. +sub isLeafNode { + my ($self, $path) = @_; + my $href = $self->parseTmplAll($path, 1); + return (defined($href) and !$href->{is_value} and $href->{type} + and !$href->{tag}); } -###### misc functions ###### +## isLeafValue("path") +# whether specified path is a "leaf value", i.e., value of a leaf node. +sub isLeafValue { + my ($self, $path) = @_; + my $href = $self->parseTmplAll($path, 1); + return (defined($href) and $href->{is_value} and !$href->{tag}); +} # compare two value lists and return "deleted" and "added" lists. # since this is for multi-value nodes, there is no "changed" (if a value's @@ -988,4 +640,39 @@ sub compareValueLists { return %comp_hash; } +############################################################ +# API functions that have not been converted +############################################################ + +# XXX the following function should not be needed. the only user is +# ConfigLoad, which uses this to get all deactivated nodes in active +# config and then reactivates everything on load. +# +# this works for "load" but not for "merge", which incorrectly +# reactivates all deactivated nodes even if they are not in the config +# file to be merged. see bug 5746. +# +# how to get rid of this function depends on how bug 5746 is going +# to be fixed. +## getAllDeactivated() +# returns array of all deactivated nodes. +my @all_deactivated_nodes; +sub getAllDeactivated { + my ($self, $path) = @_; + my $start_dir = $ENV{VYATTA_ACTIVE_CONFIGURATION_DIR}; + find ( \&wanted, $start_dir ); + return @all_deactivated_nodes; +} +sub wanted { + if ( $_ eq '.disable' ) { + my $f = $File::Find::name; + #now strip off leading path and trailing file + $f = substr($f, length($ENV{VYATTA_ACTIVE_CONFIGURATION_DIR})); + $f = substr($f, 0, length($f)-length("/.disable")); + $f =~ s/\// /g; + push @all_deactivated_nodes, $f; + } +} + 1; + diff --git a/lib/Vyatta/ConfigDOMTree.pm b/lib/Vyatta/ConfigDOMTree.pm deleted file mode 100755 index 87fc8f2..0000000 --- a/lib/Vyatta/ConfigDOMTree.pm +++ /dev/null @@ -1,366 +0,0 @@ -# -# **** License **** -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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. -# -# A copy of the GNU General Public License is available as -# `/usr/share/common-licenses/GPL' in the Debian GNU/Linux distribution -# or on the World Wide Web at `http://www.gnu.org/copyleft/gpl.html'. -# You can also obtain it by writing to the Free Software Foundation, -# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, -# MA 02110-1301, USA. -# -# This code was originally developed by Vyatta, Inc. -# Portions created by Vyatta are Copyright (C) 2005, 2006, 2007 Vyatta, Inc. -# All Rights Reserved. -# -# Author: Oleg Moskalenko -# Date: 2007 -# Description: -# -# **** End License **** -# -# - -package Vyatta::ConfigDOMTree; - -use strict; - -my %fields = ( - _dir => undef, - _name => undef, - _value => undef, - _subnodes => undef - ); - -sub new { - - my $that = shift; - my $dir = shift; - my $name = shift; - - my $class = ref ($that) || $that; - - my $self = { - %fields - }; - - bless $self, $class; - - $self->{_dir} = $dir; - $self->{_name} = $name; - - return $self->_construct_dom_tree(); -} - -#Simple DOM Tree iteration and screen output -#$1 - left screen offset (optional) -sub print { - - my $self = shift; - my $level = shift; - - my $tree = $self; - - if(!(defined $level)) { - $level=""; - } - - if(defined $tree) { - - print("$level name=",$tree->getNodeName(),"\n"); - - my $value = $tree->getNodeValue(); - - if(defined $value) { - - print("$level value=$value\n"); - - } - - my @subnodes = $tree->getSubNodes(); - - while(@subnodes) { - - my $subnode = shift @subnodes; - $subnode->print($level . " "); - } - } -} - -#Return value of the tree node -sub getNodeValue { - - my $self = shift; - my $tree = $self; - - my $ret = undef; - - if(defined $tree) { - - $ret = $tree->{_value}; - } - - return $ret; -} - -#Return value of the tree node. -#If the value is nor defined, return empty string. -sub getNodeValueAsString { - - my $self = shift; - my $tree = $self; - - my $ret = undef; - - if(defined $tree) { - - $ret = $tree->getNodeValue(); - } - - if(!defined $ret) { - $ret = ""; - } - - return $ret; -} - -#Return name of the tree node -sub getNodeName { - - my $self = shift; - my $tree = $self; - - my $ret = undef; - - if(defined $tree) { - - $ret = $tree->{_name}; - } - - return $ret; -} - -#Return array of subnodes of the tree node -sub getSubNodes { - - my $self = shift; - my $tree = $self; - - my @ret = (); - - if(defined $tree) { - - my $subnodes = $tree->{_subnodes}; - - if(defined $subnodes) { - - @ret = values %{$subnodes}; - - } - } - - return @ret; -} - -sub isLeafNode { - - my $self = shift; - my $tree = $self; - - my $ret=undef; - - if(defined $tree) { - - if(defined $tree->{_value}) { - - if(! defined $tree->{_subnodes}) { - - $ret="true"; - } - } - } - - return $ret; -} - -#Return subtree of the tree according to the path list -#$1, $2, ... - path to the subtree -sub getSubNode { - - my $self = shift; - my $tree = $self; - - my $ret = undef; - - while(@_ && $tree) { - - my $subnode = shift (@_); - - my $subnodes = $tree->{_subnodes}; - - if(defined $subnodes) { - - $tree = $subnodes->{$subnode}; - - } else { - - $tree = undef; - - } - } - - $ret=$tree; - - return $ret; -} - -#Return value of the subnode of the tree according to the path list -#$1, $2, ... - path to the subtree -sub getSubNodeValue { - - my $self = shift; - my $tree = $self; - - my $ret = undef; - - if(defined $tree) { - - my $node = $tree->getSubNode(@_); - - if(defined $node) { - - $ret=$node->getNodeValue(); - } - } - - return $ret; -} - -#Return value of the subnode of the tree according to the path list. -#If the value is not defined, return empty string. -#$1, $2, ... - path to the subtree -sub getSubNodeValueAsString { - - my $self = shift; - my $tree = $self; - - my $ret = undef; - - if(defined $tree) { - - my $node = $tree->getSubNode(@_); - - if(defined $node) { - - $ret=$node->getNodeValue(); - } - } - - if(! defined $ret) { - $ret = ""; - } - - return $ret; -} - -#Check if there is a subnode with the specified path. -#$1, $2, ... - path to the subtree -sub subNodeExist { - - my $self = shift; - my $tree = $self; - - my $ret = undef; - - if(defined $tree) { - - my $node = $tree->getSubNode(@_); - - if(defined $node) { - - $ret="true"; - } - } - - return $ret; -} - -#Return of the children of the node -#$1, $2, ... - path to the subtree -sub getSubNodesNumber { - - my $self = shift; - my $tree = $self; - - my $ret = 0; - - if(defined $tree) { - - my $node = $tree->getSubNode(@_); - - if(defined $node) { - - my @subs = $node->getSubNodes(); - - if(@subs) { - $ret = $#subs + 1; - } - } - } - - return $ret; -} - -#private method: costruct DOM Tree according to the absolute path provided -sub _construct_dom_tree { - - my $self = shift; - - my $subnodesNum=0; - my $valuePresent=0; - - return unless defined $self; - - opendir my $dir, $self->{_dir} or return; - my @entries = grep !/^\./, readdir $dir; - closedir $dir; - - while(@entries) { - - my $entry = shift @entries; - - if($entry) { - my $fn = $self->{_dir} . "/" . $entry; - if( -f $fn) { - if($entry eq "node.val") { - my $value=`cat $fn`; - while(chomp $value) {}; - $self->{_value} = $value; - $valuePresent++; - } - } elsif (-d $fn) { - my $subnode = new Vyatta::ConfigDOMTree($fn,$entry); - if(defined $subnode) { - if(! defined $self->{_subnodes} ) { - $self->{_subnodes} = {}; - } - $self->{_subnodes}->{$entry} = $subnode; - $subnodesNum++; - } - } - } - } - - return if ( $valuePresent < 1 && $subnodesNum < 1 ); - - return $self; -} - -1; diff --git a/lib/Vyatta/ConfigLoad.pm b/lib/Vyatta/ConfigLoad.pm index c806c7f..d3d7dbb 100755 --- a/lib/Vyatta/ConfigLoad.pm +++ b/lib/Vyatta/ConfigLoad.pm @@ -1,4 +1,4 @@ -# Author: An-Cheng Huang <ancheng@vyatta.com> +# Author: Vyatta <eng@vyatta.com> # Date: 2007 # Description: Perl module for loading configuration. @@ -261,8 +261,9 @@ my @delete_list = (); sub findDeletedValues { my $new_ref = $_[0]; my @active_path = @{$_[1]}; - my ($is_multi, $is_text) = $active_cfg->parseTmpl(\@active_path); + $active_cfg->setLevel(join ' ', @active_path); + my ($is_multi, $is_text) = $active_cfg->parseTmpl(); if ($is_multi) { # for "multi:" nodes, need to sort the values by the original order. my @nvals = getSortedMultiValues($new_ref, \@active_path); @@ -287,10 +288,9 @@ sub findDeletedValues { # $1: array ref representing current config path (active config) sub findDeletedNodes { my $new_ref = $_[0]; - my $ret_dis_flag = $_[1]; - my @active_path = @{$_[2]}; + my @active_path = @{$_[1]}; $active_cfg->setLevel(join ' ', @active_path); - my @active_nodes = $active_cfg->listOrigNodes(undef,$ret_dis_flag); + my @active_nodes = $active_cfg->listOrigNodesDA(); foreach (@active_nodes) { if ($_ eq 'def') { next; @@ -303,7 +303,7 @@ sub findDeletedNodes { my @plist = applySingleQuote(@active_path, $_); push @delete_list, [\@plist, 0]; } else { - findDeletedNodes($new_ref->{$_}, $ret_dis_flag, [ @active_path, $_ ]); + findDeletedNodes($new_ref->{$_}, [ @active_path, $_ ]); } } } @@ -317,8 +317,9 @@ my @set_list = (); sub findSetValues { my $new_ref = $_[0]; my @active_path = @{$_[1]}; - my ($is_multi, $is_text) = $active_cfg->parseTmpl(\@active_path); + $active_cfg->setLevel(join ' ', @active_path); + my ($is_multi, $is_text) = $active_cfg->parseTmpl(); if ($is_multi) { # for "multi:" nodes, need to sort the values by the original order. my @nvals = getSortedMultiValues($new_ref, \@active_path); @@ -354,7 +355,7 @@ sub findSetNodes { $active_cfg->setLevel(join ' ', @active_path); my @active_nodes = $active_cfg->listOrigNodes(); my %active_hash = map { $_ => 1 } @active_nodes; - my $nref = $active_cfg->parseTmplAll(join ' ', @active_path); + my $nref = $active_cfg->parseTmplAll(); if (defined($nref->{type}) and !defined($nref->{tag})) { # we are at a leaf node. findSetValues($new_ref, \@active_path); @@ -385,11 +386,10 @@ sub findSetNodes { sub getConfigDiff { $active_cfg = new Vyatta::Config; $new_cfg_ref = shift; - my $ret_del_dis_nodes = shift; @set_list = (); # @disable_list = (); @delete_list = (); - findDeletedNodes($new_cfg_ref, $ret_del_dis_nodes, [ ]); + findDeletedNodes($new_cfg_ref, [ ]); findSetNodes($new_cfg_ref, [ ]); # need to filter out deletions of nodes with default values @@ -401,7 +401,8 @@ sub getConfigDiff { $file; } @{${$del}[0]}; - my ($is_multi, $is_text, $default) = $active_cfg->parseTmpl(\@comps); + my ($is_multi, $is_text, $default) + = $active_cfg->parseTmpl(join ' ', @comps); if (!defined($default)) { push @new_delete_list, $del; } diff --git a/lib/Vyatta/ConfigOutput.pm b/lib/Vyatta/ConfigOutput.pm index 1218a8d..acb2bb3 100755 --- a/lib/Vyatta/ConfigOutput.pm +++ b/lib/Vyatta/ConfigOutput.pm @@ -70,7 +70,9 @@ sub displayValues { my $prefix = $_[2]; my $name = $_[3]; my $simple_show = $_[4]; - my ($is_multi, $is_text, $default) = $config->parseTmpl(\@cur_path); + + $config->setLevel(join ' ', @cur_path); + my ($is_multi, $is_text, $default) = $config->parseTmpl(); if ($is_text) { $default =~ /^"(.*)"$/; my $txt = $1; @@ -81,11 +83,10 @@ sub displayValues { my $is_password = ($name =~ /^.*(passphrase|password|pre-shared-secret|key)$/); my $HIDE_PASSWORD = '****************'; - $config->setLevel(join ' ', @cur_path); if ($is_multi) { - my @ovals = $config->returnOrigValues('','true'); - my @nvals = $config->returnValues('','true'); + my @ovals = $config->returnOrigValuesDA(); + my @nvals = $config->returnValuesDA(); if ($is_text) { @ovals = map { (txt_need_quotes($_)) ? "\"$_\"" : "$_"; } @ovals; @nvals = map { (txt_need_quotes($_)) ? "\"$_\"" : "$_"; } @nvals; @@ -128,8 +129,8 @@ sub displayValues { print "$dis$diff$prefix$name $nval\n"; } } else { - my $oval = $config->returnOrigValue('','true'); - my $nval = $config->returnValue('','true'); + my $oval = $config->returnOrigValueDA(); + my $nval = $config->returnValueDA(); if ($is_text) { if (defined($oval) && txt_need_quotes($oval)) { $oval = "\"$oval\""; @@ -139,7 +140,7 @@ sub displayValues { } } - my %cnodes = $config->listNodeStatus(undef,'true'); + my %cnodes = $config->listNodeStatusDA(); my @cnames = sort keys %cnodes; if (defined($simple_show)) { @@ -189,16 +190,19 @@ sub displayDeletedOrigChildren { if (defined($dont_show_as_deleted)) { $dprefix = ''; } - $config->setLevel(''); - my @children = $config->listOrigNodes(join(' ', @cur_path),'true'); + $config->setLevel(''); + my @children = $config->listOrigNodesDA(join(' ', @cur_path)); for my $child (sort @children) { + # reset level + $config->setLevel(''); + if ($child eq 'node.val') { # should not happen! next; } - my $is_tag = $config->isTagNode([ @cur_path, $child ]); + my $is_tag = $config->isTagNode(join(' ', @cur_path, $child)); if (!defined $is_tag) { my $path = join(' ',( @cur_path, $child )); @@ -207,31 +211,37 @@ sub displayDeletedOrigChildren { print "$prefix /* $comment */\n"; } - my ($state, $n) = $config->getDeactivated($path); - if (defined $state) { - if ($state eq 'active') { - $dis = '! '; - } - elsif ($state eq 'local') { - if (defined($dont_show_as_deleted)) { - $dis = ' '; - } - else { - $dis = 'D '; - } - } - else { - $dis = '! '; - } - } - else { - $dis = ' '; - } + # check deactivate state + my $de_working = $config->deactivated($path); + my $de_active = $config->deactivatedOrig($path); + if ($de_active) { + if ($de_working) { + # deactivated in both + $dis = '! '; + } else { + # deactivated only in active + $dis = '! '; + } + } else { + if ($de_working) { + # deactivated only in working + if (defined($dont_show_as_deleted)) { + $dis = ' '; + } else { + $dis = 'D '; + } + } else { + # deactivated in neither + $dis = ' '; + } + } } $config->setLevel(join ' ', (@cur_path, $child)); - my @cnames = grep(!/^def$/, sort($config->listOrigNodes(undef,'true'))); + # remove listOrigNodesNoDef so it's one fewer function that uses + # the cstore implementation details at the perl API level. + my @cnames = grep(!/^def$/, sort($config->listOrigNodesDA())); if ($cnames[0] eq 'node.val') { displayValues([ @cur_path, $child ], $dis, $prefix, $child, @@ -248,33 +258,37 @@ sub displayDeletedOrigChildren { } my $path = join(' ',( @cur_path, $child, $cname )); + $config->setLevel($path); - my $comment = $config->returnComment($path); + my $comment = $config->returnComment(); if (defined $comment) { print "$prefix /* $comment */\n"; } - #need separate check here - my ($state, $n) = $config->getDeactivated($path); - if (defined $state) { - if ($state eq 'active') { - $dis = '! '; - } - elsif ($state eq 'local') { - if (defined($dont_show_as_deleted)) { - $dis = ' '; - } - else { - $dis = 'D '; - } - } - else { - $dis = '! '; - } - } - else { - $dis = ' '; - } + # check deactivate state + my $de_working = $config->deactivated(); + my $de_active = $config->deactivatedOrig(); + if ($de_active) { + if ($de_working) { + # deactivated in both + $dis = '! '; + } else { + # deactivated only in active + $dis = '! '; + } + } else { + if ($de_working) { + # deactivated only in working + if (defined($dont_show_as_deleted)) { + $dis = ' '; + } else { + $dis = 'D '; + } + } else { + # deactivated in neither + $dis = ' '; + } + } print "$dis$dprefix$prefix$child $cname {\n"; displayDeletedOrigChildren([ @cur_path, $child, $cname ], @@ -288,7 +302,7 @@ sub displayDeletedOrigChildren { print "$dis$dprefix$prefix}\n"; } } else { - my $has_tmpl_children = $config->hasTmplChildren([ @cur_path, $child ]); + my $has_tmpl_children = $config->hasTmplChildren(); print "$dis$dprefix$prefix$child" . ($has_tmpl_children ? " {\n$dis$dprefix$prefix}\n" : "\n"); } @@ -323,7 +337,9 @@ sub displayChildren { } elsif ($child_hash{$child} eq 'changed') { $vdiff = '>'; } - my $is_tag = $config->isTagNode([ @cur_path, $child ]); + + $config->setLevel(''); + my $is_tag = $config->isTagNode(join(' ', @cur_path, $child)); if (!defined($is_tag)) { my $path = join(' ',( @cur_path, $child )); @@ -332,30 +348,34 @@ sub displayChildren { print "$prefix /* $comment */\n"; } - my ($state, $n) = $config->getDeactivated($path); - if (defined $state) { - if ($state eq 'active') { - if ($child_hash{$child} eq 'deleted') { - $dis = '! '; - } - else { - $dis = 'A '; - } - } - elsif ($state eq 'local') { - $dis = 'D '; - } - else { - $dis = '! '; - } - } - else { - $dis = ' '; - } + # check deactivate state + my $de_working = $config->deactivated($path); + my $de_active = $config->deactivatedOrig($path); + if ($de_active) { + if ($de_working) { + # deactivated in both + $dis = '! '; + } else { + # deactivated only in active + if ($child_hash{$child} eq 'deleted') { + $dis = '! '; + } else { + $dis = 'A '; + } + } + } else { + if ($de_working) { + # deactivated only in working + $dis = 'D '; + } else { + # deactivated in neither + $dis = ' '; + } + } } $config->setLevel(join ' ', (@cur_path, $child)); - my %cnodes = $config->listNodeStatus(undef,'true'); + my %cnodes = $config->listNodeStatusDA(); my @cnames = sort keys %cnodes; #if node.val exists and ct == 0 w/o def or ct ==1 w/ def @@ -382,31 +402,36 @@ sub displayChildren { } my $path = join(' ',( @cur_path, $child, $cname )); - my $comment = $config->returnComment($path); + $config->setLevel($path); + my $comment = $config->returnComment(); if (defined $comment) { print "$prefix /* $comment */\n"; } - my ($state, $n) = $config->getDeactivated($path); - if (defined $state) { - if ($state eq 'active') { - if ($cnodes{$cname} eq 'deleted') { - $dis = '! '; - } - else { - $dis = 'A '; - } - } - elsif ($state eq 'local') { - $dis = 'D '; - } - else { - $dis = '! '; - } - } - else { - $dis = ' '; - } + # check deactivate state + my $de_working = $config->deactivated(); + my $de_active = $config->deactivatedOrig(); + if ($de_active) { + if ($de_working) { + # deactivated in both + $dis = '! '; + } else { + # deactivated only in active + if ($cnodes{$cname} eq 'deleted') { + $dis = '! '; + } else { + $dis = 'A '; + } + } + } else { + if ($de_working) { + # deactivated only in working + $dis = 'D '; + } else { + # deactivated in neither + $dis = ' '; + } + } my $tdiff = ' '; if ($cnodes{$cname} eq 'deleted') { @@ -420,7 +445,7 @@ sub displayChildren { $dis, "$prefix "); } else { $config->setLevel(join ' ', (@cur_path, $child, $cname)); - my %ccnodes = $config->listNodeStatus(undef,'true'); + my %ccnodes = $config->listNodeStatusDA(); displayChildren(\%ccnodes, [ @cur_path, $child, $cname ], $dis, "$prefix "); } @@ -439,7 +464,7 @@ sub displayChildren { } else { if ($child_hash{$child} eq 'deleted') { $config->setLevel(''); - my @onodes = $config->listOrigNodes(join ' ', (@cur_path, $child), 'true'); + my @onodes = $config->listOrigNodesDA(join ' ', (@cur_path, $child)); if ($#onodes == 0 && $onodes[0] eq 'node.val') { displayValues([ @cur_path, $child ], $dis, $prefix, $child); } else { @@ -449,7 +474,7 @@ sub displayChildren { } } else { my $has_tmpl_children - = $config->hasTmplChildren([ @cur_path, $child ]); + = $config->hasTmplChildren(); print "$dis$diff$prefix$child" . ($has_tmpl_children ? " {\n$dis$diff$prefix}\n" : "\n"); } @@ -462,7 +487,7 @@ sub displayChildren { sub outputNewConfig { $config = new Vyatta::Config; $config->setLevel(join ' ', @_); - my %rnodes = $config->listNodeStatus(undef,'true'); + my %rnodes = $config->listNodeStatusDA(); if (scalar(keys %rnodes) > 0) { my @rn = keys %rnodes; @@ -489,7 +514,8 @@ sub outputNewConfig { if ($config->existsOrig() && ! $config->exists()) { # this is a deleted node print 'Configuration under "' . (join ' ', @_) . "\" has been deleted\n"; - } elsif (!defined($config->getTmplPath(\@_))) { + } elsif (!$config->validateTmplPath('', 1)) { + # validation of current path (including values) failed print "Specified configuration path is not valid\n"; } else { print 'Configuration under "' . (join ' ', @_) . "\" is empty\n"; diff --git a/perl_dmod/.gitignore b/perl_dmod/.gitignore new file mode 100644 index 0000000..b336cc7 --- /dev/null +++ b/perl_dmod/.gitignore @@ -0,0 +1,2 @@ +/Makefile +/Makefile.in diff --git a/perl_dmod/Cstore/.gitignore b/perl_dmod/Cstore/.gitignore new file mode 100644 index 0000000..7082d2a --- /dev/null +++ b/perl_dmod/Cstore/.gitignore @@ -0,0 +1,5 @@ +/Makefile +/Cstore.bs +/Cstore.cpp +/blib +/pm_to_blib diff --git a/perl_dmod/Cstore/Changes b/perl_dmod/Cstore/Changes new file mode 100644 index 0000000..15e5188 --- /dev/null +++ b/perl_dmod/Cstore/Changes @@ -0,0 +1,6 @@ +Revision history for Perl extension Cstore. + +0.01 Tue Jun 15 12:03:35 2010 + - original version; created by h2xs 1.23 with options + -c --skip-ppport -n Cstore + diff --git a/perl_dmod/Cstore/Cstore.xs b/perl_dmod/Cstore/Cstore.xs new file mode 100644 index 0000000..5dd105e --- /dev/null +++ b/perl_dmod/Cstore/Cstore.xs @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2010 Vyatta, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" + +/* these macros are defined in perl headers but conflict with C++ headers */ +#undef do_open +#undef do_close + +#include <cstring> +#include <vector> +#include <string> +#include <map> + +/* currently use the UnionfsCstore implementation */ +#include <cstore/unionfs/cstore-unionfs.hpp> + +typedef SV STRVEC; +typedef SV STRSTRMAP; + +MODULE = Cstore PACKAGE = Cstore + + +Cstore * +Cstore::new() +CODE: + RETVAL = new UnionfsCstore(); +OUTPUT: + RETVAL + + +bool +Cstore::cfgPathExists(STRVEC *vref, bool active_cfg) +PREINIT: + vector<string> arg_strvec; +CODE: + RETVAL = THIS->cfgPathExists(arg_strvec, active_cfg); +OUTPUT: + RETVAL + + +STRVEC * +Cstore::cfgPathGetChildNodes(STRVEC *vref, bool active_cfg) +PREINIT: + vector<string> arg_strvec; +CODE: + vector<string> ret_strvec; + THIS->cfgPathGetChildNodes(arg_strvec, ret_strvec, active_cfg); +OUTPUT: + RETVAL + + +SV * +Cstore::cfgPathGetValue(STRVEC *vref, bool active_cfg) +PREINIT: + vector<string> arg_strvec; +CODE: + string value; + if (THIS->cfgPathGetValue(arg_strvec, value, active_cfg)) { + RETVAL = newSVpv(value.c_str(), 0); + } else { + XSRETURN_UNDEF; + } +OUTPUT: + RETVAL + + +STRVEC * +Cstore::cfgPathGetValues(STRVEC *vref, bool active_cfg) +PREINIT: + vector<string> arg_strvec; +CODE: + vector<string> ret_strvec; + THIS->cfgPathGetValues(arg_strvec, ret_strvec, active_cfg); +OUTPUT: + RETVAL + + +bool +Cstore::cfgPathEffective(STRVEC *vref) +PREINIT: + vector<string> arg_strvec; +CODE: + RETVAL = THIS->cfgPathEffective(arg_strvec); +OUTPUT: + RETVAL + + +STRVEC * +Cstore::cfgPathGetEffectiveChildNodes(STRVEC *vref) +PREINIT: + vector<string> arg_strvec; +CODE: + vector<string> ret_strvec; + THIS->cfgPathGetEffectiveChildNodes(arg_strvec, ret_strvec); +OUTPUT: + RETVAL + + +SV * +Cstore::cfgPathGetEffectiveValue(STRVEC *vref) +PREINIT: + vector<string> arg_strvec; +CODE: + string value; + if (THIS->cfgPathGetEffectiveValue(arg_strvec, value)) { + RETVAL = newSVpv(value.c_str(), 0); + } else { + XSRETURN_UNDEF; + } +OUTPUT: + RETVAL + + +STRVEC * +Cstore::cfgPathGetEffectiveValues(STRVEC *vref) +PREINIT: + vector<string> arg_strvec; +CODE: + vector<string> ret_strvec; + THIS->cfgPathGetEffectiveValues(arg_strvec, ret_strvec); +OUTPUT: + RETVAL + + +bool +Cstore::cfgPathDeleted(STRVEC *vref) +PREINIT: + vector<string> arg_strvec; +CODE: + RETVAL = THIS->cfgPathDeleted(arg_strvec); +OUTPUT: + RETVAL + + +bool +Cstore::cfgPathAdded(STRVEC *vref) +PREINIT: + vector<string> arg_strvec; +CODE: + RETVAL = THIS->cfgPathAdded(arg_strvec); +OUTPUT: + RETVAL + + +bool +Cstore::cfgPathChanged(STRVEC *vref) +PREINIT: + vector<string> arg_strvec; +CODE: + RETVAL = THIS->cfgPathChanged(arg_strvec); +OUTPUT: + RETVAL + + +STRVEC * +Cstore::cfgPathGetDeletedChildNodes(STRVEC *vref) +PREINIT: + vector<string> arg_strvec; +CODE: + vector<string> ret_strvec; + THIS->cfgPathGetDeletedChildNodes(arg_strvec, ret_strvec); +OUTPUT: + RETVAL + + +STRSTRMAP * +Cstore::cfgPathGetChildNodesStatus(STRVEC *vref) +PREINIT: + vector<string> arg_strvec; +CODE: + map<string, string> ret_strstrmap; + THIS->cfgPathGetChildNodesStatus(arg_strvec, ret_strstrmap); +OUTPUT: + RETVAL + + +STRVEC * +Cstore::cfgPathGetValuesDA(STRVEC *vref, bool active_cfg) +PREINIT: + vector<string> arg_strvec; +CODE: + vector<string> ret_strvec; + THIS->cfgPathGetValuesDA(arg_strvec, ret_strvec, active_cfg); +OUTPUT: + RETVAL + + +SV * +Cstore::cfgPathGetValueDA(STRVEC *vref, bool active_cfg) +PREINIT: + vector<string> arg_strvec; +CODE: + string value; + if (THIS->cfgPathGetValueDA(arg_strvec, value, active_cfg)) { + RETVAL = newSVpv(value.c_str(), 0); + } else { + XSRETURN_UNDEF; + } +OUTPUT: + RETVAL + + +STRVEC * +Cstore::cfgPathGetChildNodesDA(STRVEC *vref, bool active_cfg) +PREINIT: + vector<string> arg_strvec; +CODE: + vector<string> ret_strvec; + THIS->cfgPathGetChildNodesDA(arg_strvec, ret_strvec, active_cfg); +OUTPUT: + RETVAL + + +bool +Cstore::cfgPathDeactivated(STRVEC *vref, bool active_cfg) +PREINIT: + vector<string> arg_strvec; +CODE: + RETVAL = THIS->cfgPathDeactivated(arg_strvec, active_cfg); +OUTPUT: + RETVAL + + +STRSTRMAP * +Cstore::cfgPathGetChildNodesStatusDA(STRVEC *vref) +PREINIT: + vector<string> arg_strvec; +CODE: + map<string, string> ret_strstrmap; + THIS->cfgPathGetChildNodesStatusDA(arg_strvec, ret_strstrmap); +OUTPUT: + RETVAL + + +STRVEC * +Cstore::tmplGetChildNodes(STRVEC *vref) +PREINIT: + vector<string> arg_strvec; +CODE: + vector<string> ret_strvec; + THIS->tmplGetChildNodes(arg_strvec, ret_strvec); +OUTPUT: + RETVAL + + +bool +Cstore::validateTmplPath(STRVEC *vref, bool validate_vals) +PREINIT: + vector<string> arg_strvec; +CODE: + RETVAL = THIS->validateTmplPath(arg_strvec, validate_vals); +OUTPUT: + RETVAL + + +STRSTRMAP * +Cstore::getParsedTmpl(STRVEC *vref, bool allow_val) +PREINIT: + vector<string> arg_strvec; +CODE: + map<string, string> ret_strstrmap; + if (!THIS->getParsedTmpl(arg_strvec, ret_strstrmap, allow_val)) { + XSRETURN_UNDEF; + } +OUTPUT: + RETVAL + + +SV * +Cstore::cfgPathGetComment(STRVEC *vref, bool active_cfg) +PREINIT: + vector<string> arg_strvec; +CODE: + string comment; + if (THIS->cfgPathGetComment(arg_strvec, comment, active_cfg)) { + RETVAL = newSVpv(comment.c_str(), 0); + } else { + XSRETURN_UNDEF; + } +OUTPUT: + RETVAL + + diff --git a/perl_dmod/Cstore/MANIFEST b/perl_dmod/Cstore/MANIFEST new file mode 100644 index 0000000..3f4f007 --- /dev/null +++ b/perl_dmod/Cstore/MANIFEST @@ -0,0 +1,7 @@ +Changes +Makefile.PL +MANIFEST +README +Cstore.xs +t/Cstore.t +lib/Cstore.pm diff --git a/perl_dmod/Cstore/Makefile.PL b/perl_dmod/Cstore/Makefile.PL new file mode 100644 index 0000000..d3968f7 --- /dev/null +++ b/perl_dmod/Cstore/Makefile.PL @@ -0,0 +1,88 @@ +# Copyright (C) 2010 Vyatta, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, see <http://www.gnu.org/licenses/>. + +package MY; + +use 5.010000; +use ExtUtils::MakeMaker; + +my $PMOD_DIR = '$(SITEPREFIX)/share/perl5'; + +sub constants{ + my $self = shift; + my $orig_txt = $self->SUPER::constants(@_); + $orig_txt =~ s#= \$\(SITEPREFIX\)/(lib|share)/.*#= $PMOD_DIR#g; + return $orig_txt; +} + +sub c_o { + my $self = shift; + my $orig_txt = $self->SUPER::c_o(@_); + $orig_txt =~ s/\.c(\s)/.cpp$1/g; + return $orig_txt; +} + +sub xs_c { + my $self = shift; + my $orig_txt = $self->SUPER::xs_c(@_); + $orig_txt =~ s/\.c(\s)/.cpp$1/g; + return $orig_txt; +} + +sub xs_o { + my $self = shift; + my $orig_txt = $self->SUPER::xs_o(@_); + $orig_txt =~ s/\.c(\s)/.cpp$1/g; + return $orig_txt; +} + +sub install { + my $self = shift; + my $orig_txt = $self->SUPER::install(@_); + $orig_txt =~ s/pure_install doc_install/pure_install/g; + $orig_txt =~ s/\$\(INST_MAN3DIR\) .*/undef undef/g; + return $orig_txt; +} + +sub clean { + my $self = shift; + my $orig_txt = $self->SUPER::clean(@_); + $orig_txt =~ s/Cstore\.c\s/Cstore.cpp /g; + return $orig_txt; +} + +sub dynamic_lib { + my $self = shift; + my $orig_txt = $self->SUPER::dynamic_lib(@_); + $orig_txt =~ s/(\s)LD_RUN_PATH=\S+\s+/$1/g; + return $orig_txt; +} + +WriteMakefile( + NAME => 'Cstore', + VERSION_FROM => 'lib/Cstore.pm', + PREREQ_PM => {}, + ($] >= 5.005 ? + (ABSTRACT_FROM => 'lib/Cstore.pm', + AUTHOR => 'Vyatta <eng@vyatta.com>') : ()), + # note: MM will convert LIBS to absolute path in Makefile. + # => regenerate Makefile every time + LIBS => ['-L../../src/.libs -lvyatta-cfg'], + DEFINE => '', + INC => '-I../../src', + CC => 'g++', + PREFIX => '/opt/vyatta', + INSTALLDIRS => 'site', +); + diff --git a/perl_dmod/Cstore/README b/perl_dmod/Cstore/README new file mode 100644 index 0000000..84870fc --- /dev/null +++ b/perl_dmod/Cstore/README @@ -0,0 +1,33 @@ +Cstore version 0.01 +========================== + +This module provides Perl bindings to the Vyatta Cstore library. + + +INSTALLATION + +This module is installed as part of the vyatta-cfg package. + + +DEPENDENCIES + +This module requires the Vyatta Cstore library. + + +COPYRIGHT AND LICENCE + +Copyright (C) 2010 Vyatta, Inc. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2 as +published by the Free Software Foundation. + +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, see <http://www.gnu.org/licenses/>. + + diff --git a/perl_dmod/Cstore/lib/Cstore.pm b/perl_dmod/Cstore/lib/Cstore.pm new file mode 100644 index 0000000..7cf2169 --- /dev/null +++ b/perl_dmod/Cstore/lib/Cstore.pm @@ -0,0 +1,96 @@ +# Copyright (C) 2010 Vyatta, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, see <http://www.gnu.org/licenses/>. + +package Cstore; + +use 5.010000; +use strict; +use warnings; + +require Exporter; +use AutoLoader qw(AUTOLOAD); + +our @ISA = qw(Exporter); + +# Items to export into callers namespace by default. Note: do not export +# names by default without a very good reason. Use EXPORT_OK instead. +# Do not simply export all your public functions/methods/constants. + +# This allows declaration use Cstore ':all'; +# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK +# will save memory. +our %EXPORT_TAGS = ( 'all' => [ qw( + +) ] ); + +our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); + +our @EXPORT = qw( + +); + +our $VERSION = '0.01'; + +require XSLoader; +XSLoader::load('Cstore', $VERSION); + +# Preloaded methods go here. + +# Autoload methods go after =cut, and are processed by the autosplit program. + +1; +__END__ +=head1 NAME + +Cstore - Perl binding for the Vyatta Cstore library + +=head1 SYNOPSIS + + use Cstore; + my $cstore = new Cstore; + +=head1 DESCRIPTION + +This module provides the Perl binding for the Vyatta Cstore library. + +=head2 EXPORT + +None by default. + +=head1 SEE ALSO + +For more information on the Cstore library, see the documentation and +source code for the main library. + +=head1 AUTHOR + +Vyatta, Inc. E<lt>eng@vyatta.comE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2010 Vyatta, Inc. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2 as +published by the Free Software Foundation. + +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, see <http://www.gnu.org/licenses/>. + +=cut diff --git a/perl_dmod/Cstore/t/Cstore.t b/perl_dmod/Cstore/t/Cstore.t new file mode 100644 index 0000000..51d23ac --- /dev/null +++ b/perl_dmod/Cstore/t/Cstore.t @@ -0,0 +1,15 @@ +# Before `make install' is performed this script should be runnable with +# `make test'. After `make install' it should work as `perl Cstore.t' + +######################### + +# change 'tests => 1' to 'tests => last_test_to_print'; + +use Test::More tests => 1; +BEGIN { use_ok('Cstore') }; + +######################### + +# Insert your test code below, the Test::More module is use()ed here so read +# its man page ( perldoc Test::More ) for help writing this test script. + diff --git a/perl_dmod/Cstore/typemap b/perl_dmod/Cstore/typemap new file mode 100644 index 0000000..f1f6a82 --- /dev/null +++ b/perl_dmod/Cstore/typemap @@ -0,0 +1,68 @@ +# Copyright (C) 2010 Vyatta, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, see <http://www.gnu.org/licenses/>. + +Cstore * O_CPPOBJ +STRVEC * T_STRVEC_REF +STRSTRMAP * T_STRSTRMAP_REF + + +############################################################ +OUTPUT +O_CPPOBJ + sv_setref_pv($arg, CLASS, (void *) $var); + +T_STRVEC_REF + AV *results; + results = (AV *) sv_2mortal((SV *) newAV()); + for (unsigned int i = 0; i < ret_strvec.size(); i++) { + av_push(results, newSVpv(ret_strvec[i].c_str(), 0)); + } + $arg = newRV((SV *) results); + +T_STRSTRMAP_REF + HV *href = (HV *) sv_2mortal((SV *) newHV()); + map<string, string>::iterator it = ret_strstrmap.begin(); + for (; it != ret_strstrmap.end(); ++it) { + const char *key = (*it).first.c_str(); + const char *val = (*it).second.c_str(); + hv_store(href, key, strlen(key), newSVpv(val, 0), 0); + } + $arg = newRV((SV *) href); + + +############################################################ +INPUT +O_CPPOBJ + if (sv_isobject($arg) && (SvTYPE(SvRV($arg)) == SVt_PVMG)) { + $var = ($type) SvIV((SV *) SvRV($arg)); + } else { + warn(\"${Package}::$func_name(): $var is not a blessed SV reference\"); + XSRETURN_UNDEF; + } + +T_STRVEC_REF + { + int i = 0; + I32 num = 0; + if (!SvROK($arg) || SvTYPE(SvRV($arg)) != SVt_PVAV) { + XSRETURN_UNDEF; + } + num = av_len((AV *) SvRV($arg)); + /* if input array is empty, vector will be empty as well. */ + for (i = 0; i <= num; i++) { + string str = SvPV_nolen(*av_fetch((AV *) SvRV($arg), i, 0)); + arg_strvec.push_back(str); + } + } + diff --git a/perl_dmod/Makefile.am b/perl_dmod/Makefile.am new file mode 100644 index 0000000..6c12b35 --- /dev/null +++ b/perl_dmod/Makefile.am @@ -0,0 +1,25 @@ +PERL_MODS = Cstore + +# nop for all-local. make install will do a build anyway, so don't repeat +# the build here. +all-local: ; + +install-exec-local: + for pm in $(PERL_MODS); do \ + (cd $$pm; \ + perl Makefile.PL; \ + $(MAKE) $(AM_MAKEFLAGS) install); \ + done + +clean-local: + for pm in $(PERL_MODS); do \ + (cd $$pm; \ + perl Makefile.PL; \ + $(MAKE) $(AM_MAKEFLAGS) realclean); \ + done + +# nops +check-local: ; +install-data-local: ; +uninstall-local: ; + diff --git a/scripts/vyatta-activate-config.pl b/scripts/vyatta-activate-config.pl deleted file mode 100755 index 6bf6c3b..0000000 --- a/scripts/vyatta-activate-config.pl +++ /dev/null @@ -1,163 +0,0 @@ -#!/usr/bin/perl - -# Author: Michael Larson <mike@vyatta.com> -# Date: 2010 -# Description: Perl script for activating/deactivating portions of the configuration - -# **** License **** -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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. -# -# This code was originally developed by Vyatta, Inc. -# Portions created by Vyatta are Copyright (C) 2006, 2007, 2008, 2009, 2010 Vyatta, Inc. -# All Rights Reserved. -# **** End License **** - -use strict; -use warnings; -use File::Find; -use lib "/opt/vyatta/share/perl5"; - - -sub wanted { - return unless ( $_ eq '.disable' ); - print("Cannot deactivate nested elements\n"); - exit 1; -} - -sub wanted_local { - if ( $_ eq '.disable' ) { - #we'll supercede this .disable by the parent and remove this. - my $f = $File::Find::name; - `rm -f $f`; - } -} - -sub check_parents { - my @p = @_; - my $l_dir = "$ENV{VYATTA_TEMP_CONFIG_DIR}/$ENV{VYATTA_EDIT_LEVEL}"; - my $a_dir = "$ENV{VYATTA_ACTIVE_CONFIGURATION_DIR}/$ENV{VYATTA_EDIT_LEVEL}"; - foreach my $sw (@p) { - $l_dir .= "/$sw"; - $a_dir .= "/$sw"; - - if (-e "$l_dir/.disable") { - return 1; - } - if (-e "$a_dir/.disable") { - return 1; - } - } - return 0; -} - -sub usage() { - print "Usage: $0 <activate|deactivate> <path>\n"; - exit 0; -} - -my $action = $ARGV[0]; - -if (!defined $ARGV[1] || $ARGV[1] eq '') { - print("Cannot activate/deactivate configuration root\n"); - exit 1; -} - -#adjust for leaf node -my $i = 0; -my $arg_ct = $#ARGV; -my @path = @ARGV[1..$arg_ct]; -my @parent_path = @ARGV[1..($arg_ct-1)]; - -foreach my $elem (@path) { - $elem =~ s/\//%2F/g; - $elem =~ s/\s+/\//g; - $path[$i++] = $elem; -} -my $edit_level = "$ENV{VYATTA_EDIT_LEVEL}"; - -my $path = $edit_level . join '/', @path; - -my $full_path = "$ENV{VYATTA_TEMP_CONFIG_DIR}/$path"; - -if (-e $full_path) { - my $leaf = "$full_path/node.val"; - if (-e $leaf) { - #prevent setting on leaf or multi, check for node.val - if (!defined $ENV{BOOT}) { - printf("Cannot deactivate end node\n"); - } - exit 1; - } -} -else { - #check if this is a leaf node with value - my $parent_path_leaf = $ENV{VYATTA_TEMP_CONFIG_DIR} . "/" . $edit_level . join('/', @parent_path) . "/node.val"; - if (-e $parent_path_leaf) { - #prevent setting on leaf or multi, check for node.val - if (!defined $ENV{BOOT}) { - printf("Cannot deactivate end node\n"); - } - exit 1; - } - if (!defined $ENV{BOOT}) { - printf("This configuration element does not exist: " . join(' ', @path) . "\n"); - } - exit 1; -} - -####################################################### -#now check for nesting of the activate/deactivate nodes -####################################################### -if ($action eq 'deactivate') { - my $active_dir = "$ENV{VYATTA_ACTIVE_CONFIGURATION_DIR}/$path"; - my $local_dir = $full_path; - if (-e $active_dir && !(-e "$active_dir/.disable")) { #checks active children - find( \&wanted, $active_dir ); - } - if (-e $local_dir) { #checks locally commit children, will remove disabled children - find( \&wanted_local, $local_dir ); - } - #final check that walks up tree and checks - if (!(-e "$active_dir/.disable") && check_parents(@path)) { #checks active and locally committed parents - if (!defined $ENV{BOOT}) { - print("Cannot deactivate nested elements\n"); - } - exit 1; - } -} - -####################################################### -#now apply the magic -####################################################### -if ($action eq 'activate') { - $full_path .= "/.disable"; - if (-e $full_path) { - `rm -f $full_path`; - } - else { - printf("This element has not been deactivated\n"); - exit 1; - } -} -elsif ($action eq 'deactivate') { - #first let's check and ensure that there is not another child .disable node... - #also needs to be enforced when committing - my $active_dir = "$ENV{VYATTA_ACTIVE_CONFIGURATION_DIR}/$path"; - my $local_dir = $full_path; - `touch $full_path/.disable`; -} -else { - printf("bad argument: " . $action . "\n"); - usage(); -} - -`touch $ENV{VYATTA_TEMP_CONFIG_DIR}/.modified`; - -exit 0; diff --git a/scripts/vyatta-cfg-cmd-wrapper b/scripts/vyatta-cfg-cmd-wrapper index 070b64f..e04faeb 100755 --- a/scripts/vyatta-cfg-cmd-wrapper +++ b/scripts/vyatta-cfg-cmd-wrapper @@ -1,6 +1,6 @@ #!/bin/bash -# Author: An-Cheng Huang <ancheng@vyatta.com> +# Author: Vyatta <eng@vyatta.com> # Date: 2007 # Description: command wrapper @@ -19,21 +19,12 @@ # All Rights Reserved. # **** End License **** -if grep -q union=aufs /proc/cmdline || grep -q aufs /proc/filesystems ; then - export UNIONFS=aufs -else - export UNIONFS=unionfs -fi - -# permissions -## note: this script should be running as the vyattacfg group, e.g., with "sg". -## otherwise there may be permission problems with the files created. -UMASK_SAVE=`umask` -umask 0002 +# note: this script MUST be running as the vyattacfg group, e.g., with "sg". +# otherwise there WILL be permission problems with the files created. -export VYATTA_EDIT_LEVEL=/; -export VYATTA_TEMPLATE_LEVEL=/; -export VYATTA_ACTIVE_CONFIGURATION_DIR=/opt/vyatta/config/active; +# some env variables are needed +export vyatta_sysconfdir=/opt/vyatta/etc +export vyatta_sbindir=/opt/vyatta/sbin # allow env variable to override default session id (ppid). this enables # the script to handle cases where the invocations can come from @@ -42,167 +33,26 @@ SID=$PPID if [ -n "$CMD_WRAPPER_SESSION_ID" ]; then SID=$CMD_WRAPPER_SESSION_ID fi -export VYATTA_CHANGES_ONLY_DIR=/tmp/changes_only_$SID; -export VYATTA_TEMP_CONFIG_DIR=/opt/vyatta/config/tmp/new_config_$SID; -export VYATTA_CONFIG_TMP=/opt/vyatta/config/tmp/tmp_$SID; - -vyatta_escape () -{ - # copied over from /etc/bash_completion.d/20vyatta-cfg - # $1: \$original - # $2: \$escaped - eval "$2=\${$1//\%/%25}" - eval "$2=\${$2//\*/%2A}" - eval "$2=\${$2//\//%2F}" -} - -mvcp () -{ - # copied over from /etc/bash_completion.d/20vyatta-cfg - local str=$1 - shift - local Str=$1 - shift - local cmd=$1 - shift - local _otag=$1 - local _ovalu=$2 - local _to=$3 - local _ntag=$4 - local _nvalu=$5 - local _oval='' - local _nval='' - local _mpath=${VYATTA_TEMP_CONFIG_DIR}/${VYATTA_EDIT_LEVEL} - local _tpath=${VYATTA_CONFIG_TEMPLATE}/${VYATTA_TEMPLATE_LEVEL} - vyatta_escape _ovalu _oval - vyatta_escape _nvalu _nval - if [ "$_to" != 'to' ] || [ -z "$_ntag" ] || [ -z "$_nval" ]; then - echo "Invalid $str command" - return 1 - fi - if [ "$_otag" != "$_ntag" ]; then - echo "Cannot $str from \"$_otag\" to \"$_ntag\"" - return 1 - fi - if [ ! -d "$_tpath/$_otag/$VYATTA_TAG_NAME" ]; then - echo "Cannot $str under \"$_otag\"" - return 1 - fi - if [ ! -d "$_mpath/$_otag/$_oval" ]; then - echo "Configuration \"$_otag $_ovalu\" does not exist" - return 1 - fi - if [ -d "$_mpath/$_ntag/$_nval" ]; then - echo "Configuration \"$_ntag $_nvalu\" already exists" - return 1 - fi - if ! /opt/vyatta/sbin/my_set $_ntag "$_nvalu"; then - echo "$Str failed" - return 1 - fi - /opt/vyatta/sbin/my_delete $_ntag "$_nvalu" >&/dev/null 3>&1 - - $cmd "$_mpath/$_otag/$_oval" "$_mpath/$_ntag/$_nval" - - return 0 -} - -do_move () -{ - local -a args=("$@") - local pargc - (( pargc = ${#args[@]} - 4 )) - if (( pargc < 1 )); then - echo "Invalid move command \"move $@\"" - return 1 - fi - - local -a pargs=("${args[@]:0:$pargc}") - args=("${args[@]:$pargc}") - local tag=${args[0]} - local oval=${args[1]} - local to=${args[2]} - local nval=${args[3]} - - if [ -z "$tag" ] || [ -z "$oval" ] || [ "$to" != 'to' ] \ - || [ -z "$nval" ]; then - echo "Invalid move command \"move $@\"" - return 1 - fi - - local _mpath=${VYATTA_TEMP_CONFIG_DIR}/${VYATTA_EDIT_LEVEL} - local _tpath=${VYATTA_CONFIG_TEMPLATE}/${VYATTA_TEMPLATE_LEVEL} - local idx - for (( idx = 0; idx < pargc; idx++ )); do - local comp=${pargs[$idx]} - vyatta_escape comp comp - _mpath="$_mpath/$comp" - _tpath="$_tpath/$comp" - if [ ! -d $_mpath ]; then - # node doesn't exist - break - fi - if [ -d $_tpath ]; then - # found non-tag node - continue - fi - - # check if it's tag node - _tpath=$(dirname $_tpath)/node.tag - if [ -d $_tpath ]; then - # found tag node - continue - fi - - # invalid node - break - done - if (( idx != pargc )); then - # invalid node - echo "Invalid node path \"${pargs[@]}\"" - return 1 - fi - if [[ "$_tpath" != */node.tag ]]; then - # path doesn't end with a tag value. must not have "type". - if [ ! -f "$_tpath/node.def" ]; then - echo "Invalid node path \"${pargs[@]}\"" - return 1 - fi - if grep -q '^type: ' "$_tpath/node.def"; then - echo "Invalid move command \"move $@\"" - return 1 - fi - fi - # set edit level - VYATTA_EDIT_LEVEL="${_mpath#$VYATTA_TEMP_CONFIG_DIR}/" - VYATTA_TEMPLATE_LEVEL="${_tpath#$VYATTA_CONFIG_TEMPLATE}/" - mvcp rename Rename mv "$tag" "$oval" 'to' "$tag" "$nval" -} +# set up the session environment (get it from the unified lib) +session_env=$(${vyatta_sbindir}/my_cli_shell_api getSessionEnv $SID) +eval "$session_env" RET_STATUS=0 case "$1" in begin) - # set up the environment/directories - mkdir -p $VYATTA_ACTIVE_CONFIGURATION_DIR - mkdir -p $VYATTA_CHANGES_ONLY_DIR - if [ ! -d $VYATTA_TEMP_CONFIG_DIR ]; then - mkdir -p $VYATTA_TEMP_CONFIG_DIR - sudo mount -t $UNIONFS -o dirs=${VYATTA_CHANGES_ONLY_DIR}=rw:${VYATTA_ACTIVE_CONFIGURATION_DIR}=ro $UNIONFS ${VYATTA_TEMP_CONFIG_DIR} - fi - mkdir -p $VYATTA_CONFIG_TMP + # set up the session + ${vyatta_sbindir}/my_cli_shell_api setupSession + RET_STATUS=$? ;; end) - # tear down the environment/directories - sudo umount ${VYATTA_TEMP_CONFIG_DIR} - rm -rf ${VYATTA_CHANGES_ONLY_DIR} - rm -rf ${VYATTA_CONFIG_TMP} - rm -rf ${VYATTA_TEMP_CONFIG_DIR} + # tear down the session + ${vyatta_sbindir}/my_cli_shell_api teardownSession + RET_STATUS=$? ;; cleanup|discard) - sudo umount ${VYATTA_TEMP_CONFIG_DIR} - rm -rf $VYATTA_CHANGES_ONLY_DIR/* $VYATTA_CHANGES_ONLY_DIR/.modified - sudo mount -t $UNIONFS -o dirs=${VYATTA_CHANGES_ONLY_DIR}=rw:${VYATTA_ACTIVE_CONFIGURATION_DIR}=ro $UNIONFS ${VYATTA_TEMP_CONFIG_DIR} + /opt/vyatta/sbin/my_discard + RET_STATUS=$? ;; set) /opt/vyatta/sbin/my_set "${@:2}" @@ -213,11 +63,11 @@ case "$1" in RET_STATUS=$? ;; deactivate) - /opt/vyatta/sbin/vyatta-activate-config.pl deactivate "${@:2}" + /opt/vyatta/sbin/my_deactivate "${@:2}" RET_STATUS=$? ;; activate) - /opt/vyatta/sbin/vyatta-activate-config.pl activate "${@:2}" + /opt/vyatta/sbin/my_activate "${@:2}" RET_STATUS=$? ;; show) @@ -225,7 +75,7 @@ case "$1" in RET_STATUS=$? ;; comment) - /opt/vyatta/sbin/vyatta-comment-config.pl "${@:2}" + /opt/vyatta/sbin/my_comment "${@:2}" RET_STATUS=$? ;; commit) @@ -240,41 +90,30 @@ case "$1" in RET_STATUS=$? ;; load) - export vyatta_sysconfdir=/opt/vyatta/etc - export vyatta_sbindir=/opt/vyatta/sbin /opt/vyatta/sbin/vyatta-load-config.pl "${@:2}" RET_STATUS=$? ;; rule-rename) # this option is to be used for renaming firewall and nat rules only # usage for this option specified on the next two lines - + # 2 3 4 5 6 7 8 # rule-rename firewall $firewall_ruleset rule $rule_num to rule $rename_rulenum + # 2 3 4 5 6 7 # rule-rename nat rule $rule_num to rule $rename_rulenum - if [ "$2" == "firewall" ]; then - VYATTA_TEMPLATE_LEVEL=/firewall/name/node.tag; - VYATTA_EDIT_LEVEL="/firewall/name/$3"; + /opt/vyatta/sbin/my_move firewall name "$3" rule "$5" to "$8" + RET_STATUS=$? elif [ "$2" == "nat" ]; then - VYATTA_TEMPLATE_LEVEL=/service/nat; - VYATTA_EDIT_LEVEL=/service/nat; - fi - _mpath=${VYATTA_TEMP_CONFIG_DIR}/${VYATTA_EDIT_LEVEL} - _tpath=${VYATTA_CONFIG_TEMPLATE}/${VYATTA_TEMPLATE_LEVEL} - VYATTA_EDIT_LEVEL="${_mpath#$VYATTA_TEMP_CONFIG_DIR}/" - VYATTA_TEMPLATE_LEVEL="${_tpath#$VYATTA_CONFIG_TEMPLATE}/" - if [ $2 == "firewall" ]; then - mvcp rename Rename mv "${@:4}" - elif [ $2 == "nat" ]; then - mvcp rename Rename mv "${@:3}" + /opt/vyatta/sbin/my_move service nat rule "$4" to "$7" + RET_STATUS=$? fi - RET_STATUS=$? ;; move) # this is similar to the CLI edit+rename command. # e.g., "move interfaces ethernet eth2 vif 100 to 200" # is similar to "edit interfaces ethernet eth2" plus # "rename vif 100 to vif 200". - do_move "${@:2}" + /opt/vyatta/sbin/my_move "${@:2}" RET_STATUS=$? ;; *) @@ -283,6 +122,5 @@ case "$1" in ;; esac -umask ${UMASK_SAVE} exit $RET_STATUS diff --git a/scripts/vyatta-comment-config.pl b/scripts/vyatta-comment-config.pl deleted file mode 100755 index 5e3a315..0000000 --- a/scripts/vyatta-comment-config.pl +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/perl - -# Author: Michael Larson <mike@vyatta.com> -# Date: 2010 -# Description: Perl script for adding comments to portions of the configuration - -# **** License **** -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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. -# -# This code was originally developed by Vyatta, Inc. -# Portions created by Vyatta are Copyright (C) 2006, 2007, 2008, 2009, 2010 Vyatta, Inc. -# All Rights Reserved. -# **** End License **** - -use strict; -use warnings; -use File::Find; -use lib "/opt/vyatta/share/perl5"; -use Vyatta::Config; - - -sub usage() { - print "Usage: $0 <path>\n"; - exit 0; -} - -if ($#ARGV == 0) { - exit 0; -} - -#adjust for leaf node -my $i = 0; -my @path = @ARGV[0..$#ARGV-1]; -foreach my $elem (@path) { - $elem =~ s/\//%2F/g; - $elem =~ s/\s+/\//g; - $path[$i++] = $elem; -} -my $edit_level = "$ENV{VYATTA_EDIT_LEVEL}"; - -my $path = $edit_level . join '/', @path; - -my $full_path = "$ENV{VYATTA_TEMP_CONFIG_DIR}/$path"; - -if (! -e $full_path) { - $path = $edit_level . join '/', @path[0..$#path-1]; - my $leaf = "$ENV{VYATTA_TEMP_CONFIG_DIR}/$path/node.val"; - if (-e $leaf) { - $full_path = "$ENV{VYATTA_TEMP_CONFIG_DIR}/$path"; - } - else { - print "Configuation path is not valid\n"; - exit 0; - } -} - -my $config = new Vyatta::Config; -my @el = split('/',$edit_level); -if ($config->isTagNode([ @el, @path ])) { - print "Cannot set comment without value for this path\n"; - exit 0; -} -#scan for illegal characters here: '/*', '*/' -if ($ARGV[$#ARGV] =~ /\/\*|\*\//) { - print "illegal characters found in comment\n"; - exit 1; -} - - -if ($ARGV[$#ARGV] eq '') { - `rm -f $full_path/.comment`; -} -else { - my $cfile; - if (!open($cfile, '>', "$full_path/.comment")) { - print "Failed to set comment\n"; - exit 1; - } - print $cfile $ARGV[$#ARGV]; - close($cfile); -} - -`touch $ENV{VYATTA_TEMP_CONFIG_DIR}/.modified`; - -exit 0; diff --git a/scripts/vyatta-load-config.pl b/scripts/vyatta-load-config.pl index a101e31..20cf200 100755 --- a/scripts/vyatta-load-config.pl +++ b/scripts/vyatta-load-config.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl -# Author: An-Cheng Huang <ancheng@vyatta.com. +# Author: Vyatta <eng@vyatta.com> # Date: 2007 # Description: Perl script for loading config file at run time. @@ -27,6 +27,7 @@ use POSIX; use IO::Prompt; use Getopt::Long; use Sys::Syslog qw(:standard :macros); +use Vyatta::Config; use Vyatta::ConfigLoad; $SIG{'INT'} = 'IGNORE'; @@ -188,7 +189,7 @@ if ( scalar( keys %cfg_hier ) == 0 ) { } } -my %cfg_diff = Vyatta::ConfigLoad::getConfigDiff( \%cfg_hier, 'true' ); +my %cfg_diff = Vyatta::ConfigLoad::getConfigDiff(\%cfg_hier); my @set_list = @{ $cfg_diff{'set'} }; my @deactivate_list = @{ $cfg_diff{'deactivate'} }; my @activate_list = @{ $cfg_diff{'activate'} }; @@ -224,25 +225,20 @@ foreach (@set_list) { foreach (@activate_list) { - my $cmd = "$sbindir/vyatta-activate-config.pl activate $_"; - system("$cmd 1>/dev/null"); - #ignore error on complaint re: nested nodes + my $cmd = "$sbindir/my_activate $_"; + system("$cmd 1>/dev/null"); + #ignore error on complaint re: nested nodes } +my $cobj = new Vyatta::Config; foreach (@deactivate_list) { - my @cp = split(" ",$_); - my $p = join("/",@cp[0..$#cp-1]); - my $leaf = "$ENV{VYATTA_TEMP_CONFIG_DIR}/$p/node.val"; - my $c = ""; - if (-e $leaf) { - $c = join(" ",@cp[0..$#cp-1]); - } - else { - $c = join(" ",@cp); - } - my $cmd = "$sbindir/vyatta-activate-config.pl deactivate $c"; - system("$cmd 1>/dev/null"); - #ignore error on complaint re: nested nodes + if ($cobj->isLeafValue($_)) { + # a leaf value. go up 1 level by removing the last comp. + s/\s+\S+$//; + } + my $cmd = "$sbindir/my_deactivate $_"; + system("$cmd 1>/dev/null"); + #ignore error on complaint re: nested nodes } foreach (@comment_list) { @@ -254,7 +250,7 @@ foreach (@comment_list) { my $rel_path = join '/', @cmd_array; my $path = "/opt/vyatta/config/active/" . $rel_path . "/.comment"; if (-e $path) { - my @cmd = ( "$sbindir/vyatta-comment-config.pl ", $cmd_ref ); + my @cmd = ( "$sbindir/my_comment ", $cmd_ref ); my $cmd_str = join ' ', @cmd; system("$cmd_str 1>/dev/null"); } @@ -266,7 +262,7 @@ foreach (@comment_list) { if (-e $leaf) { $path = "/opt/vyatta/config/active/" . $rel_path . "/.comment"; if (-e $path) { - my @cmd = ( "$sbindir/vyatta-comment-config.pl ", $cmd_ref ); + my @cmd = ( "$sbindir/my_comment ", $cmd_ref ); my $cmd_str = join ' ', @cmd; system("$cmd_str 1>/dev/null"); } @@ -274,7 +270,7 @@ foreach (@comment_list) { } } else { - my @cmd = ( "$sbindir/vyatta-comment-config.pl ", $cmd_ref ); + my @cmd = ( "$sbindir/my_comment ", $cmd_ref ); my $cmd_str = join ' ', @cmd; system("$cmd_str 1>/dev/null"); } diff --git a/src/cli_bin.cpp b/src/cli_bin.cpp new file mode 100644 index 0000000..420d19c --- /dev/null +++ b/src/cli_bin.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2010 Vyatta, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <cstdio> +#include <cstring> +#include <vector> +#include <string> +#include <libgen.h> + +extern "C" { +#include <cli_val.h> +} + +#include <cstore/unionfs/cstore-unionfs.hpp> + +static int op_idx = -1; +static const char *op_bin_name[] = { + "my_set", + "my_delete", + "my_activate", + "my_deactivate", + "my_rename", + "my_copy", + "my_comment", + "my_discard", + "my_move", + NULL +}; +static const char *op_Str[] = { + "Set", + "Delete", + "Activate", + "Deactivate", + "Rename", + "Copy", + "Comment", + "Discard", + "Move", + NULL +}; +static const char *op_str[] = { + "set", + "delete", + "activate", + "deactivate", + "rename", + "copy", + "comment", + "discard", + "move", + NULL +}; +static const bool op_need_cfg_node_args[] = { + true, + true, + true, + true, + true, + true, + true, + false, + true, + false // dummy +}; +#define OP_Str op_Str[op_idx] +#define OP_str op_str[op_idx] +#define OP_need_cfg_node_args op_need_cfg_node_args[op_idx] + +static void +doSet(Cstore& cstore, const vector<string>& path_comps) +{ + if (!cstore.validateSetPath(path_comps)) { + bye("invalid set path\n"); + } + if (!cstore.setCfgPath(path_comps)) { + bye("set cfg path failed\n"); + } +} + +static void +doDelete(Cstore& cstore, const vector<string>& path_comps) +{ + vtw_def def; + if (!cstore.validateDeletePath(path_comps, def)) { + bye("invalid delete path"); + } + if (!cstore.deleteCfgPath(path_comps, def)) { + bye("delete failed\n"); + } +} + +static void +doActivate(Cstore& cstore, const vector<string>& path_comps) +{ + if (!cstore.validateActivatePath(path_comps)) { + bye("%s validate failed", OP_str); + } + if (!cstore.unmarkCfgPathDeactivated(path_comps)) { + bye("%s failed", OP_str); + } +} + +static void +doDeactivate(Cstore& cstore, const vector<string>& path_comps) +{ + if (!cstore.validateDeactivatePath(path_comps)) { + bye("%s validate failed", OP_str); + } + if (!cstore.markCfgPathDeactivated(path_comps)) { + bye("%s failed", OP_str); + } +} + +static void +doRename(Cstore& cstore, const vector<string>& path_comps) +{ + if (!cstore.validateRenameArgs(path_comps)) { + bye("invalid rename args\n"); + } + if (!cstore.renameCfgPath(path_comps)) { + bye("rename cfg path failed\n"); + } +} + +static void +doCopy(Cstore& cstore, const vector<string>& path_comps) +{ + if (!cstore.validateCopyArgs(path_comps)) { + bye("invalid copy args\n"); + } + if (!cstore.copyCfgPath(path_comps)) { + bye("copy cfg path failed\n"); + } +} + +static void +doComment(Cstore& cstore, const vector<string>& path_comps) +{ + vtw_def def; + if (!cstore.validateCommentArgs(path_comps, def)) { + bye("invalid comment args\n"); + } + if (!cstore.commentCfgPath(path_comps, def)) { + bye("comment cfg path failed\n"); + } +} + +static void +doDiscard(Cstore& cstore, const vector<string>& args) +{ + if (args.size() > 0) { + OUTPUT_USER("Invalid discard command\n"); + bye("invalid discard command\n"); + } + if (!cstore.discardChanges()) { + bye("discard failed\n"); + } + // special case for discard: don't return (don't want to mark changed) + exit(0); +} + +static void +doMove(Cstore& cstore, const vector<string>& path_comps) +{ + if (!cstore.validateMoveArgs(path_comps)) { + bye("invalid move args\n"); + } + if (!cstore.moveCfgPath(path_comps)) { + bye("move cfg path failed\n"); + } +} + +typedef void (*OpFuncT)(Cstore& cstore, + const vector<string>& path_comps); +OpFuncT OpFunc[] = { + &doSet, + &doDelete, + &doActivate, + &doDeactivate, + &doRename, + &doCopy, + &doComment, + &doDiscard, + &doMove, + NULL +}; + +int +main(int argc, char **argv) +{ + int i = 0; + while (op_bin_name[i]) { + if (strcmp(basename(argv[0]), op_bin_name[i]) == 0) { + op_idx = i; + break; + } + ++i; + } + if (op_idx == -1) { + printf("Invalid operation\n"); + exit(-1); + } + + if (initialize_output(OP_Str) == -1) { + bye("can't initialize output\n"); + } + if (OP_need_cfg_node_args && argc < 2) { + fprintf(out_stream, "Need to specify the config node to %s\n", OP_str); + bye("nothing to %s\n", OP_str); + } + + // actual CLI operations use the edit levels from environment, so pass true. + UnionfsCstore cstore(true); + vector<string> path_comps; + for (int i = 1; i < argc; i++) { + path_comps.push_back(argv[i]); + } + + // call the op function + OpFunc[op_idx](cstore, path_comps); + cstore.markCfgPathChanged(path_comps); + exit(0); +} + diff --git a/src/cli_def.l b/src/cli_def.l index 86e5c98..806112f 100644 --- a/src/cli_def.l +++ b/src/cli_def.l @@ -25,16 +25,19 @@ static int reg_fields_t[] = { DEFAULT, TAG, TYPE, MULTI, PRIORITY, 0 }; static char *act_fields[] = { "help", "syntax", "commit", "delete", "update", "activate", "create", "begin", "end", + "enumeration", "comp_help", "allowed", "val_help", NULL }; static int act_fields_t[] = { HELP, SYNTAX, COMMIT, ACTION, ACTION, ACTION, ACTION, ACTION, ACTION, - DUMMY, DUMMY, DUMMY, + ENUMERATION, + CHELP, ALLOWED, VHELP, 0 }; static int act_types[] = { -1, -1, -1, delete_act, update_act, activate_act, create_act, begin_act, end_act, + -1, -1, -1, -1, -1 }; @@ -266,7 +269,7 @@ RE_OP_OTHER (pattern|exec|,|\|\||&&|=|!|\(|\)|;) /* template fields */ RE_REG_FIELD (default|tag|type|multi|priority) -RE_ACT_FIELD (help|syntax|commit|delete|update|activate|create|begin|end|comp_help|allowed|val_help) +RE_ACT_FIELD (help|syntax|commit|delete|update|activate|create|begin|end|enumeration|comp_help|allowed|val_help) %% diff --git a/src/cli_new.c b/src/cli_new.c index b0535d8..9cc9777 100644 --- a/src/cli_new.c +++ b/src/cli_new.c @@ -21,6 +21,8 @@ #include "cli_objects.h" #include "cli_val_engine.h" +#include "cstore/cstore-c.h" + /* Defines: */ #define EXE_STRING_DELTA 512 @@ -32,9 +34,12 @@ #define VAR_REF_MARKER "$VAR(" #define VAR_REF_MARKER_LEN 5 +#define VAR_REF_SELF_MARKER "$VAR(@)" +#define VAR_REF_SELF_MARKER_LEN 7 /* Global vars: */ vtw_path m_path, t_path; +void *var_ref_handle = NULL; /* Local vars: */ static vtw_node *vtw_free_nodes; /* linked via left */ @@ -114,8 +119,8 @@ void bye(const char *msg, ...) { va_list ap; - fprintf(out_stream, "%s failed\n", - (cli_operation_name) ? cli_operation_name : "Operation"); + OUTPUT_USER("%s failed\n", + (cli_operation_name) ? cli_operation_name : "Operation"); va_start(ap, msg); if (is_echo()) @@ -239,6 +244,74 @@ void append(vtw_list *l, vtw_node *n, int aux) l->vtw_list_tail = lnode; } +/* this recursive function walks the specified "vtw_node" tree representing + * "syntax" actions and looks for the first "in" action on a "self ref" + * specified as follows in template: + * $VAR(@) in "val1", "val2", ... + * + * if found, the corresponding valstruct is returned. this is used by the + * completion mechanism to get such "allowed" values specified by "syntax". + */ +const valstruct * +get_syntax_self_in_valstruct(vtw_node *vnode) +{ + const valstruct *ret = NULL; + if (!vnode) { + return NULL; + } + if (vnode->vtw_node_oper == COND_OP && vnode->vtw_node_aux == IN_COND + && vnode->vtw_node_left && vnode->vtw_node_right) { + vtw_node *ln = vnode->vtw_node_left; + vtw_node *rn = vnode->vtw_node_right; + if (ln->vtw_node_oper == VAR_OP && ln->vtw_node_string + && strncmp(VAR_REF_SELF_MARKER, ln->vtw_node_string, + VAR_REF_SELF_MARKER_LEN) == 0 + && rn->vtw_node_oper == VAL_OP) { + // found a matching syntax action. return valstruct. + return &(rn->vtw_node_val); + } + } + // this node does not match. walk down. + ret = get_syntax_self_in_valstruct(vnode->vtw_node_left); + if (!ret) { + ret = get_syntax_self_in_valstruct(vnode->vtw_node_right); + } + return ret; +} + +/* execute specified command and return output in specified buffer as + * a null-terminated string. return number of characters in the output + * or -1 if failed. + * + * NOTE: NO attempt is made to ensure the security of the specified command. + * in other words, *DO NOT* use a user-supplied string as the command + * for this function. + */ +int +get_shell_command_output(const char *cmd, char *buf, unsigned int buf_size) +{ + int ret = -1; + FILE *cmd_in = NULL; + size_t cnt = 0; + + if (!buf || !buf_size) { + return -1; + } + if (!(cmd_in = popen(cmd, "r"))) { + return -1; + } + cnt = fread(buf, 1, buf_size - 1, cmd_in); + if (cnt == (buf_size - 1) || feof(cmd_in)) { + /* buffer full or got the whole output. null terminate */ + buf[cnt] = 0; + ret = cnt; + } + if (pclose(cmd_in) == -1) { + ret = -1; + } + return ret; +} + void dt(vtw_sorted *srtp) { int i; @@ -729,7 +802,7 @@ int char2val(vtw_def *def, char *value, valstruct *valp) //currently fails to handle mixed text + non-text case... char buf1[2048]; if (char2val_notext(def,my_type,my_type2,value,&valp,buf1) != 0) { - fprintf(out_stream,"%s",buf1); + OUTPUT_USER("%s", buf1); return -1; //only single definition } return 0; @@ -1106,43 +1179,50 @@ static int change_var_value(const char* var_reference,const char* value, int act int ret=-1; if(var_reference && value) { - - char* var_path=NULL; + if (var_ref_handle) { + /* XXX current var ref lib implementation is fs-specific. + * for now treat it as a part of the unionfs-specific + * cstore implementation. + * handle is set => we are in cstore operation. + */ + if (cstore_set_var_ref(var_ref_handle, var_reference, value, + active_dir)) { + ret = 0; + } + } else { + /* legacy usage */ + char* var_path=NULL; + clind_path_ref n_cfg_path=NULL; + clind_path_ref n_tmpl_path=NULL; + clind_path_ref n_cmd_path=NULL; - clind_path_ref n_cfg_path=NULL; - clind_path_ref n_tmpl_path=NULL; - clind_path_ref n_cmd_path=NULL; - - if(set_reference_environment(var_reference, - &n_cfg_path, - &n_tmpl_path, - &n_cmd_path, - active_dir)==0) { - - clind_val cv; - - memset(&cv,0,sizeof(cv)); - - if(clind_config_engine_apply_command_path(n_cfg_path, - n_tmpl_path, - n_cmd_path, - FALSE, - &cv, - get_tdirp(), - TRUE)==0) { - var_path=cv.value; - + if(set_reference_environment(var_reference, + &n_cfg_path, + &n_tmpl_path, + &n_cmd_path, + active_dir)==0) { + clind_val cv; + memset(&cv,0,sizeof(cv)); + if(clind_config_engine_apply_command_path(n_cfg_path, + n_tmpl_path, + n_cmd_path, + FALSE, + &cv, + get_tdirp(), + TRUE, + active_dir)==0) { + var_path=cv.value; + } + } + + if(n_cfg_path) clind_path_destruct(&n_cfg_path); + if(n_tmpl_path) clind_path_destruct(&n_tmpl_path); + if(n_cmd_path) clind_path_destruct(&n_cmd_path); + + if(var_path) { + ret=write_value_to_file(var_path,value); + free(var_path); } - - } - - if(n_cfg_path) clind_path_destruct(&n_cfg_path); - if(n_tmpl_path) clind_path_destruct(&n_tmpl_path); - if(n_cmd_path) clind_path_destruct(&n_cmd_path); - - if(var_path) { - ret=write_value_to_file(var_path,value); - free(var_path); } } @@ -1177,7 +1257,7 @@ static boolean check_syn_func(vtw_node *cur,const char **outbuf,const char* func if (ret <= 0){ if (expand_string(cur->vtw_node_right->vtw_node_string) == VTWERR_OK) { if (outbuf == NULL) { - fprintf(out_stream, "%s\n", exe_string); + OUTPUT_USER("%s\n", exe_string); } else { strcat((char*)*outbuf, exe_string); @@ -1362,9 +1442,6 @@ static int eval_va(valstruct *res, vtw_node *node) { char *endp = 0; - clind_path_ref n_cfg_path=NULL; - clind_path_ref n_tmpl_path=NULL; - clind_path_ref n_cmd_path=NULL; pathp = node->vtw_node_string; DPRINT("eval_va var[%s]\n", pathp); @@ -1388,36 +1465,63 @@ static int eval_va(valstruct *res, vtw_node *node) *endp = 0; - if(set_reference_environment(pathp, - &n_cfg_path, - &n_tmpl_path, - &n_cmd_path, - is_in_delete_action())==0) { - clind_val cv; - - memset(&cv,0,sizeof(cv)); - - status=clind_config_engine_apply_command_path(n_cfg_path, - n_tmpl_path, - n_cmd_path, - TRUE, - &cv, - get_tdirp(), - FALSE); - - if(status==0) { - if(cv.value) { - res->val_type = cv.val_type; - res->val_types = NULL; - res->free_me = TRUE; - res->val = cv.value; - } - } - } + if (var_ref_handle) { + /* XXX current var ref lib implementation is fs-specific. + * for now treat it as a part of the unionfs-specific + * cstore implementation. + * handle is set => we are in cstore operation. + */ + clind_val cv; + if (!cstore_get_var_ref(var_ref_handle, pathp, &cv, + is_in_delete_action())) { + status = -1; + } else { + /* success */ + status = 0; + if(cv.value) { + res->val_type = cv.val_type; + res->val_types = NULL; + res->free_me = TRUE; + res->val = cv.value; + } + } + } else { + /* legacy usage */ + clind_path_ref n_cfg_path=NULL; + clind_path_ref n_tmpl_path=NULL; + clind_path_ref n_cmd_path=NULL; + if(set_reference_environment(pathp, + &n_cfg_path, + &n_tmpl_path, + &n_cmd_path, + is_in_delete_action())==0) { + clind_val cv; + + memset(&cv,0,sizeof(cv)); + + status=clind_config_engine_apply_command_path(n_cfg_path, + n_tmpl_path, + n_cmd_path, + TRUE, + &cv, + get_tdirp(), + FALSE, + is_in_delete_action()); + + if(status==0) { + if(cv.value) { + res->val_type = cv.val_type; + res->val_types = NULL; + res->free_me = TRUE; + res->val = cv.value; + } + } + } - if(n_cfg_path) clind_path_destruct(&n_cfg_path); - if(n_tmpl_path) clind_path_destruct(&n_tmpl_path); - if(n_cmd_path) clind_path_destruct(&n_cmd_path); + if(n_cfg_path) clind_path_destruct(&n_cfg_path); + if(n_tmpl_path) clind_path_destruct(&n_tmpl_path); + if(n_cmd_path) clind_path_destruct(&n_cmd_path); + } *endp = ')'; @@ -1525,11 +1629,6 @@ static int expand_string(char *stringp) scanp += 4; } else { - - clind_path_ref n_cfg_path=NULL; - clind_path_ref n_tmpl_path=NULL; - clind_path_ref n_cmd_path=NULL; - char *endp; endp = strchr(scanp, ')'); @@ -1543,33 +1642,49 @@ static int expand_string(char *stringp) if (endp == scanp) bye("Empty path"); - if(set_reference_environment(scanp, - &n_cfg_path, - &n_tmpl_path, - &n_cmd_path, - is_in_delete_action())==0) { + if (var_ref_handle) { + /* XXX current var ref lib implementation is fs-specific. + * for now treat it as a part of the unionfs-specific + * cstore implementation. + * handle is set => we are in cstore operation. + */ + clind_val cv; + if (cstore_get_var_ref(var_ref_handle, scanp, &cv, + is_in_delete_action())) { + cp=cv.value; + } + } else { + /* legacy usage */ + clind_path_ref n_cfg_path=NULL; + clind_path_ref n_tmpl_path=NULL; + clind_path_ref n_cmd_path=NULL; + + if(set_reference_environment(scanp, + &n_cfg_path, + &n_tmpl_path, + &n_cmd_path, + is_in_delete_action())==0) { + + clind_val cv; + memset(&cv,0,sizeof(cv)); + if(clind_config_engine_apply_command_path(n_cfg_path, + n_tmpl_path, + n_cmd_path, + TRUE, + &cv, + get_tdirp(), + FALSE, + is_in_delete_action())==0) { + cp=cv.value; + } + + } + + if(n_cfg_path) clind_path_destruct(&n_cfg_path); + if(n_tmpl_path) clind_path_destruct(&n_tmpl_path); + if(n_cmd_path) clind_path_destruct(&n_cmd_path); + } - clind_val cv; - - memset(&cv,0,sizeof(cv)); - - if(clind_config_engine_apply_command_path(n_cfg_path, - n_tmpl_path, - n_cmd_path, - TRUE, - &cv, - get_tdirp(), - FALSE)==0) { - cp=cv.value; - - } - - } - - if(n_cfg_path) clind_path_destruct(&n_cfg_path); - if(n_tmpl_path) clind_path_destruct(&n_tmpl_path); - if(n_cmd_path) clind_path_destruct(&n_cmd_path); - if(!cp) { cp=""; } else { @@ -2047,13 +2162,12 @@ boolean validate_value(vtw_def *def, char *cp) int i = 0; for (i = 0; i < strlen(cp); i++) { if (cp[i] == '\'') { - fprintf(out_stream, "Cannot use the \"'\" (single quote) character " - "in a value string\n"); + OUTPUT_USER("Cannot use the \"'\" (single quote) character " + "in a value string\n"); return FALSE; } if (cp[i] == '\n') { - fprintf(out_stream, "Cannot use the newline character " - "in a value string\n"); + OUTPUT_USER("Cannot use the newline character in a value string\n"); return FALSE; } } @@ -2062,17 +2176,18 @@ boolean validate_value(vtw_def *def, char *cp) /* prepare cur_value */ set_at_string(cp); status = char2val(def, cp, &validate_value_val); - if (status != VTWERR_OK) + if (status != VTWERR_OK) { return FALSE; + } if ((def->def_type!=ERROR_TYPE) && ((validate_value_val.val_type != def->def_type) && (validate_value_val.val_type != def->def_type2))) { if (def->def_type_help){ (void)expand_string(def->def_type_help); - fprintf(out_stream, "%s\n", exe_string); + OUTPUT_USER("%s\n", exe_string); } else { - fprintf(out_stream, "\"%s\" is not a valid value of type \"%s\"\n", - cp, type_to_name(def->def_type)); + OUTPUT_USER("\"%s\" is not a valid value of type \"%s\"\n", cp, + type_to_name(def->def_type)); } ret = FALSE; goto validate_value_free_and_return; @@ -2241,7 +2356,7 @@ const char *type_to_name(vtw_type_e type) { case IPV6NET_TYPE: return("ipv6net"); case MACADDR_TYPE: return("macaddr"); case DOMAIN_TYPE: return("domain"); - case TEXT_TYPE: return("text"); + case TEXT_TYPE: return("txt"); case BOOL_TYPE: return("bool"); default: return("unknown"); } @@ -2396,18 +2511,20 @@ restore_output() /* system_out: * call system() with output re-enabled. * output is again redirected before returning from here. + * note: this function may be used outside of actual CLI operations, so output + * may not have been redirected. check out_stream for such cases. */ int old_system_out(const char *command) { int ret = -1; - if (restore_output() == -1) { + if (out_stream && restore_output() == -1) { return -1; } ret = system(command); - if (redirect_output() == -1) { + if (out_stream && redirect_output() == -1) { return -1; } diff --git a/src/cli_parse.y b/src/cli_parse.y index 318394b..47ba0e5 100644 --- a/src/cli_parse.y +++ b/src/cli_parse.y @@ -35,6 +35,10 @@ static void cli_deferror(const char *); %token HELP %token DEFAULT %token PRIORITY +%token ENUMERATION +%token CHELP +%token ALLOWED +%token VHELP %token PATTERN %token EXEC %token SYNTAX @@ -150,6 +154,10 @@ type: TYPE TYPE_DEF cause: help_cause | default_cause | priority_stmt + | enumeration_stmt + | chelp_stmt + | allowed_stmt + | vhelp_stmt | syntax_cause | ACTION action { append(parse_defp->actions + $1, $2, 0);} | dummy_stmt @@ -191,6 +199,45 @@ priority_stmt: PRIORITY VALUE } } +enumeration_stmt: ENUMERATION STRING + { + parse_defp->def_enumeration = $2; + } + +chelp_stmt: CHELP STRING + { + parse_defp->def_comp_help = $2; + } + +allowed_stmt: ALLOWED STRING + { + parse_defp->def_allowed = $2; + } + +vhelp_stmt: VHELP STRING + { + if (!(parse_defp->def_val_help)) { + /* first string */ + parse_defp->def_val_help = $2; + } else { + /* subsequent strings */ + char *optr = parse_defp->def_val_help; + int olen = strlen(parse_defp->def_val_help); + char *nptr = $2; + int nlen = strlen(nptr); + int len = olen + 1 /* "\n" */ + nlen + 1 /* 0 */; + char *mptr = (char *) malloc(len); + memcpy(mptr, optr, olen); + mptr[olen] = '\n'; + memcpy(&(mptr[olen + 1]), nptr, nlen); + mptr[len - 1] = 0; + parse_defp->def_val_help = mptr; + free(optr); + free(nptr); + } + /* result is a '\n'-delimited string for val_help */ + } + syntax_cause: SYNTAX exp {append(parse_defp->actions + syntax_act, $2, 0);} ; diff --git a/src/cli_shell_api.cpp b/src/cli_shell_api.cpp new file mode 100644 index 0000000..0962c80 --- /dev/null +++ b/src/cli_shell_api.cpp @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2010 Vyatta, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <cstdio> +#include <cstring> +#include <cerrno> +#include <vector> +#include <string> +#include <libgen.h> +#include <sys/mount.h> + +extern "C" { +#include <cli_val.h> +} + +#include <cstore/unionfs/cstore-unionfs.hpp> + +static int op_idx = -1; +static const char *op_name[] = { + "getSessionEnv", + "getEditEnv", + "getEditUpEnv", + "getEditResetEnv", + "editLevelAtRoot", + "getCompletionEnv", + "getEditLevelStr", + "markSessionUnsaved", + "unmarkSessionUnsaved", + "sessionUnsaved", + "sessionChanged", + "teardownSession", + "setupSession", + "inSession", + NULL +}; +static const int op_exact_args[] = { + 1, + -1, + 0, + 0, + 0, + -1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -1 +}; +static const char *op_exact_error[] = { + "Must specify session ID", + NULL, + "No argument expected", + "No argument expected", + "No argument expected", + NULL, + "No argument expected", + "No argument expected", + "No argument expected", + "No argument expected", + "No argument expected", + "No argument expected", + "No argument expected", + "No argument expected", + NULL +}; +static const int op_min_args[] = { + -1, + 1, + -1, + -1, + -1, + 2, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1 +}; +static const char *op_min_error[] = { + NULL, + "Must specify config path to edit", + NULL, + NULL, + NULL, + "Must specify command and at least one component", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; +#define OP_exact_args op_exact_args[op_idx] +#define OP_min_args op_min_args[op_idx] +#define OP_exact_error op_exact_error[op_idx] +#define OP_min_error op_min_error[op_idx] + +static void +doGetSessionEnv(const vector<string>& args) +{ + string env; + UnionfsCstore cstore(args[0], env); + printf("%s", env.c_str()); +} + +static void +doGetEditEnv(const vector<string>& args) +{ + UnionfsCstore cstore(true); + string env; + if (!cstore.getEditEnv(args, env)) { + exit(-1); + } + printf("%s", env.c_str()); +} + +static void +doGetEditUpEnv(const vector<string>& args) +{ + UnionfsCstore cstore(true); + string env; + if (!cstore.getEditUpEnv(env)) { + exit(-1); + } + printf("%s", env.c_str()); +} + +static void +doGetEditResetEnv(const vector<string>& args) +{ + UnionfsCstore cstore(true); + string env; + if (!cstore.getEditResetEnv(env)) { + exit(-1); + } + printf("%s", env.c_str()); +} + +static void +doEditLevelAtRoot(const vector<string>& args) +{ + UnionfsCstore cstore(true); + exit(cstore.editLevelAtRoot() ? 0 : 1); +} + +static void +doGetCompletionEnv(const vector<string>& args) +{ + UnionfsCstore cstore(true); + string env; + if (!cstore.getCompletionEnv(args, env)) { + exit(-1); + } + printf("%s", env.c_str()); +} + +static void +doGetEditLevelStr(const vector<string>& args) +{ + UnionfsCstore cstore(true); + vector<string> lvec; + cstore.getEditLevel(lvec); + string level; + for (unsigned int i = 0; i < lvec.size(); i++) { + if (level.length() > 0) { + level += " "; + } + level += lvec[i]; + } + printf("%s", level.c_str()); +} + +static void +doMarkSessionUnsaved(const vector<string>& args) +{ + UnionfsCstore cstore(true); + if (!cstore.markSessionUnsaved()) { + exit(-1); + } +} + +static void +doUnmarkSessionUnsaved(const vector<string>& args) +{ + UnionfsCstore cstore(true); + if (!cstore.unmarkSessionUnsaved()) { + exit(-1); + } +} + +static void +doSessionUnsaved(const vector<string>& args) +{ + UnionfsCstore cstore(true); + if (!cstore.sessionUnsaved()) { + exit(-1); + } +} + +static void +doSessionChanged(const vector<string>& args) +{ + UnionfsCstore cstore(true); + if (!cstore.sessionChanged()) { + exit(-1); + } +} + +static void +doTeardownSession(const vector<string>& args) +{ + UnionfsCstore cstore(true); + if (!cstore.teardownSession()) { + exit(-1); + } +} + +static void +doSetupSession(const vector<string>& args) +{ + UnionfsCstore cstore(true); + if (!cstore.setupSession()) { + exit(-1); + } +} + +static void +doInSession(const vector<string>& args) +{ + UnionfsCstore cstore(true); + if (!cstore.inSession()) { + exit(-1); + } +} + +typedef void (*OpFuncT)(const vector<string>& args); +OpFuncT OpFunc[] = { + &doGetSessionEnv, + &doGetEditEnv, + &doGetEditUpEnv, + &doGetEditResetEnv, + &doEditLevelAtRoot, + &doGetCompletionEnv, + &doGetEditLevelStr, + &doMarkSessionUnsaved, + &doUnmarkSessionUnsaved, + &doSessionUnsaved, + &doSessionChanged, + &doTeardownSession, + &doSetupSession, + &doInSession, + NULL +}; + +int +main(int argc, char **argv) +{ + int i = 0; + if (argc < 2) { + printf("Must specify operation\n"); + exit(-1); + } + while (op_name[i]) { + if (strcmp(argv[1], op_name[i]) == 0) { + op_idx = i; + break; + } + ++i; + } + if (op_idx == -1) { + printf("Invalid operation\n"); + exit(-1); + } + if (OP_exact_args >= 0 && (argc - 2) != OP_exact_args) { + printf("%s\n", OP_exact_error); + exit(-1); + } + if (OP_min_args >= 0 && (argc - 2) < OP_min_args) { + printf("%s\n", OP_min_error); + exit(-1); + } + + vector<string> args; + for (int i = 2; i < argc; i++) { + args.push_back(argv[i]); + } + + // call the op function + OpFunc[op_idx](args); + exit(0); +} + diff --git a/src/cli_val.h b/src/cli_val.h index 6918fcf..41e4461 100644 --- a/src/cli_val.h +++ b/src/cli_val.h @@ -111,11 +111,17 @@ typedef struct { char *def_default; unsigned int def_priority; char *def_priority_ext; + char *def_enumeration; + char *def_comp_help; + char *def_allowed; + char *def_val_help; unsigned int def_tag; unsigned int def_multi; boolean tag; boolean multi; vtw_list actions[top_act]; + int is_value; /* this is used by the config store to indicate whether + * the last path component is a "value". */ }vtw_def; typedef struct { @@ -156,6 +162,9 @@ extern vtw_node *make_str_node(char *str); extern vtw_node *make_var_node(char *str); extern vtw_node *make_str_node0(char *str, vtw_oper_e op); extern void append(vtw_list *l, vtw_node *n, int aux); +const valstruct *get_syntax_self_in_valstruct(vtw_node *vnode); +int get_shell_command_output(const char *cmd, char *buf, + unsigned int buf_size); extern int parse_def(vtw_def *defp, const char *path, boolean type_only); extern int yy_cli_val_lex(void); @@ -169,6 +178,7 @@ extern void free_def(vtw_def *defp); extern void free_sorted(vtw_sorted *sortp); extern vtw_path m_path, t_path; +extern void *var_ref_handle; /************************************************* GLOBAL FUNCTIONS @@ -231,6 +241,15 @@ extern FILE *out_stream; extern FILE *err_stream; extern int initialize_output(const char *op); +/* note that some functions may be used outside the actual CLI operations, + * so output may not have been initialized. nop in such cases. + */ +#define OUTPUT_USER(fmt, args...) do \ + { \ + if (out_stream) { \ + fprintf(out_stream, fmt , ##args); \ + } \ + } while (0); /* debug hooks? */ #define my_malloc(size, name) malloc(size) diff --git a/src/cli_val_engine.c b/src/cli_val_engine.c index 00e891b..b481b0d 100644 --- a/src/cli_val_engine.c +++ b/src/cli_val_engine.c @@ -54,13 +54,15 @@ #include <string.h> +#include <cstore/cstore-c.h> + +#include "cli_objects.h" #include "cli_val_engine.h" static int is_multi_node(clind_path_ref tmpl_path); -static boolean -is_deactivated(const char *path); +static boolean is_deactivated(const char *path, int in_active); /********************* * Data definitions @@ -120,6 +122,8 @@ static int clind_path_shift_cmd(clind_path_ref path,clind_cmd *cmd); * If path is empty, or the file is empty, or the file does not exist, * then return NULL. * The user of this function is responsible for the memory deallocation. + * + * in_active is for the deactivated check. */ static char** clind_get_current_value(clind_path_ref cfg_path, @@ -129,7 +133,7 @@ static char** clind_get_current_value(clind_path_ref cfg_path, const char* root_tmpl_path, int return_value_file_name, int multi_value, - int *ret_size) { + int *ret_size, int in_active) { char** ret=NULL; int value_ref = 0; @@ -181,7 +185,8 @@ static char** clind_get_current_value(clind_path_ref cfg_path, } else { - if (is_deactivated(clind_path_get_path_string(cfg_path)) == FALSE) { + if (is_deactivated(clind_path_get_path_string(cfg_path), in_active) + == FALSE) { FILE* f = fopen(cfg_path_string,"r"); if(f) { char buffer[8193]; @@ -233,7 +238,8 @@ static char** clind_get_current_value(clind_path_ref cfg_path, /* Directory reference: */ - if(!check_existence || ((lstat(cfg_path_string, &statbuf) == 0) && is_deactivated(cfg_path_string) == FALSE)) { + if(!check_existence || ((lstat(cfg_path_string, &statbuf) == 0) + && is_deactivated(cfg_path_string, in_active) == FALSE)) { ret=(char**)realloc(ret,sizeof(char*)*1); ret[0]=clind_unescape(cfg_end); *ret_size=1; @@ -278,7 +284,7 @@ static char** clind_get_current_value(clind_path_ref cfg_path, strcpy(fn_node_def+strlen(fn_node_def),NODE_DEF); if ((lstat(fn_node_def, &statbuf) == 0)&& - (is_deactivated(fn_node_def) == FALSE)&& + (is_deactivated(fn_node_def, in_active) == FALSE)&& (parse_def(&def, fn_node_def, TRUE)==0)) { if(def.def_type != ERROR_TYPE) { @@ -614,9 +620,15 @@ static clind_path_ref* clind_config_engine_apply_command(clind_path_ref cfg_path * handled before this is called (see set_reference_environment() in * cli_new.c). cli_new.c was passing the wrong path (changes only) * anyway, causing problems with absolute paths (bug 5213). + * * root_tmpl_path should not be necessary either but it's * used in one place in clind_get_current_value() (it's not clear * if that case is ever reached), so keep it for now. + * + * in_active is needed for the deactivated check. if operating on + * active, deactivated check should be in active as well. this + * makes a difference for nodes that are being deactivated (i.e., + * deactivated in working but not in active). */ int clind_config_engine_apply_command_path(clind_path_ref cfg_path_orig, @@ -625,7 +637,8 @@ int clind_config_engine_apply_command_path(clind_path_ref cfg_path_orig, int check_existence, clind_val* res, const char* root_tmpl_path, - int return_value_file_name) { + int return_value_file_name, + int in_active) { int ret=-1; @@ -758,7 +771,7 @@ int clind_config_engine_apply_command_path(clind_path_ref cfg_path_orig, return_value_file_name, /*Last command: */ (cmd.type==CLIND_CMD_MULTI_VALUE), - &vallen); + &vallen, in_active); clind_path_destruct(&config_paths[i]); @@ -925,57 +938,38 @@ static int clind_path_shift_cmd(clind_path_ref path,clind_cmd *cmd) { boolean -is_deactivated(const char *path_string) +is_deactivated(const char *path_string, int in_active) { - if (path_string == NULL) { - return FALSE; - } - - // const char* path_string = clind_path_get_path_string(*path); - - char buf[1024]; //ALSO USED AS LIMIT IN UNIONFS path length - strcpy(buf,path_string); - - //first we'll check the current directory - char file[1024]; - sprintf(file,"%s/.disable",buf); - struct stat s; - if (lstat(file,&s) == 0) { - return TRUE; - } - - long min_len = strlen("/opt/vyatta/config/tmp/new_config_"); - - //now walk back up tree looking for disable flag..... - while (TRUE) { - int index = strlen(buf)-1; - if (index < min_len) { - return FALSE; - } - if (buf[index] == '/') { - while (TRUE) { - if (buf[--index] != '/') { - buf[index] = '\0'; - break; - } - } - } - - char *ptr = rindex(buf,'/'); - if (ptr != NULL) { - *ptr = '\0'; - } - - char file[1024]; - sprintf(file,"%s/.disable",buf); - - // fprintf(out_stream,"checking file for disable: %s\n",file); - - struct stat s; - if (lstat(file,&s) == 0) { - return TRUE; - } + int num = 0; + boolean ret = FALSE; + char **path_comps = cstore_path_string_to_path_comps(path_string, &num); + void *csh = cstore_init(); + + /* XXX this lib should operate on "logical paths" only, but currently + * it is using physical paths. convert to logical paths (remove the + * prefix) to use the cstore library. + * + * XXX also, use is_in_delete_action() as in_active arg for the call. + * this follows the original behavior that when in delete action, var + * refs are obtained from the active config. the result of the original + * behavior is that if a node is being deleted (i.e., in active but not + * in working), a var ref for the node will still return the active value. + * + * by passing is_in_delete_action() here, the result is that if a node is + * being deactivated (i.e., deactivated in working but not in active), + * a var ref for the node will still return the active value. + * + * the original behavior may not be correct, but for compatibility + * it's emulated here. should revisit later. + */ + if (num > 5 + && cstore_cfg_path_deactivated(csh, + (const char **) &(path_comps[5]), + num - 5, in_active)) { + ret = TRUE; } - - return FALSE; + cstore_free_path_comps(path_comps, num); + cstore_free(csh); + return ret; } + diff --git a/src/cli_val_engine.h b/src/cli_val_engine.h index a0c1fe3..4f35a7f 100644 --- a/src/cli_val_engine.h +++ b/src/cli_val_engine.h @@ -35,8 +35,8 @@ #if !defined(__CLI_VAL_ENGINE__) #define __CLI_VAL_ENGINE__ -#include "cli_path_utils.h" -#include "cli_val.h" +#include <cli_path_utils.h> +#include <cli_val.h> /******************* * Type definitions @@ -81,7 +81,8 @@ int clind_config_engine_apply_command_path(clind_path_ref cfg_path, int check_existence, clind_val *res, const char* root_tmpl_path, - int return_value_file_name); + int return_value_file_name, + int in_active); diff --git a/src/cstore/cstore-c.cpp b/src/cstore/cstore-c.cpp new file mode 100644 index 0000000..3215707 --- /dev/null +++ b/src/cstore/cstore-c.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2010 Vyatta, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <cstring> +#include <vector> +#include <string> + +#include "cstore-c.h" +#include "cstore/unionfs/cstore-unionfs.hpp" + +void * +cstore_init(void) +{ + Cstore *handle = new UnionfsCstore(); + return (void *) handle; +} + +void +cstore_free(void *handle) +{ + UnionfsCstore *h = (UnionfsCstore *) handle; + delete h; +} + +int +cstore_validate_tmpl_path(void *handle, const char *path_comps[], + int num_comps, int validate_tags) +{ + if (handle) { + vector<string> vs; + for (int i = 0; i < num_comps; i++) { + vs.push_back(path_comps[i]); + } + Cstore *cs = (Cstore *) handle; + return (cs->validateTmplPath(vs, validate_tags) ? 1 : 0); + } + return 0; +} + +int +cstore_validate_tmpl_path_d(void *handle, const char *path_comps[], + int num_comps, int validate_tags, vtw_def *def) +{ + if (handle) { + vector<string> vs; + for (int i = 0; i < num_comps; i++) { + vs.push_back(path_comps[i]); + } + Cstore *cs = (Cstore *) handle; + return (cs->validateTmplPath(vs, validate_tags, *def) ? 1 : 0); + } + return 0; +} + +int +cstore_cfg_path_exists(void *handle, const char *path_comps[], int num_comps) +{ + if (handle) { + vector<string> vs; + for (int i = 0; i < num_comps; i++) { + vs.push_back(path_comps[i]); + } + Cstore *cs = (Cstore *) handle; + return (cs->cfgPathExists(vs) ? 1 : 0); + } + return 0; +} + +int +cstore_get_var_ref(void *handle, const char *ref_str, clind_val *cval, + int from_active) +{ + if (handle) { + Cstore *cs = (Cstore *) handle; + return (cs->getVarRef(ref_str, *cval, from_active) ? 1 : 0); + } + return 0; +} + +int +cstore_set_var_ref(void *handle, const char *ref_str, const char *value, + int to_active) +{ + if (handle) { + Cstore *cs = (Cstore *) handle; + return (cs->setVarRef(ref_str, value, to_active) ? 1 : 0); + } + return 0; +} + +int +cstore_cfg_path_deactivated(void *handle, const char *path_comps[], + int num_comps, int in_active) +{ + if (handle) { + vector<string> vs; + for (int i = 0; i < num_comps; i++) { + vs.push_back(path_comps[i]); + } + Cstore *cs = (Cstore *) handle; + return (cs->cfgPathDeactivated(vs, in_active) ? 1 : 0); + } + return 0; +} + +char ** +cstore_path_string_to_path_comps(const char *path_str, int *num_comps) +{ + char *pstr = strdup(path_str); + size_t len = strlen(pstr); + vector<string> vec; + char *start = NULL; + for (unsigned int i = 0; i < len; i++) { + if (pstr[i] == '/') { + if (start) { + pstr[i] = 0; + vec.push_back(start); + pstr[i] = '/'; + start = NULL; + } + continue; + } else if (!start) { + start = &(pstr[i]); + } + } + if (start) { + vec.push_back(start); + } + char **ret = (char **) malloc(sizeof(char *) * vec.size()); + for (unsigned int i = 0; i < vec.size(); i++) { + ret[i] = strdup(vec[i].c_str()); + } + *num_comps = vec.size(); + free(pstr); + return ret; +} + +void +cstore_free_path_comps(char **path_comps, int num_comps) +{ + for (int i = 0; i < num_comps; i++) { + free(path_comps[i]); + } + free(path_comps); +} + diff --git a/src/cstore/cstore-c.h b/src/cstore/cstore-c.h new file mode 100644 index 0000000..e664f95 --- /dev/null +++ b/src/cstore/cstore-c.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2010 Vyatta, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _CSTORE_C_H_ +#define _CSTORE_C_H_ +#ifdef __cplusplus +extern "C" { +#endif + +#include <cli_val.h> +#include <cli_val_engine.h> + +void *cstore_init(void); +void cstore_free(void *handle); +int cstore_validate_tmpl_path(void *handle, const char *path_comps[], + int num_comps, int validate_tags); +int cstore_validate_tmpl_path_d(void *handle, const char *path_comps[], + int num_comps, int validate_tags, + vtw_def *def); +int cstore_cfg_path_exists(void *handle, const char *path_comps[], + int num_comps); +int cstore_cfg_path_deactivated(void *handle, const char *path_comps[], + int num_comps, int in_active); + +/* the following are internal APIs for the library. they can only be used + * during cstore operations since they operate on "current" paths constructed + * by the operations. + */ +int cstore_get_var_ref(void *handle, const char *ref_str, clind_val *cval, + int from_active); +int cstore_set_var_ref(void *handle, const char *ref_str, const char *value, + int to_active); + +/* util functions */ +char **cstore_path_string_to_path_comps(const char *path_str, int *num_comps); +void cstore_free_path_comps(char **path_comps, int num_comps); + +#ifdef __cplusplus +} +#endif +#endif /* _CSTORE_C_H_ */ + diff --git a/src/cstore/cstore-varref.cpp b/src/cstore/cstore-varref.cpp new file mode 100644 index 0000000..6d71307 --- /dev/null +++ b/src/cstore/cstore-varref.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2010 Vyatta, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <cstdio> +#include <vector> +#include <string> +#include <algorithm> + +#include "cstore-varref.hpp" + +extern "C" { +#include "cli_val.h" +#include "cli_objects.h" +} + +using namespace std; + +////// constructors/destructors +Cstore::VarRef::VarRef(Cstore *cstore, const string& ref_str, bool active) + : _cstore(cstore), _active(active) +{ + /* NOTE: this class will change the paths in the cstore. caller must do + * save/restore for the cstore if necessary. + */ + if (!_cstore) { + // no cstore + return; + } + + _absolute = (ref_str[0] == '/'); + while (!_absolute && !_cstore->cfg_path_at_root()) { + _orig_path_comps.insert(_orig_path_comps.begin(), + _cstore->pop_cfg_path()); + } + _cstore->reset_paths(); + /* at this point, cstore paths are at root. _orig_path_comps contains + * the path originally in cstore (or empty if _absolute). + */ + + size_t si = (_absolute ? 1 : 0); + size_t sn = 0; + vector<string> rcomps; + while (si < ref_str.length() + && (sn = ref_str.find('/', si)) != ref_str.npos) { + rcomps.push_back(ref_str.substr(si, sn - si)); + si = sn + 1; + } + if (si < ref_str.length()) { + rcomps.push_back(ref_str.substr(si)); + } + // NOTE: if path ends in '/', the trailing slash is ignored. + + // get the "at" string. this is set inside cli_new.c. + _at_string = get_at_string(); + + // process ref + vector<string> pcomps = _orig_path_comps; + process_ref(rcomps, pcomps, ERROR_TYPE); +} + +/* process the reference(s). + * this is a recursive function and always keeps the cstore paths unchanged + * between invocations. + * + * note: def_type is added into _paths along with the paths. when it's + * ERROR_TYPE, it means the path needs to be checked for existence. + * otherwise, the path is a "value" (or "values") read from the + * actual config (working or active). + */ +void +Cstore::VarRef::process_ref(const vector<string>& ref_comps, + const vector<string>& cur_path_comps, + vtw_type_e def_type) +{ + if (ref_comps.size() == 0) { + // done + _paths.push_back(pair<vector<string>, + vtw_type_e>(cur_path_comps, def_type)); + return; + } + + vector<string> rcomps = ref_comps; + vector<string> pcomps = cur_path_comps; + string cr_comp= rcomps.front(); + rcomps.erase(rcomps.begin()); + + vtw_def def; + bool got_tmpl = _cstore->get_parsed_tmpl(pcomps, false, def); + bool handle_leaf = false; + if (cr_comp == "@") { + if (!got_tmpl) { + // invalid path + return; + } + if (def.def_type == ERROR_TYPE) { + // no value for typeless node + return; + } + if (pcomps.size() == _orig_path_comps.size()) { + if (pcomps.size() == 0 + || equal(pcomps.begin(), pcomps.end(), _orig_path_comps.begin())) { + /* we are at the original path. this is a self-reference, e.g., + * $VAR(@), so use the "at string". + */ + pcomps.push_back(_at_string); + process_ref(rcomps, pcomps, def.def_type); + return; + } + } + if (pcomps.size() < _orig_path_comps.size()) { + // within the original path. @ translates to the path comp. + pcomps.push_back(_orig_path_comps[pcomps.size()]); + process_ref(rcomps, pcomps, def.def_type); + return; + } + if (def.is_value || def.tag) { + // invalid ref + return; + } + // handle leaf node + handle_leaf = true; + } else if (cr_comp == ".") { + process_ref(rcomps, pcomps, ERROR_TYPE); + } else if (cr_comp == "..") { + if (!got_tmpl || pcomps.size() == 0) { + // invalid path + return; + } + pcomps.pop_back(); + if (!_cstore->get_parsed_tmpl(pcomps, false, def)) { + // invalid tmpl path + return; + } + if (def.is_value && def.tag) { + // at "tag value", need to pop one more. + if (pcomps.size() == 0) { + // invalid path + return; + } + pcomps.pop_back(); + } + process_ref(rcomps, pcomps, ERROR_TYPE); + } else if (cr_comp == "@@") { + if (!got_tmpl) { + // invalid path + return; + } + if (def.def_type == ERROR_TYPE) { + // no value for typeless node + return; + } + if (def.is_value) { + // invalid ref + return; + } + if (def.tag) { + // tag node + vector<string> cnodes; + _cstore->cfgPathGetChildNodes(pcomps, cnodes, _active); + for (unsigned int i = 0; i < cnodes.size(); i++) { + pcomps.push_back(cnodes[i]); + process_ref(rcomps, pcomps, def.def_type); + pcomps.pop_back(); + } + } else { + // handle leaf node + handle_leaf = true; + } + } else { + // just text. go down 1 level. + if (got_tmpl && def.tag && !def.is_value) { + // at "tag node". need to go down 1 more level. + if (pcomps.size() >= _orig_path_comps.size()) { + // already under the original node. invalid ref. + return; + } + // within the original path. take the original tag value. + pcomps.push_back(_orig_path_comps[pcomps.size()]); + } + pcomps.push_back(cr_comp); + process_ref(rcomps, pcomps, ERROR_TYPE); + } + + if (handle_leaf) { + if (def.multi) { + // multi-value node + vector<string> vals; + if (!_cstore->cfgPathGetValues(pcomps, vals, _active)) { + return; + } + string val; + for (unsigned int i = 0; i < vals.size(); i++) { + if (val.length() > 0) { + val += " "; + } + val += vals[i]; + } + pcomps.push_back(val); + // treat "joined" multi-values as TEXT_TYPE + _paths.push_back(pair<vector<string>, vtw_type_e>(pcomps, TEXT_TYPE)); + // at leaf. stop recursion. + } else { + // single-value node + string val; + if (!_cstore->cfgPathGetValue(pcomps, val, _active)) { + return; + } + pcomps.push_back(val); + _paths.push_back(pair<vector<string>, vtw_type_e>(pcomps, def.def_type)); + // at leaf. stop recursion. + } + } +} + +bool +Cstore::VarRef::getValue(string& value, vtw_type_e& def_type) +{ + vector<string> result; + map<string, bool> added; + def_type = ERROR_TYPE; + for (unsigned int i = 0; i < _paths.size(); i++) { + if (_paths[i].first.size() == 0) { + // empty path + continue; + } + if (added.find(_paths[i].first.back()) != added.end()) { + // already added + continue; + } + if (_paths[i].second == ERROR_TYPE + && !_cstore->cfgPathExists(_paths[i].first, _active)) { + // path doesn't exist + continue; + } + if (_paths[i].second != ERROR_TYPE) { + // set def_type. all types should be the same if multiple entries exist. + def_type = _paths[i].second; + } + added[_paths[i].first.back()] = true; + result.push_back(_paths[i].first.back()); + } + if (result.size() == 0) { + // got nothing + return false; + } + if (result.size() > 1 || def_type == ERROR_TYPE) { + /* if no type is available or we are returning "joined" multiple values, + * treat it as text type. + */ + def_type = TEXT_TYPE; + } + value = ""; + for (unsigned int i = 0; i < result.size(); i++) { + if (i > 0) { + value += " "; + } + value += result[i]; + } + return true; +} + +bool +Cstore::VarRef::getSetPath(vector<string>& path_comps) +{ + /* XXX this function is currently unused and untested. see setVarRef() + * in Cstore for more information. + */ + if (_paths.size() != 1) { + // for set_var_ref operation, there can be only one path. + return false; + } + path_comps = _paths[0].first; + return true; +} + diff --git a/src/cstore/cstore-varref.hpp b/src/cstore/cstore-varref.hpp new file mode 100644 index 0000000..1fc1d52 --- /dev/null +++ b/src/cstore/cstore-varref.hpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2010 Vyatta, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _CSTORE_VARREF_H_ +#define _CSTORE_VARREF_H_ +#include <vector> +#include <string> +#include <map> + +#include "cstore.hpp" + +using namespace std; + +class Cstore::VarRef { +public: + VarRef(Cstore *cstore, const string& ref_str, bool active); + ~VarRef() {}; + + bool getValue(string& value, vtw_type_e& def_type); + bool getSetPath(vector<string>& path_comps); + +private: + Cstore *_cstore; + bool _active; + bool _absolute; + string _at_string; + vector<string> _orig_path_comps; + vector<pair<vector<string>, vtw_type_e> > _paths; + + void process_ref(const vector<string>& ref_comps, + const vector<string>& cur_path_comps, vtw_type_e def_type); +}; + +#endif /* _CSTORE_VARREF_H_ */ + diff --git a/src/cstore/cstore.cpp b/src/cstore/cstore.cpp new file mode 100644 index 0000000..31c896a --- /dev/null +++ b/src/cstore/cstore.cpp @@ -0,0 +1,2496 @@ +/* + * Copyright (C) 2010 Vyatta, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <cstdarg> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <map> +#include <algorithm> +#include <sstream> + +extern "C" { +#include "cli_val.h" +} + +#include "cstore.hpp" +#include "cstore-varref.hpp" + + +////// constants +//// node status +const string Cstore::C_NODE_STATUS_DELETED = "deleted"; +const string Cstore::C_NODE_STATUS_ADDED = "added"; +const string Cstore::C_NODE_STATUS_CHANGED = "changed"; +const string Cstore::C_NODE_STATUS_STATIC = "static"; + +//// env vars for shell +// current levels +const string Cstore::C_ENV_EDIT_LEVEL = "VYATTA_EDIT_LEVEL"; +const string Cstore::C_ENV_TMPL_LEVEL = "VYATTA_TEMPLATE_LEVEL"; + +// shell-specific vars +const string Cstore::C_ENV_SHELL_PROMPT = "PS1"; +const string Cstore::C_ENV_SHELL_CWORDS = "COMP_WORDS"; +const string Cstore::C_ENV_SHELL_CWORD_COUNT = "COMP_CWORD"; + +// shell api vars +const string Cstore::C_ENV_SHAPI_COMP_VALS = "_cli_shell_api_comp_values"; +const string Cstore::C_ENV_SHAPI_LCOMP_VAL = "_cli_shell_api_last_comp_val"; +const string Cstore::C_ENV_SHAPI_COMP_HELP = "_cli_shell_api_comp_help"; +const string Cstore::C_ENV_SHAPI_HELP_ITEMS = "_cli_shell_api_hitems"; +const string Cstore::C_ENV_SHAPI_HELP_STRS = "_cli_shell_api_hstrs"; + +//// dirs +const string Cstore::C_ENUM_SCRIPT_DIR = "/opt/vyatta/share/enumeration"; + +////// constructors/destructors +/* this constructor just returns the generic environment string, + * currently the two levels. implementation-specific environment + * (e.g., unionfs stuff) is handled by derived class. + * + * note: currently using original semantics for the levels, i.e., they + * represent the actual physical paths, which involve fs-specific + * escaping. this should be changed to a "logical" representation + * so that their manipulation can be moved from derived class to + * this base class. + */ +Cstore::Cstore(string& env) +{ + string decl = "declare -x "; + env = (decl + C_ENV_EDIT_LEVEL + "=/; "); + env += (decl + C_ENV_TMPL_LEVEL + "=/;"); +} + + +////// public interface +/* check if specified "logical path" corresponds to a valid template. + * validate_vals: whether to validate "values" along specified path. + * return true if valid. otherwise return false. + */ +bool +Cstore::validateTmplPath(const vector<string>& path_comps, bool validate_vals) +{ + vtw_def def; + // if we can get parsed tmpl, path is valid + return get_parsed_tmpl(path_comps, validate_vals, def); +} + +/* same as above but return parsed template. + * def: (output) parsed template. + * note: if last path component is "value" (i.e., def.is_value), parsed + * template is actually at "full path - 1". see get_parsed_tmpl() for details. + */ +bool +Cstore::validateTmplPath(const vector<string>& path_comps, bool validate_vals, + vtw_def& def) +{ + // if we can get parsed tmpl, path is valid + return get_parsed_tmpl(path_comps, validate_vals, def); +} + +/* get parsed template of specified path as a string-string map + * tmap: (output) parsed template. + * return true if successful. otherwise return false. + */ +bool +Cstore::getParsedTmpl(const vector<string>& path_comps, + map<string, string>& tmap, bool allow_val) +{ + vtw_def def; + /* currently this function is used outside actual CLI operations, mainly + * from the perl API. since value validation is from the original CLI + * implementation, it doesn't seem to behave correctly in such cases, + * probably because "at string" is not set? + * + * anyway, not validating values in the following call. + */ + if (!get_parsed_tmpl(path_comps, false, def)) { + return false; + } + if (!allow_val && def.is_value) { + /* note: !allow_val means specified path must terminate at an actual + * "node", not a "value". so this fails since path ends in value. + * this emulates the original perl API behavior. + */ + return false; + } + if (def.is_value) { + tmap["is_value"] = "1"; + } + // make the map + if (def.def_type != ERROR_TYPE) { + tmap["type"] = type_to_name(def.def_type); + } + if (def.def_type2 != ERROR_TYPE) { + tmap["type2"] = type_to_name(def.def_type2); + } + if (def.def_node_help) { + tmap["help"] = def.def_node_help; + } + if (def.multi) { + tmap["multi"] = "1"; + if (def.def_multi > 0) { + ostringstream s; + s << def.def_multi; + tmap["limit"] = s.str(); + } + } else if (def.tag) { + tmap["tag"] = "1"; + if (def.def_tag > 0) { + ostringstream s; + s << def.def_tag; + tmap["limit"] = s.str(); + } + } else if (def.def_default) { + tmap["default"] = def.def_default; + } + if (def.def_enumeration) { + tmap["enum"] = def.def_enumeration; + } + if (def.def_allowed) { + tmap["allowed"] = def.def_allowed; + } + if (def.def_val_help) { + tmap["val_help"] = def.def_val_help; + } + return true; +} + +/* get names of all template child nodes of specified path. + * cnodes: (output) template child node names. + * note: if specified path is at a "tag node", "node.tag" will be returned. + */ +void +Cstore::tmplGetChildNodes(const vector<string>& path_comps, + vector<string>& cnodes) +{ + SAVE_PATHS; + append_tmpl_path(path_comps); + get_all_tmpl_child_node_names(cnodes); + RESTORE_PATHS; +} + +/* delete specified "logical path" from "working config". + * def: parsed template corresponding to logical path path_comps. + * return true if successful. otherwise return false. + * note: assume specified path has been validated + * (i.e., validateDeletePath()). + */ +bool +Cstore::deleteCfgPath(const vector<string>& path_comps, const vtw_def& def) +{ + if (!cfg_path_exists(path_comps, false, true)) { + output_user("Nothing to delete (the specified %s does not exist)\n", + (!def.is_value || def.tag) ? "node" : "value"); + // treat as success + return true; + } + + /* path already validated and in working config. + * cases: + * 1. has default value + * => replace current value with default + * 2. no default value + * => remove config path + */ + if (def.def_default) { + // case 1. construct path for value file. + SAVE_PATHS; + append_cfg_path(path_comps); + if (def.is_value) { + // last comp is "value". need to go up 1 level. + pop_cfg_path(); + } + + /* assume default value is valid (parser should have validated). + * also call unmark_deactivated() in case the node being deleted was + * also deactivated. note that unmark_deactivated() succeeds if it's + * not marked deactivated. + */ + bool ret = (write_value(def.def_default) && mark_display_default() + && unmark_deactivated()); + if (!ret) { + output_user("Failed to set default value during delete\n"); + } + RESTORE_PATHS; + return ret; + } + + /* case 2. + * sub-cases: + * (1) last path comp is "value", i.e., tag (value of tag node), + * value of single-value node, or value of multi-value node. + * (a) value of single-value node + * => remove node + * (b) value of multi-value node + * => remove value. remove node if last value. + * (c) value of tag node (i.e., tag) + * => remove tag. remove node if last tag. + * (2) last path comp is "node", i.e., typeless node, tag node, + * single-value node, or multi-value node. + * => remove node + */ + bool ret = false; + SAVE_PATHS; + append_cfg_path(path_comps); + if (!def.is_value) { + // sub-case (2) + ret = remove_node(); + } else { + // last comp is value + if (def.tag) { + // sub-case (1c) + ret = remove_tag(); + } else if (def.multi) { + // sub-case (1b) + pop_cfg_path(); + ret = remove_value_from_multi(path_comps[path_comps.size() - 1]); + } else { + // sub-case (1a). delete node at 1 level up. + pop_cfg_path(); + ret = remove_node(); + } + } + RESTORE_PATHS; + if (!ret) { + output_user("Failed to delete specified config path\n"); + } + return ret; +} + +/* check if specified "logical path" is valid for "set" operation + * return true if valid. otherwise return false. + */ +bool +Cstore::validateSetPath(const vector<string>& path_comps) +{ + // if we can get parsed tmpl, path is valid + vtw_def def; + string terr; + if (!get_parsed_tmpl(path_comps, true, def, terr)) { + output_user("%s\n", terr.c_str()); + return false; + } + + bool ret = true; + SAVE_PATHS; + if (!def.is_value) { + if (def.def_type != ERROR_TYPE) { + /* disallow setting value node without value + * note: different from old behavior, which only disallow setting a + * single-value node without value. now all value nodes + * (single-value, multi-value, and tag) must be set with value. + */ + output_user("The specified configuration node requires a value\n"); + ret = false; + } else { + /* typeless node + * note: XXX the following is present in the original logic, perhaps + * to trigger check_syn() on the typeless node? is this really + * necessary? + * also, validate_val() uses current cfg path and tmpl path, so + * construct them before calling it. + */ + append_cfg_path(path_comps); + append_tmpl_path(path_comps); + if (!validate_val(&def, "")) { + ret = false; + } + } + } + RESTORE_PATHS; + return ret; +} + +/* check if specified "logical path" is valid for "delete" operation + * return true if valid. otherwise return false. + */ +bool +Cstore::validateDeletePath(const vector<string>& path_comps, vtw_def& def) +{ + string terr; + if (!get_parsed_tmpl(path_comps, false, def, terr)) { + output_user("%s\n", terr.c_str()); + return false; + } + return true; +} + +/* check if specified "logical path" is valid for "activate" operation + * return true if valid. otherwise return false. + */ +bool +Cstore::validateActivatePath(const vector<string>& path_comps) +{ + vtw_def def; + if (!validate_act_deact(path_comps, "activate", def)) { + return false; + } + if (!cfgPathMarkedDeactivated(path_comps)) { + output_user("Activate can only be performed on a node on which the " + "deactivate\ncommand has been performed.\n"); + return false; + } + bool ret = true; + if (def.is_value && def.tag && def.def_tag > 0) { + // we are activating a tag, and there is a limit on number of tags. + vector<string> cnodes; + SAVE_PATHS; + append_cfg_path(path_comps); + string t = pop_cfg_path(); + // get child nodes, excluding deactivated ones. + get_all_child_node_names(cnodes, false, false); + if (def.def_tag <= cnodes.size()) { + // limit exceeded + output_user("Cannot activate \"%s\": number of values exceeds limit " + "(%d allowed)\n", t.c_str(), def.def_tag); + ret = false; + } + RESTORE_PATHS; + } + return ret; +} + +/* check if specified "logical path" is valid for "deactivate" operation + * return true if valid. otherwise return false. + */ +bool +Cstore::validateDeactivatePath(const vector<string>& path_comps) +{ + vtw_def def; + return validate_act_deact(path_comps, "deactivate", def); +} + +/* check if specified "logical path" is valid for "edit" operation. + * return false if invalid. + * if valid, set "env" arg to the environment string needed for the "edit" + * operation and return true. + */ +bool +Cstore::getEditEnv(const vector<string>& path_comps, string& env) +{ + vtw_def def; + string terr; + if (!get_parsed_tmpl(path_comps, false, def, terr)) { + output_user("%s\n", terr.c_str()); + return false; + } + if (!cfg_path_exists(path_comps, false, true)) { + output_user("The specified config path does not exist\n"); + return false; + } + /* "edit" is only allowed when path ends at a + * (1) "tag value" + * OR + * (2) "typeless node" + */ + if (!(def.is_value && def.tag) + && !(!def.is_value && def.def_type == ERROR_TYPE)) { + // neither "tag value" nor "typeless node" + output_user("The \"edit\" command cannot be issued " + "at the specified level\n"); + return false; + } + SAVE_PATHS; + append_cfg_path(path_comps); + append_tmpl_path(path_comps); + get_edit_env(env); + RESTORE_PATHS; + /* doing the save/restore above to be consistent with the rest of the API. + * however, after the caller evals the returned environment string, the + * levels in "this" will become out-of-sync with the environment. so + * "this" should no longer be used and a new object should be created. + * + * this is only an issue if the calling process doesn't terminate. since + * the function should only be used by the shell/completion, it's not a + * problem (each invocation of my_cli_shell_api uses a new object anyway). + */ + + return true; +} + +/* set "env" arg to the environment string needed for the "up" operation. + * return true if successful. + */ +bool +Cstore::getEditUpEnv(string& env) +{ + /* "up" is based on current levels in environment. levels should already + * be set up in constructor (with "use_edit_level" true). + */ + if (edit_level_at_root()) { + output_user("Already at the top level\n"); + return false; + } + + /* get_parsed_tmpl() does not allow empty path, so use one component + * from current paths. + */ + vtw_def def; + string terr; + vector<string> path_comps; + if (!get_parsed_tmpl(path_comps, false, def, terr)) { + // this should not happen since it's using existing levels + output_user("%s\n", terr.c_str()); + return false; + } + SAVE_PATHS; + if (def.is_value && def.tag) { + // edit level is at "tag value". go up 1 extra level. + pop_cfg_path(); + pop_tmpl_path(); + } + pop_cfg_path(); + pop_tmpl_path(); + get_edit_env(env); + RESTORE_PATHS; + // also see getEditEnv for comment on save/restore above + + return true; +} + +/* set "env" arg to the environment string needed to reset edit levels. + * return true if successful. + */ +bool +Cstore::getEditResetEnv(string& env) +{ + SAVE_PATHS; + while (!edit_level_at_root()) { + pop_cfg_path(); + pop_tmpl_path(); + } + get_edit_env(env); + RESTORE_PATHS; + // also see getEditEnv for comment on save/restore above + + return true; +} + +/* set "env" arg to the environment string needed for "completion". + * return true if successful. + * + * note: comps must have at least 2 components, the "command" and the + * first path element (which can be empty string). + */ +bool +Cstore::getCompletionEnv(const vector<string>& comps, string& env) +{ + vector<string> pcomps = comps; + string cmd = pcomps[0]; + string last_comp = pcomps.back(); + pcomps.erase(pcomps.begin()); + pcomps.pop_back(); + bool exists_only = (cmd == "delete" || cmd == "show" || cmd == "edit" + || cmd == "comment" || cmd == "activate" + || cmd == "deactivate"); + + /* at this point, pcomps contains the command line arguments minus the + * "command" and the last one. + */ + bool ret = false; + SAVE_PATHS; + do { + vtw_def def; + if (pcomps.size() > 0) { + if (!get_parsed_tmpl(pcomps, false, def)) { + // invalid path + break; + } + if (exists_only && !cfg_path_exists(pcomps, false, true)) { + // invalid path for the command (must exist) + break; + } + append_cfg_path(pcomps); + append_tmpl_path(pcomps); + } else { + // we are at root. simulate a typeless node. + def.def_type = ERROR_TYPE; + def.tag = def.multi = def.is_value = 0; + } + + /* at this point, cfg and tmpl paths are constructed up to the comp + * before last_comp, and def is parsed. + */ + if (def.is_value && !def.tag) { + // invalid path (this means the comp before last_comp is a leaf value) + break; + } + + vector<string> comp_vals; + string comp_string; + string comp_help; + vector<pair<string, string> > help_pairs; + bool last_comp_val = true; + if (def.def_type == ERROR_TYPE || def.is_value) { + /* path so far is at a typeless node OR a tag value (tag already + * checked above): + * completions: from tmpl children. + * help: + * values: same as completions. + * text: "help" from child templates. + * + * note: for such completions, we filter non-existent nodes if + * necessary. + */ + vector<string> ufvec; + if (exists_only) { + // only return existing config nodes + get_all_child_node_names(ufvec, false, true); + } else { + // return all template children + get_all_tmpl_child_node_names(ufvec); + } + for (unsigned int i = 0; i < ufvec.size(); i++) { + if (last_comp == "" + || ufvec[i].compare(0, last_comp.length(), last_comp) == 0) { + comp_vals.push_back(ufvec[i]); + } + } + if (comp_vals.size() == 0) { + // no matches + break; + } + sort(comp_vals.begin(), comp_vals.end()); + for (unsigned int i = 0; i < comp_vals.size(); i++) { + pair<string, string> hpair(comp_vals[i], ""); + push_tmpl_path(hpair.first); + vtw_def cdef; + if (tmpl_parse(cdef)) { + hpair.second = cdef.def_node_help; + } else { + hpair.second = "<No help text available>"; + } + help_pairs.push_back(hpair); + pop_tmpl_path(); + } + // last comp is not value + last_comp_val = false; + } else { + /* path so far is at a "value node". + * note: follow the original implementation and don't filter + * non-existent values for such completions + */ + // first, handle completions. + if (def.tag) { + // it's a "tag node". get completions from tag values. + get_all_child_node_names(comp_vals, false, true); + } else { + // it's a "leaf value node". get completions from values. + read_value_vec(comp_vals, false); + } + /* more possible completions from this node's template: + * "allowed" + * "enumeration" + * "$VAR(@) in ..." + */ + if (def.def_enumeration || def.def_allowed) { + /* do "enumeration" or "allowed". + * note: emulate original implementation and set up COMP_WORDS and + * COMP_CWORD environment variables. these are needed by some + * "allowed" scripts. + */ + ostringstream cword_count; + cword_count << (comps.size() - 1); + string cmd_str = ("export " + C_ENV_SHELL_CWORD_COUNT + "=" + + cword_count.str() + "; "); + cmd_str += ("export " + C_ENV_SHELL_CWORDS + "=("); + for (unsigned int i = 0; i < comps.size(); i++) { + cmd_str += (" '" + comps[i] + "'"); + } + cmd_str += "); "; + if (def.def_enumeration) { + cmd_str += (C_ENUM_SCRIPT_DIR + "/" + def.def_enumeration); + } else { + cmd_str += def.def_allowed; + } + + char *buf = (char *) malloc(MAX_CMD_OUTPUT_SIZE); + int ret = get_shell_command_output(cmd_str.c_str(), buf, + MAX_CMD_OUTPUT_SIZE); + if (ret > 0) { + // '<' and '>' need to be escaped + char *ptr = buf; + while (*ptr) { + if (*ptr == '<' || *ptr == '>') { + comp_string += "\\"; + } + comp_string += *ptr; + ptr++; + } + } + /* note that for "enumeration" and "allowed", comp_string is the + * complete output of the command and it is to be evaled by the + * shell into an array of values. + */ + free(buf); + } else if (def.actions[syntax_act].vtw_list_head) { + // look for "self ref in values" from syntax + const valstruct *vals = get_syntax_self_in_valstruct( + def.actions[syntax_act].vtw_list_head); + if (vals) { + if (vals->cnt == 0 && vals->val) { + comp_vals.push_back(vals->val); + } else if (vals->cnt > 0) { + for (int i = 0; i < vals->cnt; i++) { + if (vals->vals[i]) { + comp_vals.push_back(vals->vals[i]); + } + } + } + } + } + + // now handle help. + if (def.def_comp_help) { + // "comp_help" exists. + comp_help = def.def_comp_help; + shell_escape_squotes(comp_help); + } + if (def.def_val_help) { + // has val_help. first separate individual lines. + unsigned int start = 0, i = 0; + vector<string> vhelps; + for (i = 0; def.def_val_help[i]; i++) { + if (def.def_val_help[i] == '\n') { + vhelps.push_back(string(&(def.def_val_help[start]), i - start)); + start = i + 1; + } + } + if (start < i) { + vhelps.push_back(string(&(def.def_val_help[start]), i - start)); + } + + // process each line + for (i = 0; i < vhelps.size(); i++) { + size_t sc; + if ((sc = vhelps[i].find(';')) == vhelps[i].npos) { + // no ';' + if (i == 0 && def.def_type != ERROR_TYPE) { + // first val_help. pair with "type". + help_pairs.push_back(pair<string, string>( + type_to_name(def.def_type), vhelps[i])); + } + if (i == 1 && def.def_type2 != ERROR_TYPE) { + // second val_help. pair with "type2". + help_pairs.push_back(pair<string, string>( + type_to_name(def.def_type2), vhelps[i])); + } + } else { + // ';' at index sc + help_pairs.push_back(pair<string, string>( + vhelps[i].substr(0, sc), + vhelps[i].substr(sc + 1))); + } + } + } else if (def.def_type && def.def_node_help) { + // simple case. just use "type" and "help" + help_pairs.push_back(pair<string, string>(type_to_name(def.def_type), + def.def_node_help)); + } + } + + // this var is the array of possible completions + env = (C_ENV_SHAPI_COMP_VALS + "=("); + for (unsigned int i = 0; i < comp_vals.size(); i++) { + shell_escape_squotes(comp_vals[i]); + env += ("'" + comp_vals[i] + "' "); + } + /* as mentioned above, comp_string is the complete command output. + * let the shell eval it into the array since we don't want to + * re-implement the shell interpretation here. + * + * note that as a result, we will not be doing the filtering here. + * instead, the completion script will do the filtering on + * the resulting comp_values array. should be straightforward since + * there's no "existence filtering", only "prefix filtering". + */ + env += (comp_string + "); "); + + /* this var indicates whether the last comp is "value" + * follow original implementation: if last comp is value, completion + * script needs to do the following. + * use comp_help if exists + * prefix filter comp_values + * replace any <*> in comp_values with "" + * convert help items to data representation + */ + env += (C_ENV_SHAPI_LCOMP_VAL + "="); + env += (last_comp_val ? "true; " : "false; "); + + // this var is the "comp_help" string + env += (C_ENV_SHAPI_COMP_HELP + "='" + comp_help + "'; "); + + // this var is the array of "help items", i.e., type names, etc. + string hitems = (C_ENV_SHAPI_HELP_ITEMS + "=("); + // this var is the array of "help strings" corresponding to the items + string hstrs = (C_ENV_SHAPI_HELP_STRS + "=("); + for (unsigned int i = 0; i < help_pairs.size(); i++) { + string hi = help_pairs[i].first; + string hs = help_pairs[i].second; + shell_escape_squotes(hi); + shell_escape_squotes(hs); + // get rid of leading/trailing "space" chars in help string + while (hi.size() > 0 && isspace(hi[0])) { + hi.erase(0, 1); + } + while (hs.size() > 0 && isspace(hs[0])) { + hs.erase(0, 1); + } + while (hi.size() > 0 && isspace(hi[hi.size() - 1])) { + hi.erase(hi.size() - 1); + } + while (hs.size() > 0 && isspace(hs[hs.size() - 1])) { + hs.erase(hs.size() - 1); + } + hitems += ("'" + hi + "' "); + hstrs += ("'" + hs + "' "); + } + env += (hitems + "); " + hstrs + "); "); + ret = true; + } while(0); + RESTORE_PATHS; + return ret; +} + +/* set specified "logical path" in "working config". + * return true if successful. otherwise return false. + * note: assume specified path is valid (i.e., validateSetPath()). + */ +bool +Cstore::setCfgPath(const vector<string>& path_comps) +{ + vector<string> ppath; + vtw_def def; + bool ret = true; + bool path_exists = true; + // do the set from the top down + SAVE_PATHS; + for (unsigned int i = 0; i < path_comps.size(); i++) { + // partial path + ppath.push_back(path_comps[i]); + + // get template at this level + if (!get_parsed_tmpl(ppath, false, def)) { + output_user("paths[%s,%s]\n", cfg_path_to_str().c_str(), + tmpl_path_to_str().c_str()); + for (unsigned int i = 0; i < ppath.size(); i++) { + output_user(" [%s]\n", ppath[i].c_str()); + } + exit_internal("failed to get tmpl during set. not validate first?\n"); + } + + // nop if this level already in working (including deactivated) + if (cfg_path_exists(ppath, false, true)) { + continue; + } + path_exists = false; + + // this level not in working. set it. + append_cfg_path(ppath); + append_tmpl_path(ppath); + + if (!def.is_value) { + // this level is a "node" + if (!add_node() || !create_default_children()) { + ret = false; + break; + } + } else if (def.tag) { + // this level is a "tag value". + // add the tag, taking the max tag limit into consideration. + if (!add_tag(def) || !create_default_children()) { + ret = false; + break; + } + } else { + // this level is a "value" of a single-/multi-value node. + // go up 1 level to get the node. + pop_cfg_path(); + if (def.multi) { + // value of multi-value node. + // add the value, taking the max multi limit into consideration. + if (!add_value_to_multi(def, ppath.back())) { + ret = false; + break; + } + } else { + // value of single-value node + if (!write_value(ppath.back())) { + ret = false; + break; + } + } + } + RESTORE_PATHS; + } + RESTORE_PATHS; // if "break" was hit + + if (ret && def.is_value && def.def_default) { + /* a node with default has been explicitly set. needs to be marked + * as non-default for display purposes. + * + * note: when the new value is the same as the old value, the behavior + * is different from before, which was a nop. the new behavior is + * that the value will remain unchanged, but the "default status" + * will be changed, i.e., it will be marked as non-default. + */ + append_cfg_path(path_comps); + pop_cfg_path(); + // only do it if it's previously marked default + if (marked_display_default()) { + ret = unmark_display_default(); + + /* XXX work around current commit's unionfs implementation problem. + * current commit's unionfs implementation looks at the "changes only" + * directory (i.e., the r/w portion of the union mount), which is wrong. + * + * all config information should be obtained from two directories: + * "active" and "working", e.g., instead of looking at whiteout files + * in "changes only" to find deleted nodes, nodes that are in "active" + * but not in "working" have been deleted. + * + * in this particular case, commit looks at "changes only" to read the + * node.val file. however, since the value didn't change (only the + * "default status" changed), node.val doesn't appear in "changes only". + * here we re-write the file to force it into "changes only" so that + * commit can work correctly. + */ + vector<string> vvec; + read_value_vec(vvec, false); + write_value_vec(vvec); + + // pretend it didn't exist since we changed the status + path_exists = false; + } + RESTORE_PATHS; + } + if (path_exists) { + // whole path already exists + output_user("The specified configuration node already exists\n"); + // treat as success + } + return ret; +} + +/* check if specified "arguments" is valid for "rename" operation + * return true if valid. otherwise return false. + */ +bool +Cstore::validateRenameArgs(const vector<string>& args) +{ + return validate_rename_copy(args, "rename"); +} + +/* check if specified "arguments" is valid for "copy" operation + * return true if valid. otherwise return false. + */ +bool +Cstore::validateCopyArgs(const vector<string>& args) +{ + return validate_rename_copy(args, "copy"); +} + +/* check if specified "arguments" is valid for "move" operation + * return true if valid. otherwise return false. + */ +bool +Cstore::validateMoveArgs(const vector<string>& args) +{ + vector<string> epath; + vector<string> nargs; + if (!conv_move_args_for_rename(args, epath, nargs)) { + output_user("Invalid move command\n"); + return false; + } + SAVE_PATHS; + append_cfg_path(epath); + append_tmpl_path(epath); + bool ret = validate_rename_copy(nargs, "move"); + RESTORE_PATHS; + return ret; +} + +/* check if specified "arguments" is valid for "comment" operation + * return true if valid. otherwise return false. + */ +bool +Cstore::validateCommentArgs(const vector<string>& args, vtw_def& def) +{ + /* separate path from comment. + * follow the original implementation: the last arg is the comment, and + * everything else is part of the path. + */ + vector<string> path_comps(args); + string comment = args.back(); + path_comps.pop_back(); + + // check the path + string terr; + if (!get_parsed_tmpl(path_comps, false, def, terr)) { + output_user("%s\n", terr.c_str()); + return false; + } + // here we want to include deactivated nodes + if (!cfg_path_exists(path_comps, false, true)) { + output_user("The specified config node does not exist\n"); + return false; + } + if (def.is_value && !def.tag) { + /* XXX differ from the original implementation, which allows commenting + * on a "value" BUT silently "promote" the comment to the parent + * "node". this will probably create confusion for the user. + * + * just disallow such cases here. + */ + output_user("Cannot comment on config values\n"); + return false; + } + if (def.tag && !def.is_value) { + /* XXX follow original implementation and disallow comment on a + * "tag node". this is because "show" does not display such + * comments (see bug 5794). + */ + output_user("Cannot add comment at this level\n"); + return false; + } + if (comment.find_first_of('*') != string::npos) { + // don't allow '*'. this is due to config files using C-style /**/ + // comments. this probably belongs to lower-level, but we are enforcing + // it here. + output_user("Cannot use the '*' character in a comment\n"); + return false; + } + return true; +} + +/* perform rename in "working config" according to specified args. + * return true if successful. otherwise return false. + * note: assume args are already validated (i.e., validateRenameArgs()). + */ +bool +Cstore::renameCfgPath(const vector<string>& args) +{ + string otagnode = args[0]; + string otagval = args[1]; + string ntagval = args[4]; + push_cfg_path(otagnode); + bool ret = rename_child_node(otagval, ntagval); + pop_cfg_path(); + return ret; +} + +/* perform copy in "working config" according to specified args. + * return true if successful. otherwise return false. + * note: assume args are already validated (i.e., validateCopyArgs()). + */ +bool +Cstore::copyCfgPath(const vector<string>& args) +{ + string otagnode = args[0]; + string otagval = args[1]; + string ntagval = args[4]; + push_cfg_path(otagnode); + bool ret = copy_child_node(otagval, ntagval); + pop_cfg_path(); + return ret; +} + +/* perform "comment" in working config according to specified args. + * return true if valid. otherwise return false. + */ +bool +Cstore::commentCfgPath(const vector<string>& args, const vtw_def& def) +{ + // separate path from comment + vector<string> path_comps(args); + string comment = args.back(); + path_comps.pop_back(); + + SAVE_PATHS; + append_cfg_path(path_comps); + bool ret; + if (comment == "") { + // follow original impl: empty comment => remove it + ret = remove_comment(); + if (!ret) { + output_user("Failed to remove comment for specified config node\n"); + } + } else { + ret = set_comment(comment); + if (!ret) { + output_user("Failed to add comment for specified config node\n"); + } + } + RESTORE_PATHS; + return ret; +} + +/* discard all changes in working config. + * return true if successful. otherwise return false. + */ +bool +Cstore::discardChanges() +{ + // just call underlying implementation + unsigned long long num_removed = 0; + if (discard_changes(num_removed)) { + if (num_removed > 0) { + output_user("Changes have been discarded\n"); + } else { + output_user("No changes have been discarded\n"); + } + return true; + } + return false; +} + +/* perform move in "working config" according to specified args. + * return true if successful. otherwise return false. + * note: assume args are already validated (i.e., validateMoveArgs()). + */ +bool +Cstore::moveCfgPath(const vector<string>& args) +{ + vector<string> epath; + vector<string> nargs; + if (!conv_move_args_for_rename(args, epath, nargs)) { + output_user("Invalid move command\n"); + return false; + } + SAVE_PATHS; + append_cfg_path(epath); + append_tmpl_path(epath); + bool ret = renameCfgPath(nargs); + RESTORE_PATHS; + return ret; +} + +/* check if specified "logical path" exists in working config (i.e., the union) + * or active config (i.e., the original). + * return true if it exists. otherwise return false. + */ +bool +Cstore::cfgPathExists(const vector<string>& path_comps, bool active_cfg) +{ + return cfg_path_exists(path_comps, active_cfg, false); +} + +// same as above but "deactivate-aware" +bool +Cstore::cfgPathExistsDA(const vector<string>& path_comps, bool active_cfg, + bool include_deactivated) +{ + return cfg_path_exists(path_comps, active_cfg, include_deactivated); +} + +/* check if specified "logical path" has been deleted in working config. + */ +bool +Cstore::cfgPathDeleted(const vector<string>& path_comps) +{ + // whether it's in active but not in working + return (cfg_path_exists(path_comps, true, false) + && !cfg_path_exists(path_comps, false, false)); +} + +/* check if specified "logical path" has been added in working config. + */ +bool +Cstore::cfgPathAdded(const vector<string>& path_comps) +{ + // whether it's not in active but in working + return (!cfg_path_exists(path_comps, true, false) + && cfg_path_exists(path_comps, false, false)); +} + +/* check if specified "logical path" has been "changed" in working config. + * XXX the definition of "changed" is different from the original + * perl API implementation isChanged(), which was inconsistent between + * "deleted" and "deactivated". + * + * original logic (with $disable arg not defined) returns true in + * either of the 2 cases below: + * (1) node is BEING deactivated or activated + * (2) node appears in changes_only dir + * which means it returns false for nodes being deleted but true + * for nodes being deactivated. + * + * new logic returns true if any of the following is true + * (remember this functions is NOT "deactivate-aware") + * (1) cfgPathDeleted() + * (2) cfgPathAdded() + * (3) marked_changed() + */ +bool +Cstore::cfgPathChanged(const vector<string>& path_comps) +{ + if (cfgPathDeleted(path_comps) || cfgPathAdded(path_comps)) { + return true; + } + SAVE_PATHS; + append_cfg_path(path_comps); + bool ret = marked_changed(); + RESTORE_PATHS; + return ret; +} + +/* get names of "deleted" child nodes of specified path during commit + * operation. names are returned in cnodes. + */ +void +Cstore::cfgPathGetDeletedChildNodes(const vector<string>& path_comps, + vector<string>& cnodes) +{ + cfgPathGetDeletedChildNodesDA(path_comps, cnodes, false); +} + +// same as above but "deactivate-aware" +void +Cstore::cfgPathGetDeletedChildNodesDA(const vector<string>& path_comps, + vector<string>& cnodes, + bool include_deactivated) +{ + vector<string> acnodes; + cfgPathGetChildNodesDA(path_comps, acnodes, true, include_deactivated); + vector<string> wcnodes; + cfgPathGetChildNodesDA(path_comps, wcnodes, false, include_deactivated); + map<string, bool> cmap; + for (unsigned int i = 0; i < wcnodes.size(); i++) { + cmap[wcnodes[i]] = true; + } + for (unsigned int i = 0; i < acnodes.size(); i++) { + if (cmap.find(acnodes[i]) == cmap.end()) { + // in active but not in working + cnodes.push_back(acnodes[i]); + } + } +} + +/* this is the equivalent of the listNodeStatus() from the original + * perl API. it provides the "status" ("deleted", "added", "changed", + * or "static") of each child node of specified path. + * cmap: (output) contains the status of child nodes. + * + * note: this function is NOT "deactivate-aware". + */ +void +Cstore::cfgPathGetChildNodesStatus(const vector<string>& path_comps, + map<string, string>& cmap) +{ + // get a union of active and working + map<string, bool> umap; + vector<string> acnodes; + vector<string> wcnodes; + cfgPathGetChildNodes(path_comps, acnodes, true); + cfgPathGetChildNodes(path_comps, wcnodes, false); + for (unsigned int i = 0; i < acnodes.size(); i++) { + umap[acnodes[i]] = true; + } + for (unsigned int i = 0; i < wcnodes.size(); i++) { + umap[wcnodes[i]] = true; + } + + // get the status of each one + vector<string> ppath = path_comps; + map<string, bool>::iterator it = umap.begin(); + for (; it != umap.end(); ++it) { + string c = (*it).first; + ppath.push_back(c); + // note: "changed" includes "deleted" and "added", so check those first. + if (cfgPathDeleted(ppath)) { + cmap[c] = C_NODE_STATUS_DELETED; + } else if (cfgPathAdded(ppath)) { + cmap[c] = C_NODE_STATUS_ADDED; + } else if (cfgPathChanged(ppath)) { + cmap[c] = C_NODE_STATUS_CHANGED; + } else { + cmap[c] = C_NODE_STATUS_STATIC; + } + ppath.pop_back(); + } +} + +/* DA version of the above function. + * cmap: (output) contains the status of child nodes. + * + * note: this follows the original perl API listNodeStatus() implementation. + */ +void +Cstore::cfgPathGetChildNodesStatusDA(const vector<string>& path_comps, + map<string, string>& cmap) +{ + // process deleted nodes first + vector<string> del_nodes; + cfgPathGetDeletedChildNodesDA(path_comps, del_nodes); + for (unsigned int i = 0; i < del_nodes.size(); i++) { + cmap[del_nodes[i]] = C_NODE_STATUS_DELETED; + } + + // get all nodes in working config + vector<string> work_nodes; + cfgPathGetChildNodesDA(path_comps, work_nodes, false); + vector<string> ppath = path_comps; + for (unsigned int i = 0; i < work_nodes.size(); i++) { + ppath.push_back(work_nodes[i]); + /* note: in the DA version here, we do NOT check the deactivate state + * when considering the state of the child nodes (which include + * deactivated ones). the reason is that this DA function is used + * for config output-related operations and should return whether + * each node is actually added/deleted from the config independent + * of its deactivate state. + * + * for "added" state, can't use cfgPathAdded() since it's not DA. + * + * deleted ones already handled above. + */ + if (!cfg_path_exists(ppath, true, true) + && cfg_path_exists(ppath, false, true)) { + cmap[work_nodes[i]] = C_NODE_STATUS_ADDED; + } else if (cfgPathChanged(ppath)) { + cmap[work_nodes[i]] = C_NODE_STATUS_CHANGED; + } else { + cmap[work_nodes[i]] = C_NODE_STATUS_STATIC; + } + ppath.pop_back(); + } +} + +/* check whether specified path is "deactivated" in working config or + * active config. + * a node is "deactivated" if the node itself or any of its ancestors is + * "marked deactivated". + */ +bool +Cstore::cfgPathDeactivated(const vector<string>& path_comps, bool active_cfg) +{ + vector<string> ppath; + for (unsigned int i = 0; i < path_comps.size(); i++) { + ppath.push_back(path_comps[i]); + if (cfgPathMarkedDeactivated(ppath, active_cfg)) { + // an ancestor or itself is marked deactivated + return true; + } + } + return false; +} + +/* check whether specified path is "marked deactivated" in working config or + * active config. + * a node is "marked deactivated" if a deactivate operation has been + * performed on the node. + */ +bool +Cstore::cfgPathMarkedDeactivated(const vector<string>& path_comps, + bool active_cfg) +{ + SAVE_PATHS; + append_cfg_path(path_comps); + bool ret = marked_deactivated(active_cfg); + RESTORE_PATHS; + return ret; +} + +/* get names of child nodes of specified path in working config or active + * config. names are returned in cnodes. + */ +void +Cstore::cfgPathGetChildNodes(const vector<string>& path_comps, + vector<string>& cnodes, bool active_cfg) +{ + cfgPathGetChildNodesDA(path_comps, cnodes, active_cfg, false); +} + +// same as above but "deactivate-aware" +void +Cstore::cfgPathGetChildNodesDA(const vector<string>& path_comps, + vector<string>& cnodes, bool active_cfg, + bool include_deactivated) +{ + if (!include_deactivated && cfgPathDeactivated(path_comps, active_cfg)) { + /* this node is deactivated (an ancestor or this node itself is + * marked deactivated) and we don't want to include deactivated. nop. + */ + return; + } + SAVE_PATHS; + append_cfg_path(path_comps); + get_all_child_node_names(cnodes, active_cfg, include_deactivated); + RESTORE_PATHS; +} + +/* get value of specified single-value node. + * value: (output) node value. + * active_cfg: whether to get value from active config. + * return false if fails (invalid node, doesn't exist, read fails, etc.). + * otherwise return true. + */ +bool +Cstore::cfgPathGetValue(const vector<string>& path_comps, string& value, + bool active_cfg) +{ + return cfgPathGetValueDA(path_comps, value, active_cfg, false); +} + +// same as above but "deactivate-aware" +bool +Cstore::cfgPathGetValueDA(const vector<string>& path_comps, string& value, + bool active_cfg, bool include_deactivated) +{ + vtw_def def; + if (!get_parsed_tmpl(path_comps, false, def)) { + // invalid node + return false; + } + /* note: the behavior here is different from original perl API, which + * does not check if specified node is indeed single-value. so if + * the function is erroneously used on a multi-value node, the + * original API will return a single string that includes all values. + * this new function will return failure in such cases. + */ + if (def.is_value || def.multi || def.tag || def.def_type == ERROR_TYPE) { + // specified path is not a single-value node + return false; + } + if (!cfg_path_exists(path_comps, active_cfg, include_deactivated)) { + // specified node doesn't exist + return false; + } + vector<string> vvec; + SAVE_PATHS; + append_cfg_path(path_comps); + bool ret = false; + if (read_value_vec(vvec, active_cfg)) { + if (vvec.size() >= 1) { + // if for some reason we got multiple values, just take the first one. + value = vvec[0]; + ret = true; + } + } + RESTORE_PATHS; + return ret; +} + +/* get values of specified multi-value node. + * values: (output) node values. + * active_cfg: whether to get values from active config. + * return false if fails (invalid node, doesn't exist, etc.). + * otherwise return true. + */ +bool +Cstore::cfgPathGetValues(const vector<string>& path_comps, + vector<string>& values, bool active_cfg) +{ + return cfgPathGetValuesDA(path_comps, values, active_cfg, false); +} + +// same as above but "deactivate-aware" +bool +Cstore::cfgPathGetValuesDA(const vector<string>& path_comps, + vector<string>& values, bool active_cfg, + bool include_deactivated) +{ + vtw_def def; + if (!get_parsed_tmpl(path_comps, false, def)) { + // invalid node + return false; + } + /* note: the behavior here is different from original perl API, which + * does not check if specified node is indeed multi-value. so if + * the function is erroneously used on a single-value node, the + * original API will return the node's value. this new function + * will return failure in such cases. + */ + if (def.is_value || !def.multi || def.tag || def.def_type == ERROR_TYPE) { + // specified path is not a multi-value node + return false; + } + if (!cfg_path_exists(path_comps, active_cfg, include_deactivated)) { + // specified node doesn't exist + return false; + } + SAVE_PATHS; + append_cfg_path(path_comps); + bool ret = read_value_vec(values, active_cfg); + RESTORE_PATHS; + return ret; +} + +/* get comment of specified node. + * comment: (output) node comment. + * active_cfg: whether to get comment from active config. + * return false if fails (invalid node, doesn't exist, etc.). + * otherwise return true. + */ +bool +Cstore::cfgPathGetComment(const vector<string>& path_comps, string& comment, + bool active_cfg) +{ + SAVE_PATHS; + append_cfg_path(path_comps); + bool ret = get_comment(comment, active_cfg); + RESTORE_PATHS; + return ret; +} + +/* the following functions are observers of the "effective" config. + * they can be used + * (1) outside a config session (e.g., op mode, daemons, callbacks, etc.). + * OR + * (2) during a config session + * + * HOWEVER, NOTE that the definition of "effective" is different under these + * two scenarios. + * (1) when used outside a config session, "effective" == "active". + * in other words, in such cases the effective config is the same + * as the running config. + * + * (2) when used during a config session, a config path (leading to either + * a "node" or a "value") is "effective" if ANY of the following + * is true. + * (a) active && working + * path is in both active and working configs, i.e., unchanged. + * (b) !active && working && committed + * path is not in active, has been set in working, AND has + * already been committed, i.e., "commit" has successfully + * processed the addition/update of the path. + * (c) active && !working && !committed + * path is in active, has been deleted from working, AND + * has not been committed yet, i.e., "commit" (per priority) has + * not processed the deletion of the path yet, or it has been + * processed but failed. + * + * note: during commit, deactivate has the same effect as delete. so + * in such cases, as far as these functions are concerned, + * deactivated nodes don't exist. + * + * originally, these functions are exclusively for use during config + * sessions. however, for some usage scenarios, it is useful to have a set + * of API functions that can be used both during and outside config + * sessions. therefore, definition (1) is added above for convenience. + * + * for example, a developer can use these functions in a script that can + * be used both during a commit action and outside config mode, as long as + * the developer is clearly aware of the difference between the above two + * definitions. + * + * note that when used outside a config session (i.e., definition (1)), + * these functions are equivalent to the observers for the "active" config. + * + * to avoid any confusiton, when possible (e.g., in a script that is + * exclusively used in op mode), developers should probably use those + * "active" observers explicitly when outside a config session instead + * of these "effective" observers. + * + * it is also important to note that when used outside a config session, + * due to race conditions, it is possible that the "observed" active config + * becomes out-of-sync with the config that is actually "in effect". + * specifically, this happens when two things occur simultaneously: + * (a) an observer function is called from outside a config session. + * AND + * (b) someone invokes "commit" inside a config session (any session). + * + * this is because "commit" only updates the active config at the end after + * all commit actions have been executed, so before the update happens, + * some config nodes have already become "effective" but are not yet in the + * "active config" and therefore are not observed by these functions. + * + * note that this is only a problem when the caller is outside config mode. + * in such cases, the caller (which could be an op-mode command, a daemon, + * a callback script, etc.) already must be able to handle config changes + * that can happen at any time. if "what's configured" is more important, + * using the "active config" should be fine as long as it is relatively + * up-to-date. if the actual "system state" is more important, then the + * caller should probably just check the system state in the first place + * (instead of using these config observers). + * + * one possible solution is for these "effective" observers to obtain the + * global commit lock before returning their observations. this has not + * been implemented yet since the impact of this issue is not clear at + * the moment. + */ + +// return whether specified path is "effective". +bool +Cstore::cfgPathEffective(const vector<string>& path_comps) +{ + vtw_def def; + if (!validateTmplPath(path_comps, false, def)) { + // invalid path + return false; + } + + bool in_active = cfg_path_exists(path_comps, true, false); + if (!inSession()) { + // not in a config session. use active config only. + return in_active; + } + + bool in_work = cfg_path_exists(path_comps, false, false); + if (in_active && in_work) { + // case (1) + return true; + } + bool ret = false; + SAVE_PATHS; + append_cfg_path(path_comps); + if (!in_active && in_work) { + // check if case (2) + ret = marked_committed(def, true); + } else if (in_active && !in_work) { + // check if case (3) + ret = !marked_committed(def, false); + } + RESTORE_PATHS; + + return ret; +} + +/* get names of "effective" child nodes of specified path during commit + * operation. see above function for definition of "effective". + * names are returned in cnodes. + */ +void +Cstore::cfgPathGetEffectiveChildNodes(const vector<string>& path_comps, + vector<string>& cnodes) +{ + if (!inSession()) { + // not in a config session. use active config only. + cfgPathGetChildNodes(path_comps, cnodes, true); + return; + } + + // get a union of active and working + map<string, bool> cmap; + vector<string> acnodes; + vector<string> wcnodes; + cfgPathGetChildNodes(path_comps, acnodes, true); + cfgPathGetChildNodes(path_comps, wcnodes, false); + for (unsigned int i = 0; i < acnodes.size(); i++) { + cmap[acnodes[i]] = true; + } + for (unsigned int i = 0; i < wcnodes.size(); i++) { + cmap[wcnodes[i]] = true; + } + + // get only the effective ones from the union + vector<string> ppath = path_comps; + map<string, bool>::iterator it = cmap.begin(); + for (; it != cmap.end(); ++it) { + string c = (*it).first; + ppath.push_back(c); + if (cfgPathEffective(ppath)) { + cnodes.push_back(c); + } + ppath.pop_back(); + } +} + +/* get the "effective" value of specified path during commit operation. + * value: (output) node value + * return true if successful. otherwise return false. + */ +bool +Cstore::cfgPathGetEffectiveValue(const vector<string>& path_comps, + string& value) +{ + if (!inSession()) { + // not in a config session. use active config only. + return cfgPathGetValue(path_comps, value, true); + } + + vector<string> ppath = path_comps; + string oval, nval; + bool oret = cfgPathGetValue(path_comps, oval, true); + bool nret = cfgPathGetValue(path_comps, nval, false); + bool ret = false; + // all 4 combinations of oret and nret are covered below + if (nret) { + // got new value + ppath.push_back(nval); + if (cfgPathEffective(ppath)) { + // nval already effective + value = nval; + ret = true; + } else if (!oret) { + // no oval. failure. + } else { + // oval still effective + value = oval; + ret = true; + } + } else if (oret) { + // got oval only + ppath.push_back(oval); + if (cfgPathEffective(ppath)) { + // oval still effective + value = oval; + ret = true; + } + } + return ret; +} + +/* get the "effective" values of specified path during commit operation. + * values: (output) node values + * return true if successful. otherwise return false. + */ +bool +Cstore::cfgPathGetEffectiveValues(const vector<string>& path_comps, + vector<string>& values) +{ + if (!inSession()) { + // not in a config session. use active config only. + cfgPathGetValues(path_comps, values, true); + return (values.size() > 0); + } + + // get a union of active and working + map<string, bool> vmap; + vector<string> ovals; + vector<string> nvals; + cfgPathGetValues(path_comps, ovals, true); + cfgPathGetValues(path_comps, nvals, false); + for (unsigned int i = 0; i < ovals.size(); i++) { + vmap[ovals[i]] = true; + } + for (unsigned int i = 0; i < nvals.size(); i++) { + vmap[nvals[i]] = true; + } + + // get only the effective ones from the union + vector<string> ppath = path_comps; + map<string, bool>::iterator it = vmap.begin(); + for (; it != vmap.end(); ++it) { + string c = (*it).first; + ppath.push_back(c); + if (cfgPathEffective(ppath)) { + values.push_back(c); + } + ppath.pop_back(); + } + return (values.size() > 0); +} + +/* get the value string that corresponds to specified variable ref string. + * ref_str: var ref string (e.g., "./cost/@"). + * cval: (output) contains the resulting string. + * from_active: if true, value string should come from "active config". + * otherwise from "working config". + * return true if successful. otherwise return false. + */ +bool +Cstore::getVarRef(const string& ref_str, clind_val& cval, bool from_active) +{ + bool ret = true; + SAVE_PATHS; + VarRef vref(this, ref_str, from_active); + string val; + vtw_type_e type; + if (!vref.getValue(val, type)) { + ret = false; + } else { + cval.val_type = type; + // follow original implementation. caller is supposed to free this. + cval.value = strdup(val.c_str()); + } + RESTORE_PATHS; + return ret; +} + +/* set the node corresponding to specified variable ref string to specified + * value. + * ref_str: var ref string (e.g., "../encrypted-password/@"). + * value: value to be set. + * to_active: if true, set in "active config". + * otherwise in "working config". + * return true if successful. otherwise return false. + */ +bool +Cstore::setVarRef(const string& ref_str, const string& value, bool to_active) +{ + /* XXX functions in cli_new only performs "set var ref" operations (e.g., + * '$VAR(@) = ""', which sets current node's value to empty string) + * during "commit", i.e., if a "set var ref" is specified in + * "syntax:", it will not be performed during "set" (but will be + * during commit). + * + * since commit has not been converted to use the new library, it + * does not use this function. instead, it uses the "cli_val_engine" + * implementation (where filesystem paths are deeply embedded, which + * makes it difficult to abstract low-level filesystem operations + * from high-level functions). as a result, this function is unused + * and untested at the moment. must revisit when converting commit. + */ + SAVE_PATHS; + VarRef vref(this, ref_str, to_active); + vector<string> pcomps; + bool ret = false; + if (vref.getSetPath(pcomps)) { + reset_paths(); + vtw_def def; + if (get_parsed_tmpl(pcomps, false, def)) { + if (!def.is_value && !def.tag && !def.multi + && def.def_type != ERROR_TYPE) { + // currently only support single-value node + append_cfg_path(pcomps); + if (write_value(value, to_active)) { + ret = true; + } + } + } + } + RESTORE_PATHS; + return ret; +} + +/* perform deactivate operation on a node, i.e., make the node + * "marked deactivated". + * note: assume all validations have been peformed (see activate.cpp). + * also, when marking a node as deactivated, all of its descendants + * that had been marked deactivated are unmarked. + */ +bool +Cstore::markCfgPathDeactivated(const vector<string>& path_comps) +{ + if (cfgPathDeactivated(path_comps)) { + output_user("The specified configuration node is already deactivated\n"); + // treat as success + return true; + } + + SAVE_PATHS; + append_cfg_path(path_comps); + bool ret = (mark_deactivated() && unmark_deactivated_descendants()); + RESTORE_PATHS; + return ret; +} + +/* perform activate operation on a node, i.e., make the node no longer + * "marked deactivated". + * note: assume all validations have been peformed (see activate.cpp). + */ +bool +Cstore::unmarkCfgPathDeactivated(const vector<string>& path_comps) +{ + SAVE_PATHS; + append_cfg_path(path_comps); + bool ret = unmark_deactivated(); + RESTORE_PATHS; + return ret; +} + +/* mark specified path as changed in working config. + * the marking is used during commit to check if a node has been changed. + * this should be done after set/delete/activate/deactivate. + * note: if a node is changed, all of its ancestors are also considered + * changed. + * return true if successful. otherwise return false. + */ +bool +Cstore::markCfgPathChanged(const vector<string>& path_comps) +{ + // first mark the root changed + if (!mark_changed()) { + return false; + } + + // now mark each level as changed + vector<string> ppath; + for (unsigned int i = 0; i < path_comps.size(); i++) { + ppath.push_back(path_comps[i]); + if (!cfg_path_exists(ppath, false, false)) { + // this level no longer in working. nothing further. + break; + } + SAVE_PATHS; + append_cfg_path(ppath); + bool ret = mark_changed(); + RESTORE_PATHS; + if (!ret) { + return false; + } + } + return true; +} + + +////// protected functions +void +Cstore::output_user(const char *fmt, ...) +{ + va_list alist; + va_start(alist, fmt); + if (out_stream) { + vfprintf(out_stream, fmt, alist); + } else { + vprintf(fmt, alist); + } + va_end(alist); +} + +void +Cstore::output_internal(const char *fmt, ...) +{ + va_list alist; + va_start(alist, fmt); + + int fdout = -1; + FILE *fout = NULL; + do { + // XXX for now use the constant from cli_val.h + if ((fdout = open(LOGFILE_STDOUT, O_WRONLY | O_CREAT, 0660)) == -1) { + break; + } + if (lseek(fdout, 0, SEEK_END) == ((off_t) -1)) { + break; + } + if ((fout = fdopen(fdout, "a")) == NULL) { + break; + } + vfprintf(fout, fmt, alist); + } while (0); + va_end(alist); + if (fout) { + fclose(fout); + // fdout is implicitly closed + } else if (fdout >= 0) { + close(fdout); + } +} + + +////// private functions +/* try to append the logical path to template path. + * is_tag: (output) whether the last component is a "tag". + * return false if logical path is not valid. otherwise return true. + */ +bool +Cstore::append_tmpl_path(const vector<string>& path_comps, bool& is_tag) +{ + is_tag = false; + for (unsigned int i = 0; i < path_comps.size(); i++) { + push_tmpl_path(path_comps[i]); + if (tmpl_node_exists()) { + // got exact match. continue to next component. + continue; + } + // not exact match. check if it's a tag. + pop_tmpl_path(); + push_tmpl_path_tag(); + if (tmpl_node_exists()) { + // got tag match. continue to next component. + if (i == (path_comps.size() - 1)) { + // last comp + is_tag = true; + } + continue; + } + // not a valid path + return false; + } + return true; +} + +/* check whether specified "logical path" is valid template path. + * then template at the path is parsed. + * path_comps: vector of path components. + * validate_vals: whether to validate all "values" along specified path. + * def: (output) parsed template. + * error: (output) error message if failed. + * return false if invalid template path. otherwise return true. + * note: + * also, if last path component is value (i.e., def.is_value), the template + * parsed is actually at "full path - 1". + */ +bool +Cstore::get_parsed_tmpl(const vector<string>& path_comps, bool validate_vals, + vtw_def& def, string& error) +{ + // default error message + error = "The specified configuration node is not valid"; + + if (tmpl_path_at_root() && path_comps.size() == 0) { + // empty path not valid + return false; + } + + /* note: this function may be invoked recursively (depth 1) when + * validating values, i.e., validate_value will process variable + * reference, which calls this indirectly to get templates. + * so need special save/restore identifier. + */ + const char *not_validating = "get_parsed_tmpl_not_validating"; + if (validate_vals) { + SAVE_PATHS; + } else { + save_paths(not_validating); + } + /* need at least 1 comp to work. 2 comps if last comp is value. + * so pop tmpl_path and prepend them. note that path_comps remain + * constant. + */ + vector<string> *pcomps = const_cast<vector<string> *>(&path_comps); + vector<string> new_path_comps; + if (path_comps.size() < 2) { + new_path_comps = path_comps; + pcomps = &new_path_comps; + for (unsigned int i = 0; i < 2 && pcomps->size() < 2; i++) { + if (!tmpl_path_at_root()) { + pcomps->insert(pcomps->begin(), pop_tmpl_path()); + } + } + } + bool ret = false; + do { + /* cases for template path: + * (1) valid path ending in "actual node", i.e., typeless node, tag node, + * single-value node, or multi-value node: + * => tmpl at full path. e.g.: + * typeless node: "service ssh allow-root" + * tag node: "interfaces ethernet" + * single-value node: "system gateway-address" + * multi-value node: "system name-server" + * (2) valid path ending in "value", i.e., tag (value of tag node), or + * value of single-/multi-value node: + * => tmpl at "full path - 1". e.g.: + * "value" of tag node: "interfaces ethernet eth0" + * value of single-value node: "system gateway-address 1.1.1.1" + * value of multi-value node: "system name-server 2.2.2.2" + * (3) invalid path + * => no tmpl + */ + // first scan up to "full path - 1" + bool valid = true; + for (unsigned int i = 0; i < (pcomps->size() - 1); i++) { + if ((*pcomps)[i] == "") { + // only the last component is potentially allowed to be empty str + valid = false; + break; + } + bool is_tag; + if (append_tmpl_path((*pcomps)[i], is_tag)) { + if (is_tag && validate_vals) { + /* last comp is tag and want to validate value. + * note: validate_val() will use the current tmpl path and cfg path. + * so need both at the "node" level before calling it. + * at this point cfg path is not pushed yet so no need to + * pop it. + */ + pop_tmpl_path(); + if (!validate_val(NULL, (*pcomps)[i])) { + // invalid value + error = "Value validation failed"; + valid = false; + break; + } + // restore tmpl path + append_tmpl_path((*pcomps)[i], is_tag); + } + /* cfg path is not used here but is needed by validate_val(), so + * need to keep it in sync with tmpl path. + */ + push_cfg_path((*pcomps)[i]); + } else { + // not a valid path + valid = false; + break; + } + } + if (!valid) { + // case (3) + break; + } + /* we are valid up to "full path - 1". now process last path component. + * first, if we are case (2), we should find a "value node" at this point. + * note: this is only possible if there are more than 1 component. otherwise + * we haven't done anything yet. + */ + if (pcomps->size() > 1) { + if (tmpl_parse(def)) { + if (def.tag || def.multi || def.def_type != ERROR_TYPE) { + // case (2). last component is "value". + if (validate_vals) { + // validate value + if (!validate_val(&def, (*pcomps)[pcomps->size() - 1])) { + // invalid value + error = "Value validation failed"; + break; + } + } + def.is_value = 1; + ret = true; + break; + } + } + // if no valid template or not a value, it's not case (2) so continue. + } + // now check last component + if ((*pcomps)[pcomps->size() - 1] == "") { + // only value is potentially allowed to be empty str + break; + } + push_tmpl_path((*pcomps)[pcomps->size() - 1]); + // no need to push cfg path (only needed for validate_val()) + if (tmpl_node_exists()) { + // case (1). last component is "node". + if (!tmpl_parse(def)) { + exit_internal("failed to parse tmpl [%s]\n", + tmpl_path_to_str().c_str()); + } + def.is_value = 0; + ret = true; + break; + } + // case (3) (fall through) + } while (0); + if (validate_vals) { + RESTORE_PATHS; + } else { + restore_paths(not_validating); + } + return ret; +} + +/* check if specified "logical path" is valid for "activate" or + * "deactivate" operation. + * return true if valid. otherwise return false. + */ +bool +Cstore::validate_act_deact(const vector<string>& path_comps, const string& op, + vtw_def& def) +{ + string terr; + if (!get_parsed_tmpl(path_comps, false, def, terr)) { + output_user("%s\n", terr.c_str()); + return false; + } + if (def.is_value && !def.tag) { + /* last component is a value of a single- or multi-value node (i.e., + * a leaf value) => not allowed + */ + output_user("Cannot %s a leaf configuration value\n", op.c_str()); + return false; + } + if (!cfg_path_exists(path_comps, false, true)) { + output_user("Nothing to %s (the specified %s does not exist)\n", + op.c_str(), (!def.is_value || def.tag) ? "node" : "value"); + return false; + } + return true; +} + +/* check if specified args is valid for "rename" or "copy" operation. + * return true if valid. otherwise return false. + */ +bool +Cstore::validate_rename_copy(const vector<string>& args, const string& op) +{ + if (args.size() != 5 || args[2] != "to") { + output_user("Invalid %s command\n", op.c_str()); + return false; + } + string otagnode = args[0]; + string otagval = args[1]; + string ntagnode = args[3]; + string ntagval = args[4]; + if (otagnode != ntagnode) { + output_user("Cannot %s from \"%s\" to \"%s\"\n", + op.c_str(), otagnode.c_str(), ntagnode.c_str()); + return false; + } + + // check the old path + vector<string> ppath; + ppath.push_back(otagnode); + ppath.push_back(otagval); + vtw_def def; + string terr; + if (!get_parsed_tmpl(ppath, false, def, terr)) { + output_user("%s\n", terr.c_str()); + return false; + } + if (!def.is_value || !def.tag) { + // can only rename "tagnode tagvalue" + output_user("Cannot %s under \"%s\"\n", op.c_str(), otagnode.c_str()); + return false; + } + if (!cfg_path_exists(ppath, false, true)) { + output_user("Configuration \"%s %s\" does not exist\n", + otagnode.c_str(), otagval.c_str()); + return false; + } + + // check the new path + ppath.pop_back(); + ppath.push_back(ntagval); + if (cfg_path_exists(ppath, false, true)) { + output_user("Configuration \"%s %s\" already exists\n", + ntagnode.c_str(), ntagval.c_str()); + return false; + } + if (!get_parsed_tmpl(ppath, true, def, terr)) { + output_user("%s\n", terr.c_str()); + return false; + } + return true; +} + +// convert args for "move" to be used for equivalent "rename" operation +bool +Cstore::conv_move_args_for_rename(const vector<string>& args, + vector<string>& edit_path_comps, + vector<string>& rn_args) +{ + /* note: + * "move interfaces ethernet eth2 vif 100 to 200" + * is equivalent to + * "edit interfaces ethernet eth2" + "rename vif 100 to vif 200" + * + * set the extra levels and then just validate as rename + */ + unsigned int num_args = args.size(); + if (num_args < 4) { + // need at least 4 args + return false; + } + for (unsigned int i = 0; i < (num_args - 4); i++) { + edit_path_comps.push_back(args[i]); + } + rn_args.push_back(args[num_args - 4]); // vif + rn_args.push_back(args[num_args - 3]); // 100 + rn_args.push_back(args[num_args - 2]); // to + rn_args.push_back(args[num_args - 4]); // vif + rn_args.push_back(args[num_args - 1]); // 200 + return true; +} + +/* check if specified "logical path" exists in working config or + * active config. + * v_def: ptr to parsed template. NULL if none. + * return true if it exists. otherwise return false. + */ +bool +Cstore::cfg_path_exists(const vector<string>& path_comps, + bool active_cfg, bool include_deactivated) +{ + SAVE_PATHS; + append_cfg_path(path_comps); + // first check if it's a "node". + bool ret = cfg_node_exists(active_cfg); + if (!ret) { + // doesn't exist as a node. maybe a value? + pop_cfg_path(); + ret = cfg_value_exists(path_comps[path_comps.size() - 1], active_cfg); + } + RESTORE_PATHS; + if (ret && !include_deactivated + && cfgPathDeactivated(path_comps, active_cfg)) { + // don't include deactivated + ret = false; + } + return ret; +} + +/* remove tag at current work path and its subtree. + * if specified tag is the last one, also remove the tag node. + * return true if successful. otherwise return false. + * note: assume current work path is a tag. + */ +bool +Cstore::remove_tag() +{ + if (!remove_node()) { + return false; + } + + // go up one level and check if that was the last tag + bool ret = true; + string c = pop_cfg_path(); + vector<string> cnodes; + // get child nodes, including deactivated ones. + get_all_child_node_names(cnodes, false, true); + if (cnodes.size() == 0) { + // it was the last tag. remove the node as well. + if (!remove_node()) { + ret = false; + } + } + push_cfg_path(c); + return ret; +} + +/* remove specified value from the multi-value node at current work path. + * if specified value is the last one, also remove the multi-value node. + * return true if successful. otherwise return false. + * note: assume current work path is a multi-value node and specified value is + * configured for the node. + */ +bool +Cstore::remove_value_from_multi(const string& value) +{ + // get current values + vector<string> vvec; + if (!read_value_vec(vvec, false)) { + return false; + } + + // remove the value + unsigned int bc = vvec.size(); + vector<string> nvec(vvec.begin(), remove(vvec.begin(), vvec.end(), value)); + unsigned int ac = nvec.size(); + + // sanity check + if (ac == bc) { + // nothing removed + return false; + } + if (ac == 0) { + // was the last value. remove the node. + return remove_node(); + } else { + // write the new values out + return write_value_vec(nvec); + } +} + +/* check whether specified value exists at current work path. + * note: assume current work path is a value node. + */ +bool +Cstore::cfg_value_exists(const string& value, bool active_cfg) +{ + // get current values + vector<string> vvec; + if (!read_value_vec(vvec, active_cfg)) { + return false; + } + + return (find(vvec.begin(), vvec.end(), value) != vvec.end()); +} + +/* validate value at current template path. + * def: pointer to parsed template. NULL if none. + * val: value to be validated. + * return true if valid. otherwise return false. + * note: current template and cfg paths both point to the node, + * not the value. + */ +bool +Cstore::validate_val(const vtw_def *def, const string& value) +{ + vtw_def ndef; + if (!def) { + if (!tmpl_parse(ndef)) { + exit_internal("failed to parse tmpl [%s]\n", tmpl_path_to_str().c_str()); + } + def = &ndef; + if (def->def_type == ERROR_TYPE) { + // not a value node + exit_internal("validating non-value node [%s]\n", + tmpl_path_to_str().c_str()); + } + } + + // validate_value() may change "value". make a copy first. + size_t vlen = value.size(); + char *vbuf = (char *) malloc(vlen + 1); + strncpy(vbuf, value.c_str(), vlen + 1); + vbuf[vlen] = 0; + bool ret = validate_val_impl((vtw_def *) def, vbuf); + free(vbuf); + return ret; +} + +/* add tag at current work path. + * return true if successful. otherwise return false. + * note: assume current work path is a new tag and path from root to parent + * already exists. + */ +bool +Cstore::add_tag(const vtw_def& def) +{ + string t = pop_cfg_path(); + vector<string> cnodes; + // get child nodes, excluding deactivated ones. + get_all_child_node_names(cnodes, false, false); + bool ret = false; + do { + if (def.def_tag > 0 && def.def_tag <= cnodes.size()) { + // limit exceeded + output_user("Cannot set node \"%s\": number of values exceeds limit" + "(%d allowed)\n", t.c_str(), def.def_tag); + break; + } + /* XXX the following is the original logic, which is wrong since def_tag + * is unsigned. + */ + if (def.def_tag < 0 && cnodes.size() == 1) { + /* XXX special case in the original implementation where the previous + * tag should be replaced. this is probably unnecessary since + * "rename" can be used for tag node anyway. + */ + ret = rename_child_node(cnodes[0], t); + break; + } + // neither of the above. just add the tag. + ret = add_child_node(t); + } while (0); + push_cfg_path(t); + return ret; +} + +/* add specified value to the multi-value node at current work path. + * return true if successful. otherwise return false. + * note: assume current work path is a multi-value node and specified value is + * not configured for the node. + */ +bool +Cstore::add_value_to_multi(const vtw_def& def, const string& value) +{ + // get current values + vector<string> vvec; + // ignore return value here. if it failed, vvec is empty. + read_value_vec(vvec, false); + + /* note: XXX the original limit-checking logic uses the same count as tag + * node, which is wrong since multi-node values are not stored as + * directories in the original implementation. + * + * also, original logic only applies limit when def_multi > 1. + * this was probably to support the special case in the design + * when def_multi is 1 to make it behave like a single-value + * node (i.e., a subsequent set replaces the value). however, + * the implementation uses "-1" as the special case (but def_multi + * is unsigned anyway). see also "add_tag()". + * + * for now just apply the limit for anything >= 1. + */ + if (def.def_multi >= 1 && vvec.size() >= def.def_multi) { + // limit exceeded + output_user("Cannot set value \"%s\": number of values exceeded " + "(%d allowed)\n", value.c_str(), def.def_multi); + return false; + } + + // append the value + vvec.push_back(value); + return write_value_vec(vvec); +} + +/* this uses the get_all_child_node_names_impl() from the underlying + * implementation but provides the option to exclude deactivated nodes. + */ +void +Cstore::get_all_child_node_names(vector<string>& cnodes, bool active_cfg, + bool include_deactivated) +{ + vector<string> nodes; + get_all_child_node_names_impl(nodes, active_cfg); + for (unsigned int i = 0; i < nodes.size(); i++) { + if (!include_deactivated) { + push_cfg_path(nodes[i]); + bool skip = marked_deactivated(active_cfg); + pop_cfg_path(); + if (skip) { + continue; + } + } + cnodes.push_back(nodes[i]); + } +} + +/* create all child nodes of current work path that have default values + * return true if successful. otherwise return false. + * note: assume current work path has just been created so no child + * nodes exist. + */ +bool +Cstore::create_default_children() +{ + vector<string> tcnodes; + get_all_tmpl_child_node_names(tcnodes); + bool ret = true; + for (unsigned int i = 0; i < tcnodes.size(); i++) { + push_tmpl_path(tcnodes[i]); + vtw_def def; + if (tmpl_node_exists() && tmpl_parse(def)) { + if (def.def_default) { + // has default value. set it. + push_cfg_path(tcnodes[i]); + if (!add_node() || !write_value(def.def_default) + || !mark_display_default()) { + ret = false; + } + pop_cfg_path(); + } + } + pop_tmpl_path(); + if (!ret) { + break; + } + } + return ret; +} + +/* return environment string for "edit"-related operations based on current + * work/tmpl paths. + */ +void +Cstore::get_edit_env(string& env) +{ + vector<string> lvec; + get_edit_level(lvec); + string lvl; + for (unsigned int i = 0; i < lvec.size(); i++) { + if (lvl.length() > 0) { + lvl += " "; + } + lvl += lvec[i]; + } + + env = ("export " + C_ENV_EDIT_LEVEL + "='" + get_edit_level_path() + "';"); + env += (" export " + C_ENV_TMPL_LEVEL + "='" + get_tmpl_level_path() + "';"); + env += (" export " + C_ENV_SHELL_PROMPT + "='" + + get_shell_prompt(lvl) + "';"); +} + +// return shell prompt string for specified edit level +string +Cstore::get_shell_prompt(const string& level) +{ + string lvl = level; + if (lvl.length() > 0) { + lvl = " " + lvl; + } + return ("[edit" + lvl + "]\\n\\u@\\h# "); +} + +// escape the single quotes in the string for shell +void +Cstore::shell_escape_squotes(string& str) +{ + size_t sq = 0; + while ((sq = str.find('\'', sq)) != str.npos) { + str.replace(sq, 1, "'\\''"); + sq += 4; + } +} + diff --git a/src/cstore/cstore.hpp b/src/cstore/cstore.hpp new file mode 100644 index 0000000..f6a4215 --- /dev/null +++ b/src/cstore/cstore.hpp @@ -0,0 +1,409 @@ +/* + * Copyright (C) 2010 Vyatta, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _CSTORE_H_ +#define _CSTORE_H_ +#include <vector> +#include <string> +#include <map> + +extern "C" { +#include <cli_val.h> +#include <cli_val_engine.h> +} + +#define exit_internal(fmt, args...) do \ + { \ + output_internal("[%s:%d] " fmt, __FILE__, __LINE__ , ##args); \ + exit(-1); \ + } while (0); + +/* macros for saving/restoring paths. + * note: this allows "nested" save/restore invocations but NOT recursive ones. + */ +#define SAVE_PATHS save_paths(&__func__) +#define RESTORE_PATHS restore_paths(&__func__) + +using namespace std; + +class Cstore { +public: + Cstore() {}; + Cstore(string& env); + ~Cstore() {}; + + // constants + static const string C_NODE_STATUS_DELETED; + static const string C_NODE_STATUS_ADDED; + static const string C_NODE_STATUS_CHANGED; + static const string C_NODE_STATUS_STATIC; + + static const string C_ENV_EDIT_LEVEL; + static const string C_ENV_TMPL_LEVEL; + + static const string C_ENV_SHELL_PROMPT; + static const string C_ENV_SHELL_CWORDS; + static const string C_ENV_SHELL_CWORD_COUNT; + + static const string C_ENV_SHAPI_COMP_VALS; + static const string C_ENV_SHAPI_LCOMP_VAL; + static const string C_ENV_SHAPI_COMP_HELP; + static const string C_ENV_SHAPI_HELP_ITEMS; + static const string C_ENV_SHAPI_HELP_STRS; + + static const string C_ENUM_SCRIPT_DIR; + + static const size_t MAX_CMD_OUTPUT_SIZE = 4096; + + ////// the public cstore interface + //// functions implemented in this base class + // these operate on template path + bool validateTmplPath(const vector<string>& path_comps, bool validate_vals); + bool validateTmplPath(const vector<string>& path_comps, bool validate_vals, + vtw_def& def); + bool getParsedTmpl(const vector<string>& path_comps, + map<string, string>& tmap, bool allow_val = true); + void tmplGetChildNodes(const vector<string>& path_comps, + vector<string>& cnodes); + + /****** + * functions for actual CLI operations: + * set + * delete + * activate + * deactivate + * rename + * copy + * comment + * discard + * move (currently this is only available in cfg-cmd-wrapper) + * edit-related commands (invoked from shell functions) + * completion-related (for completion script) + * session-related (setup, teardown, etc.) + * load (XXX currently still implemented in perl) + * + * these operate on the "working config" and the session and MUST NOT + * be used by anything other than the listed operations. + */ + // set + bool validateSetPath(const vector<string>& path_comps); + bool setCfgPath(const vector<string>& path_comps); + // delete + bool validateDeletePath(const vector<string>& path_comps, vtw_def& def); + bool deleteCfgPath(const vector<string>& path_comps, const vtw_def& def); + // activate (actually "unmark deactivated" since it is 2-state, not 3) + bool validateActivatePath(const vector<string>& path_comps); + bool unmarkCfgPathDeactivated(const vector<string>& path_comps); + // deactivate + bool validateDeactivatePath(const vector<string>& path_comps); + bool markCfgPathDeactivated(const vector<string>& path_comps); + // rename + bool validateRenameArgs(const vector<string>& args); + bool renameCfgPath(const vector<string>& args); + // copy + bool validateCopyArgs(const vector<string>& args); + bool copyCfgPath(const vector<string>& args); + // comment + bool validateCommentArgs(const vector<string>& args, vtw_def& def); + bool commentCfgPath(const vector<string>& args, const vtw_def& def); + // discard + bool discardChanges(); + // move + bool validateMoveArgs(const vector<string>& args); + bool moveCfgPath(const vector<string>& args); + // edit-related + bool getEditEnv(const vector<string>& path_comps, string& env); + bool getEditUpEnv(string& env); + bool getEditResetEnv(string& env); + bool editLevelAtRoot() { + return edit_level_at_root(); + }; + // completion-related + bool getCompletionEnv(const vector<string>& comps, string& env); + void getEditLevel(vector<string>& comps) { + get_edit_level(comps); + }; + // session-related + virtual bool markSessionUnsaved() = 0; + virtual bool unmarkSessionUnsaved() = 0; + virtual bool sessionUnsaved() = 0; + virtual bool sessionChanged() = 0; + virtual bool setupSession() = 0; + virtual bool teardownSession() = 0; + virtual bool inSession() = 0; + // common + bool markCfgPathChanged(const vector<string>& path_comps); + // XXX load + //bool unmarkCfgPathDeactivatedDescendants(const vector<string>& path_comps); + + /****** + * these functions are observers of the current "working config" or + * "active config" during a config session. + * MOST users of the cstore API should be using these functions (most + * likely during a commit operation). + * + * note that these MUST NOT worry about "deactivated" state. + * for these functions, deactivated nodes are equivalent to having been + * deleted. in other words, these functions are NOT "deactivate-aware". + * + * also, the functions that can be used to observe "active config" can + * be used outside a config session as well (only when observing active + * config, of course). + */ + // observers for "working config" (by default) OR "active config" + bool cfgPathExists(const vector<string>& path_comps, + bool active_cfg = false); + void cfgPathGetChildNodes(const vector<string>& path_comps, + vector<string>& cnodes, bool active_cfg = false); + bool cfgPathGetValue(const vector<string>& path_comps, string& value, + bool active_cfg = false); + bool cfgPathGetValues(const vector<string>& path_comps, + vector<string>& values, bool active_cfg = false); + bool cfgPathGetComment(const vector<string>& path_comps, string& comment, + bool active_cfg = false); + /* observers for working AND active configs (at the same time). + * MUST ONLY be used during config session. + */ + bool cfgPathDeleted(const vector<string>& path_comps); + bool cfgPathAdded(const vector<string>& path_comps); + bool cfgPathChanged(const vector<string>& path_comps); + void cfgPathGetDeletedChildNodes(const vector<string>& path_comps, + vector<string>& cnodes); + void cfgPathGetChildNodesStatus(const vector<string>& path_comps, + map<string, string>& cmap); + /* observers for "effective config" (a combination of working config, + * active config, and commit processing state) only. + * MUST ONLY be used during config session. + */ + bool cfgPathEffective(const vector<string>& path_comps); + void cfgPathGetEffectiveChildNodes(const vector<string>& path_comps, + vector<string>& cnodes); + bool cfgPathGetEffectiveValue(const vector<string>& path_comps, + string& value); + bool cfgPathGetEffectiveValues(const vector<string>& path_comps, + vector<string>& values); + + /****** + * "deactivate-aware" observers of the current working or active config. + * these are the only functions that are allowed to see the "deactivate" + * state of config nodes. + * + * these functions MUST ONLY be used by operations that NEED to distinguish + * between deactivated nodes and deleted nodes. below is the list + * of operations that are allowed to use these functions: + * * configuration output (show, save, load) + * + * operations that are not on the above list MUST NOT use these + * "deactivate-aware" functions. + * + * note: the last argument "include_deactivated" for the DA functions + * is for implementation convenience and does not need to be + * passed in when calling them. + */ + // working or active config + bool cfgPathDeactivated(const vector<string>& path_comps, + bool active_cfg = false); + bool cfgPathMarkedDeactivated(const vector<string>& path_comps, + bool active_cfg = false); + bool cfgPathExistsDA(const vector<string>& path_comps, + bool active_cfg = false, + bool include_deactivated = true); + void cfgPathGetChildNodesDA(const vector<string>& path_comps, + vector<string>& cnodes, + bool active_cfg = false, + bool include_deactivated = true); + bool cfgPathGetValueDA(const vector<string>& path_comps, string& value, + bool active_cfg = false, + bool include_deactivated = true); + bool cfgPathGetValuesDA(const vector<string>& path_comps, + vector<string>& values, + bool active_cfg = false, + bool include_deactivated = true); + // working AND active configs + void cfgPathGetDeletedChildNodesDA(const vector<string>& path_comps, + vector<string>& cnodes, + bool include_deactivated = true); + void cfgPathGetChildNodesStatusDA(const vector<string>& path_comps, + map<string, string>& cmap); + + + /* these are internal API functions and operate on current cfg and + * tmpl paths during cstore operations. they are only used to work around + * the limitations of the original CLI library implementation and MUST NOT + * be used by anyone other than the original CLI library. + */ + bool getVarRef(const string& ref_str, clind_val& cval, bool from_active); + bool setVarRef(const string& ref_str, const string& value, bool to_active); + +protected: + ////// functions for subclasses + void output_user(const char *fmt, ...); + void output_internal(const char *fmt, ...); + +private: + ////// member class + // for variable reference + class VarRef; + + ////// virtual + /* "path modifiers" + * note: only these functions are allowed to permanently change the paths. + * all other functions must "preserve" the paths (but can change paths + * "during" an invocation), i.e., the paths after invocation must be + * the same as before invocation. + */ + // begin path modifiers + virtual void push_tmpl_path(const string& path_comp) = 0; + virtual void push_tmpl_path_tag() = 0; + virtual string pop_tmpl_path() = 0; + virtual void push_cfg_path(const string& path_comp) = 0; + virtual void push_cfg_path_val() = 0; + virtual string pop_cfg_path() = 0; + virtual void append_cfg_path(const vector<string>& path_comps) = 0; + virtual void reset_paths() = 0; + virtual void save_paths(const void *handle = NULL) = 0; + virtual void restore_paths(const void *handle = NULL) = 0; + virtual bool cfg_path_at_root() = 0; + virtual bool tmpl_path_at_root() = 0; + // end path modifiers + + // these operate on current tmpl path + virtual bool tmpl_node_exists() = 0; + virtual bool tmpl_parse(vtw_def& def) = 0; + + // these operate on current work path (or active with "active_cfg") + virtual bool remove_node() = 0; + virtual void get_all_child_node_names_impl(vector<string>& cnodes, + bool active_cfg = false) = 0; + virtual void get_all_tmpl_child_node_names(vector<string>& cnodes) = 0; + virtual bool write_value_vec(const vector<string>& vvec, + bool active_cfg = false) = 0; + virtual bool add_node() = 0; + virtual bool rename_child_node(const string& oname, const string& nname) = 0; + virtual bool copy_child_node(const string& oname, const string& nname) = 0; + virtual bool mark_display_default() = 0; + virtual bool unmark_display_default() = 0; + virtual bool mark_deactivated() = 0; + virtual bool unmark_deactivated() = 0; + virtual bool unmark_deactivated_descendants() = 0; + virtual bool mark_changed() = 0; + virtual bool remove_comment() = 0; + virtual bool set_comment(const string& comment) = 0; + virtual bool discard_changes(unsigned long long& num_removed) = 0; + + // observers for current work path + virtual bool marked_changed() = 0; + virtual bool marked_display_default() = 0; + + // observers for current work path or active path + virtual bool read_value_vec(vector<string>& vvec, bool active_cfg) = 0; + virtual bool cfg_node_exists(bool active_cfg) = 0; + virtual bool marked_deactivated(bool active_cfg) = 0; + virtual bool get_comment(string& comment, bool active_cfg) = 0; + + // observers during commit operation + virtual bool marked_committed(const vtw_def& def, bool is_set) = 0; + + // these operate on both current tmpl and work paths + virtual bool validate_val_impl(vtw_def *def, char *value) = 0; + + // observers for "edit/tmpl levels" (for "edit"-related operations) + /* note that these should be handled in the base class since they + * should not be implementation-specific. however, current definitions + * of these "levels" environment vars require filesystem-specific + * "escapes", so handle them in derived class. + * + * revisit when the env vars are redefined. + * + * these operate on current "work" or "tmpl" path, i.e., cfg/tmpl path + * needs to be set up before calling these. + */ + virtual string get_edit_level_path() = 0; + virtual string get_tmpl_level_path() = 0; + virtual void get_edit_level(vector<string>& path_comps) = 0; + virtual bool edit_level_at_root() = 0; + + // these are for testing/debugging + virtual string cfg_path_to_str() = 0; + virtual string tmpl_path_to_str() = 0; + + ////// implemented + // begin path modifiers (only these can change path permanently) + bool append_tmpl_path(const vector<string>& path_comps, bool& is_tag); + bool append_tmpl_path(const vector<string>& path_comps) { + bool dummy; + return append_tmpl_path(path_comps, dummy); + }; + bool append_tmpl_path(const string& path_comp, bool& is_tag) { + return append_tmpl_path(vector<string>(1, path_comp), is_tag); + }; + bool append_tmpl_path(const string& path_comp) { + bool dummy; + return append_tmpl_path(path_comp, dummy); + }; + // end path modifiers + + // these require full path + // (note: get_parsed_tmpl also uses current tmpl path) + bool get_parsed_tmpl(const vector<string>& path_comps, bool validate_vals, + vtw_def& def, string& error); + bool get_parsed_tmpl(const vector<string>& path_comps, bool validate_vals, + vtw_def& def) { + string dummy; + return get_parsed_tmpl(path_comps, validate_vals, def, dummy); + }; + bool validate_act_deact(const vector<string>& path_comps, const string& op, + vtw_def& def); + bool validate_rename_copy(const vector<string>& args, const string& op); + bool conv_move_args_for_rename(const vector<string>& args, + vector<string>& edit_path_comps, + vector<string>& rn_args); + bool cfg_path_exists(const vector<string>& path_comps, + bool active_cfg, bool include_deactivated); + + // these operate on current work path (or active with "active_cfg") + bool remove_tag(); + bool remove_value_from_multi(const string& value); + bool write_value(const string& value, bool active_cfg = false) { + vector<string> vvec(1, value); + return write_value_vec(vvec, active_cfg); + }; + bool add_tag(const vtw_def& def); + bool add_value_to_multi(const vtw_def& def, const string& value); + bool add_child_node(const string& name) { + push_cfg_path(name); + bool ret = add_node(); + pop_cfg_path(); + return ret; + }; + void get_all_child_node_names(vector<string>& cnodes, bool active_cfg, + bool include_deactivated); + + // observers for work path or active path + bool cfg_value_exists(const string& value, bool active_cfg); + + // these operate on both current tmpl and work paths + bool validate_val(const vtw_def *def, const string& value); + bool create_default_children(); + void get_edit_env(string& env); + + // util functions + string get_shell_prompt(const string& level); + void shell_escape_squotes(string& str); +}; + +#endif /* _CSTORE_H_ */ + diff --git a/src/cstore/unionfs/cstore-unionfs.cpp b/src/cstore/unionfs/cstore-unionfs.cpp new file mode 100644 index 0000000..977a597 --- /dev/null +++ b/src/cstore/unionfs/cstore-unionfs.cpp @@ -0,0 +1,1078 @@ +/* + * Copyright (C) 2010 Vyatta, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <map> +#include <fstream> +#include <sstream> + +#include <errno.h> +#include <sys/mount.h> + +extern "C" { +#include "cli_val.h" +#include "cli_objects.h" +} + +#include "cstore-unionfs.hpp" + + +////// constants +// environment vars defining root dirs +const string UnionfsCstore::C_ENV_TMPL_ROOT = "VYATTA_CONFIG_TEMPLATE"; +const string UnionfsCstore::C_ENV_WORK_ROOT = "VYATTA_TEMP_CONFIG_DIR"; +const string UnionfsCstore::C_ENV_ACTIVE_ROOT + = "VYATTA_ACTIVE_CONFIGURATION_DIR"; +const string UnionfsCstore::C_ENV_CHANGE_ROOT = "VYATTA_CHANGES_ONLY_DIR"; +const string UnionfsCstore::C_ENV_TMP_ROOT = "VYATTA_CONFIG_TMP"; + +// default root dirs/paths +const string UnionfsCstore::C_DEF_TMPL_ROOT + = "/opt/vyatta/share/vyatta-cfg/templates"; +const string UnionfsCstore::C_DEF_CFG_ROOT + = "/opt/vyatta/config"; +const string UnionfsCstore::C_DEF_ACTIVE_ROOT + = UnionfsCstore::C_DEF_CFG_ROOT + "/active"; +const string UnionfsCstore::C_DEF_CHANGE_PREFIX = "/tmp/changes_only_"; +const string UnionfsCstore::C_DEF_WORK_PREFIX + = UnionfsCstore::C_DEF_CFG_ROOT + "/tmp/new_config_"; +const string UnionfsCstore::C_DEF_TMP_PREFIX + = UnionfsCstore::C_DEF_CFG_ROOT + "/tmp/tmp_"; + +// markers +const string UnionfsCstore::C_MARKER_DEF_VALUE = "def"; +const string UnionfsCstore::C_MARKER_DEACTIVATE = ".disable"; +const string UnionfsCstore::C_MARKER_CHANGED = ".modified"; +const string UnionfsCstore::C_MARKER_UNSAVED = ".unsaved"; +const string UnionfsCstore::C_COMMITTED_MARKER_FILE = "/tmp/.changes"; +const string UnionfsCstore::C_COMMENT_FILE = ".comment"; + + +////// static +static map<char, string> _fs_escape_chars; +static map<string, char> _fs_unescape_chars; +static void +_init_fs_escape_chars() +{ + _fs_escape_chars[-1] = "\%\%\%"; + _fs_escape_chars['%'] = "\%25"; + _fs_escape_chars['/'] = "\%2F"; + + _fs_unescape_chars["\%\%\%"] = -1; + _fs_unescape_chars["\%25"] = '%'; + _fs_unescape_chars["\%2F"] = '/'; +} + +static string +_escape_char(char c) +{ + map<char, string>::iterator p = _fs_escape_chars.find(c); + if (p != _fs_escape_chars.end()) { + return _fs_escape_chars[c]; + } else { + return string(1, c); + } +} + +static map<string, string> _escape_path_name_cache; + +static string +_escape_path_name(const string& path) +{ + map<string, string>::iterator p = _escape_path_name_cache.find(path); + if (p != _escape_path_name_cache.end()) { + // found escaped string in cache. just return it. + return _escape_path_name_cache[path]; + } + + // special case for empty string + string npath = (path.size() == 0) ? _fs_escape_chars[-1] : ""; + for (unsigned int i = 0; i < path.size(); i++) { + npath += _escape_char(path[i]); + } + + // cache it before return + _escape_path_name_cache[path] = npath; + return npath; +} + +static map<string, string> _unescape_path_name_cache; + +static string +_unescape_path_name(const string& path) +{ + map<string, string>::iterator p = _unescape_path_name_cache.find(path); + if (p != _unescape_path_name_cache.end()) { + // found unescaped string in cache. just return it. + return _unescape_path_name_cache[path]; + } + + // assume all escape patterns are 3-char + string npath = ""; + for (unsigned int i = 0; i < path.size(); i++) { + if ((path.size() - i) < 3) { + npath += path.substr(i); + break; + } + string s = path.substr(i, 3); + map<string, char>::iterator p = _fs_unescape_chars.find(s); + if (p != _fs_unescape_chars.end()) { + char c = _fs_unescape_chars[s]; + if (path.size() == 3 && c == -1) { + // special case for empty string + npath = ""; + break; + } + npath += string(1, _fs_unescape_chars[s]); + // skip the escape sequence + i += 2; + } else { + npath += path.substr(i, 1); + } + } + // cache it before return + _unescape_path_name_cache[path] = npath; + return npath; +} + + +////// constructor/destructor +/* "current session" constructor. + * this constructor sets up the object from environment. + * used when environment is already set up, i.e., when operating on the + * "current" config session. e.g., in the following scenarios + * configure commands + * perl module + * shell "current session" api + * + * note: this also applies when using the cstore in operational mode, + * in which case only the template root and the active root will be + * valid. + */ +UnionfsCstore::UnionfsCstore(bool use_edit_level) +{ + // set up root dir strings + char *val; + if ((val = getenv(C_ENV_TMPL_ROOT.c_str()))) { + tmpl_path = val; + } else { + tmpl_path = C_DEF_TMPL_ROOT; + } + tmpl_root = tmpl_path; // save a copy of tmpl root + if ((val = getenv(C_ENV_WORK_ROOT.c_str()))) { + work_root = val; + } + if ((val = getenv(C_ENV_TMP_ROOT.c_str()))) { + tmp_root = val; + } + if ((val = getenv(C_ENV_ACTIVE_ROOT.c_str()))) { + active_root = val; + } else { + active_root = C_DEF_ACTIVE_ROOT; + } + if ((val = getenv(C_ENV_CHANGE_ROOT.c_str()))) { + change_root = val; + } + + /* note: the original perl API module does not use the edit levels + * from environment. only the actual CLI operations use them. + * so here make it an option. + */ + if (use_edit_level) { + // set up path strings + if ((val = getenv(C_ENV_EDIT_LEVEL.c_str()))) { + mutable_cfg_path = val; + } + if ((val = getenv(C_ENV_TMPL_LEVEL.c_str()))) { + tmpl_path /= val; + } + } + _init_fs_escape_chars(); +} + +/* "specific session" constructor. + * this constructor sets up the object for the specified session ID and + * returns an environment string that can be "evaled" to set up the + * shell environment. + * + * used when the session environment needs to be established. this is + * mainly for the shell functions that set up configuration sessions. + * i.e., the "vyatta-cfg-cmd-wrapper" (on boot or for GUI etc.) and + * the cfg completion script (when entering configure mode). + * + * sid: session ID. + * env: (output) environment string. + * + * note: this does NOT set up the session. caller needs to use the + * explicit session setup/teardown functions as needed. + */ +UnionfsCstore::UnionfsCstore(const string& sid, string& env) + : Cstore(env) +{ + tmpl_root = C_DEF_TMPL_ROOT; + tmpl_path = tmpl_root; + active_root = C_DEF_ACTIVE_ROOT; + work_root = (C_DEF_WORK_PREFIX + sid); + change_root = (C_DEF_CHANGE_PREFIX + sid); + tmp_root = (C_DEF_TMP_PREFIX + sid); + + string declr = " declare -x -r "; // readonly vars + env += " umask 002; {"; + env += (declr + C_ENV_ACTIVE_ROOT + "=" + active_root.file_string()); + env += (declr + C_ENV_CHANGE_ROOT + "=" + change_root.file_string() + ";"); + env += (declr + C_ENV_WORK_ROOT + "=" + work_root.file_string() + ";"); + env += (declr + C_ENV_TMP_ROOT + "=" + tmp_root.file_string() + ";"); + env += (declr + C_ENV_TMPL_ROOT + "=" + tmpl_root.file_string() + ";"); + env += " } >&/dev/null || true"; + + // set up path strings using level vars + char *val; + if ((val = getenv(C_ENV_EDIT_LEVEL.c_str()))) { + mutable_cfg_path = val; + } + if ((val = getenv(C_ENV_TMPL_LEVEL.c_str()))) { + tmpl_path /= val; + } + + _init_fs_escape_chars(); +} + +UnionfsCstore::~UnionfsCstore() +{ +} + + +////// public virtual functions declared in base class +bool +UnionfsCstore::markSessionUnsaved() +{ + b_fs::path marker = work_root / C_MARKER_UNSAVED; + if (b_fs::exists(marker)) { + // already marked. treat as success. + return true; + } + if (!create_file(marker.file_string())) { + output_internal("failed to mark unsaved [%s]\n", + marker.file_string().c_str()); + return false; + } + return true; +} + +bool +UnionfsCstore::unmarkSessionUnsaved() +{ + b_fs::path marker = work_root / C_MARKER_UNSAVED; + if (!b_fs::exists(marker)) { + // not marked. treat as success. + return true; + } + try { + b_fs::remove(marker); + } catch (...) { + output_internal("failed to unmark unsaved [%s]\n", + marker.file_string().c_str()); + return false; + } + return true; +} + +bool +UnionfsCstore::sessionUnsaved() +{ + b_fs::path marker = work_root / C_MARKER_UNSAVED; + return b_fs::exists(marker); +} + +bool +UnionfsCstore::sessionChanged() +{ + b_fs::path marker = work_root / C_MARKER_CHANGED; + return b_fs::exists(marker); +} + +/* set up the session associated with this object. + * the session comes from either the environment or the session ID + * (see the two different constructors). + */ +bool +UnionfsCstore::setupSession() +{ + if (!b_fs::exists(work_root)) { + // session doesn't exist. create dirs. + try { + b_fs::create_directories(work_root); + b_fs::create_directories(change_root); + b_fs::create_directories(tmp_root); + if (!b_fs::exists(active_root)) { + // this should only be needed on boot + b_fs::create_directories(active_root); + } + } catch (...) { + output_internal("setup session failed to create session directories\n"); + return false; + } + + // union mount + string mopts = ("dirs=" + change_root.file_string() + "=rw:" + + active_root.file_string() + "=ro"); + if (mount("unionfs", work_root.file_string().c_str(), "unionfs", 0, + mopts.c_str()) != 0) { + output_internal("setup session mount failed [%s][%s]\n", + strerror(errno), work_root.file_string().c_str()); + return false; + } + } else if (!b_fs::is_directory(work_root)) { + output_internal("setup session not dir [%s]\n", + work_root.file_string().c_str()); + return false; + } + return true; +} + +/* tear down the session associated with this object. + * the session comes from either the environment or the session ID + * (see the two different constructors). + */ +bool +UnionfsCstore::teardownSession() +{ + // check if session exists + string wstr = work_root.file_string(); + if (wstr.empty() || wstr.find(C_DEF_WORK_PREFIX) != 0 + || !b_fs::exists(work_root) || !b_fs::is_directory(work_root)) { + // no session + output_internal("teardown invalid session [%s]\n", wstr.c_str()); + return false; + } + + // unmount the work root (union) + if (umount(wstr.c_str()) != 0) { + output_internal("teardown session umount failed [%s][%s]\n", + strerror(errno), wstr.c_str()); + return false; + } + + // remove session directories + bool ret = false; + try { + if (b_fs::remove_all(work_root) != 0 + && b_fs::remove_all(change_root) != 0 + && b_fs::remove_all(tmp_root) != 0) { + ret = true; + } + } catch (...) { + } + if (!ret) { + output_internal("failed to remove session directories\n"); + } + return ret; +} + +/* whether an actual config session is associated with this object. + * the session comes from either the environment or the session ID + * (see the two different constructors). + */ +bool +UnionfsCstore::inSession() +{ + string wstr = work_root.file_string(); + return (!wstr.empty() && wstr.find(C_DEF_WORK_PREFIX) == 0 + && b_fs::exists(work_root) && b_fs::is_directory(work_root)); +} + + +////// virtual functions defined in base class +/* check if current tmpl_path is a valid tmpl dir. + * return true if valid. otherwise return false. + */ +bool +UnionfsCstore::tmpl_node_exists() +{ + return (b_fs::exists(tmpl_path) && b_fs::is_directory(tmpl_path)); +} + +/* parse template at current tmpl_path. + * def: for storing parsed template. + * return true if successful. otherwise return false. + */ +bool +UnionfsCstore::tmpl_parse(vtw_def& def) +{ + push_tmpl_path(DEF_NAME); + bool ret = (b_fs::exists(tmpl_path) && b_fs::is_regular(tmpl_path) + && parse_def(&def, tmpl_path.file_string().c_str(), FALSE) == 0); + pop_tmpl_path(); + return ret; +} + +bool +UnionfsCstore::cfg_node_exists(bool active_cfg) +{ + b_fs::path p = (active_cfg ? get_active_path() : get_work_path()); + return (b_fs::exists(p) && b_fs::is_directory(p)); +} + +bool +UnionfsCstore::add_node() +{ + bool ret = true; + try { + if (!b_fs::create_directory(get_work_path())) { + // already exists. shouldn't call this function. + ret = false; + } + } catch (...) { + ret = false; + } + if (!ret) { + output_internal("failed to add node [%s]\n", + get_work_path().file_string().c_str()); + } + return ret; +} + +bool +UnionfsCstore::remove_node() +{ + if (!b_fs::exists(get_work_path()) || !b_fs::is_directory(get_work_path())) { + output_internal("remove non-existent node [%s]\n", + get_work_path().file_string().c_str()); + return false; + } + bool ret = false; + try { + if (b_fs::remove_all(get_work_path()) != 0) { + ret = true; + } + } catch (...) { + ret = false; + } + if (!ret) { + output_internal("failed to remove node [%s]\n", + get_work_path().file_string().c_str()); + } + return ret; +} + +void +UnionfsCstore::get_all_child_node_names_impl(vector<string>& cnodes, + bool active_cfg) +{ + b_fs::path p = (active_cfg ? get_active_path() : get_work_path()); + get_all_child_dir_names(p, cnodes); + + /* XXX special cases to emulate original perl API behavior. + * original perl listNodes() and listOrigNodes() return everything + * under a node (except for ".*"), including "node.val" and "def". + * + * perl API should operate at abstract level and should not access + * such implementation-specific details. however, currently + * things like config output depend on this behavior, so this + * function needs to return them for now. + * + * use a whilelist-approach, i.e., only add the following: + * node.val + * def + */ + if (b_fs::exists(p / VAL_NAME) && b_fs::is_regular(p / VAL_NAME)) { + cnodes.push_back(VAL_NAME); + } + if (b_fs::exists(p / C_MARKER_DEF_VALUE) + && b_fs::is_regular(p / C_MARKER_DEF_VALUE)) { + cnodes.push_back(C_MARKER_DEF_VALUE); + } +} + +bool +UnionfsCstore::read_value_vec(vector<string>& vvec, bool active_cfg) +{ + push_cfg_path_val(); + b_fs::path vpath = (active_cfg ? get_active_path() : get_work_path()); + bool ret = false; + do { + string ostr; + if (!read_whole_file(vpath, ostr)) { + break; + } + + /* XXX original implementation used to remove a trailing '\n' after + * a read. it was only necessary because it was adding a '\n' when + * writing the file. don't remove anything now since we shouldn't + * be writing it any more. + */ + // separate values using newline as delimiter + unsigned int start_idx = 0, idx = 0; + for (; idx < ostr.size(); idx++) { + if (ostr[idx] == '\n') { + // got a value + vvec.push_back(ostr.substr(start_idx, (idx - start_idx))); + start_idx = idx + 1; + } + } + if (start_idx < ostr.size()) { + vvec.push_back(ostr.substr(start_idx, (idx - start_idx))); + } else { + // last char is a newline => another empty value + vvec.push_back(""); + } + ret = true; + } while (0); + pop_cfg_path(); + return ret; +} + +bool +UnionfsCstore::write_value_vec(const vector<string>& vvec, bool active_cfg) +{ + push_cfg_path_val(); + bool ret = false; + b_fs::path wp = (active_cfg ? get_active_path() : get_work_path()); + do { + if (b_fs::exists(wp) && !b_fs::is_regular(wp)) { + // not a file + break; + } + + string ostr = ""; + for (unsigned int i = 0; i < vvec.size(); i++) { + if (i > 0) { + // subsequent values require delimiter + ostr += "\n"; + } + ostr += vvec[i]; + } + + if (!write_file(wp.file_string().c_str(), ostr)) { + break; + } + ret = true; + } while (0); + pop_cfg_path(); + if (!ret) { + output_internal("failed to write node value [%s]\n", + wp.file_string().c_str()); + } + return ret; +} + +bool +UnionfsCstore::rename_child_node(const string& oname, const string& nname) +{ + b_fs::path opath = get_work_path() / oname; + b_fs::path npath = get_work_path() / nname; + if (!b_fs::exists(opath) || !b_fs::is_directory(opath) + || b_fs::exists(npath)) { + output_internal("cannot rename node [%s,%s,%s]\n", + get_work_path().file_string().c_str(), + oname.c_str(), nname.c_str()); + return false; + } + bool ret = true; + try { + /* somehow b_fs::rename() can't be used here as it considers the operation + * "Invalid cross-device link" and fails with an exception, probably due + * to unionfs in some way. + * do it the hard way. + */ + recursive_copy_dir(opath, npath); + if (b_fs::remove_all(opath) == 0) { + ret = false; + } + } catch (...) { + ret = false; + } + if (!ret) { + output_internal("failed to rename node [%s,%s]\n", + opath.file_string().c_str(), + npath.file_string().c_str()); + } + return ret; +} + +bool +UnionfsCstore::copy_child_node(const string& oname, const string& nname) +{ + b_fs::path opath = get_work_path() / oname; + b_fs::path npath = get_work_path() / nname; + if (!b_fs::exists(opath) || !b_fs::is_directory(opath) + || b_fs::exists(npath)) { + output_internal("cannot copy node [%s,%s,%s]\n", + get_work_path().file_string().c_str(), + oname.c_str(), nname.c_str()); + return false; + } + try { + recursive_copy_dir(opath, npath); + } catch (...) { + output_internal("failed to copy node [%s,%s,%s]\n", + get_work_path().file_string().c_str(), + oname.c_str(), nname.c_str()); + return false; + } + return true; +} + +bool +UnionfsCstore::mark_display_default() +{ + b_fs::path marker = get_work_path() / C_MARKER_DEF_VALUE; + if (b_fs::exists(marker)) { + // already marked. treat as success. + return true; + } + if (!create_file(marker.file_string())) { + output_internal("failed to mark default [%s]\n", + get_work_path().file_string().c_str()); + return false; + } + return true; +} + +bool +UnionfsCstore::unmark_display_default() +{ + b_fs::path marker = get_work_path() / C_MARKER_DEF_VALUE; + if (!b_fs::exists(marker)) { + // not marked. treat as success. + return true; + } + try { + b_fs::remove(marker); + } catch (...) { + output_internal("failed to unmark default [%s]\n", + get_work_path().file_string().c_str()); + return false; + } + return true; +} + +bool +UnionfsCstore::marked_display_default() +{ + b_fs::path marker = get_work_path() / C_MARKER_DEF_VALUE; + return b_fs::exists(marker); +} + +bool +UnionfsCstore::marked_deactivated(bool active_cfg) +{ + b_fs::path p = (active_cfg ? get_active_path() : get_work_path()); + b_fs::path marker = p / C_MARKER_DEACTIVATE; + return b_fs::exists(marker); +} + +bool +UnionfsCstore::mark_deactivated() +{ + b_fs::path marker = get_work_path() / C_MARKER_DEACTIVATE; + if (b_fs::exists(marker)) { + // already marked. treat as success. + return true; + } + if (!create_file(marker.file_string())) { + output_internal("failed to mark deactivated [%s]\n", + get_work_path().file_string().c_str()); + return false; + } + return true; +} + +bool +UnionfsCstore::unmark_deactivated() +{ + b_fs::path marker = get_work_path() / C_MARKER_DEACTIVATE; + if (!b_fs::exists(marker)) { + // not deactivated. treat as success. + return true; + } + try { + b_fs::remove(marker); + } catch (...) { + output_internal("failed to unmark deactivated [%s]\n", + get_work_path().file_string().c_str()); + return false; + } + return true; +} + +bool +UnionfsCstore::unmark_deactivated_descendants() +{ + bool ret = false; + do { + // sanity check + if (!b_fs::is_directory(get_work_path())) { + break; + } + + vector<b_fs::path> markers; + b_fs::recursive_directory_iterator di(get_work_path()); + for (; di != b_fs::recursive_directory_iterator(); ++di) { + if (!b_fs::is_regular(di->path()) + || di->path().filename() != C_MARKER_DEACTIVATE) { + // not marker + continue; + } + if (di->path().parent_path() == get_work_path()) { + // don't unmark the node itself + continue; + } + markers.push_back(di->path()); + } + try { + for (unsigned int i = 0; i < markers.size(); i++) { + b_fs::remove(markers[i]); + } + } catch (...) { + break; + } + ret = true; + } while (0); + if (!ret) { + output_internal("failed to unmark deactivated descendants [%s]\n", + get_work_path().file_string().c_str()); + } + return ret; +} + +bool +UnionfsCstore::mark_changed() +{ + if (!mutable_cfg_path.has_parent_path()) { + /* at root, mark changed. root marker is needed by the original + * implementation as an indication of whether the whole config + * has changed. + */ + b_fs::path marker = get_work_path() / C_MARKER_CHANGED; + if (b_fs::exists(marker)) { + // already marked. treat as success. + return true; + } + if (!create_file(marker.file_string())) { + output_internal("failed to mark changed [%s]\n", + get_work_path().file_string().c_str()); + return false; + } + return true; + } + + /* XXX not at root => nop for now. + * we should be marking changed here. however, as commit is still + * using its own unionfs implementation, it will not understand the + * markers and therefore will not perform the necessary cleanup when + * it's done. + * + * for now, don't mark anything besides root. the query function + * will use unionfs-specific implementation (changes-only dir). + */ + return true; +} + +// remove the comment at the current work path +bool +UnionfsCstore::remove_comment() +{ + b_fs::path cfile = get_work_path() / C_COMMENT_FILE; + if (!b_fs::exists(cfile)) { + return false; + } + try { + b_fs::remove(cfile); + } catch (...) { + output_internal("failed to remove comment [%s]\n", + cfile.file_string().c_str()); + return false; + } + return true; +} + +// set comment at the current work path +bool +UnionfsCstore::set_comment(const string& comment) +{ + b_fs::path cfile = get_work_path() / C_COMMENT_FILE; + return write_file(cfile.file_string(), comment); +} + +// discard all changes in working config +bool +UnionfsCstore::discard_changes(unsigned long long& num_removed) +{ + // unionfs-specific implementation + vector<b_fs::path> files; + vector<b_fs::path> directories; + // iterate through all entries in change root + b_fs::directory_iterator di(change_root); + for (; di != b_fs::directory_iterator(); ++di) { + if (b_fs::is_directory(di->path())) { + directories.push_back(di->path()); + } else { + files.push_back(di->path()); + } + } + + // remove and count + try { + num_removed = 0; + for (unsigned int i = 0; i < files.size(); i++) { + b_fs::remove(files[i]); + num_removed++; + } + for (unsigned int i = 0; i < directories.size(); i++) { + num_removed += b_fs::remove_all(directories[i]); + } + } catch (...) { + output_internal("discard failed [%s]\n", + change_root.file_string().c_str()); + return false; + } + return true; +} + +// get comment at the current work or active path +bool +UnionfsCstore::get_comment(string& comment, bool active_cfg) +{ + b_fs::path cfile = (active_cfg ? get_active_path() : get_work_path()); + cfile /= C_COMMENT_FILE; + return read_whole_file(cfile, comment); +} + +bool +UnionfsCstore::marked_changed() +{ + /* this function is only called by cfgPathChanged() in base class. + * + * XXX currently just use the changes_only dir for this query. + * see explanation in mark_changed(). + * + * this implementation relies on the fact that cfgPathChanged() + * includes deleted/added nodes (including deactivated/activated + * nodes since it's NOT deactivate-aware). if that is not the case, + * result will be different between deleted nodes (NOT IN + * changes_only) and deactivated nodes (IN changes_only). + */ + return b_fs::exists(get_change_path()); +} + +/* XXX currently "committed marking" is done inside commit. + * TODO move "committed marking" out of commit and into low-level + * implementation (here). + */ +/* return whether current "cfg path" has been committed, i.e., whether + * the set or delete operation on the path has been processed by commit. + * is_set: whether the operation is set (for sanity check as there can + * be only one operation on the path). + */ +bool +UnionfsCstore::marked_committed(const vtw_def& def, bool is_set) +{ + b_fs::path cpath = mutable_cfg_path; + string com_str = cpath.file_string() + "/"; + if (def.is_value && !def.tag) { + // path includes leaf value. construct the right string. + string val = _unescape_path_name(cpath.filename()); + cpath = cpath.parent_path(); + /* XXX current commit implementation escapes value strings for + * single-value nodes but not for multi-value nodes for some + * reason. the following match current behavior. + */ + if (!def.multi) { + val = _escape_path_name(val); + } + com_str = cpath.file_string() + "/value:" + val; + } + com_str = (is_set ? "+ " : "- ") + com_str; + return committed_marker_exists(com_str); +} + +bool +UnionfsCstore::validate_val_impl(vtw_def *def, char *value) +{ + /* XXX filesystem paths/accesses are completely embedded in var ref lib. + * for now, treat the lib as a unionfs-specific implementation. + * generalizing it will need a rewrite. + * set the handle to be used during validate_value() for var ref + * processing. this is a global var in cli_new.c. + */ + var_ref_handle = (void *) this; + bool ret = validate_value(def, value); + var_ref_handle = NULL; + return ret; +} + +void +UnionfsCstore::get_edit_level(vector<string>& pcomps) { + b_fs::path opath = mutable_cfg_path; // use a copy + while (opath.has_parent_path()) { + pcomps.insert(pcomps.begin(), pop_path(opath)); + } +} + +string +UnionfsCstore::cfg_path_to_str() { + string cpath = mutable_cfg_path.file_string(); + if (cpath.length() == 0) { + cpath = "/"; + } + return cpath; +} + +string +UnionfsCstore::tmpl_path_to_str() { + // return only the mutable part + string tpath = tmpl_path.file_string(); + tpath.erase(0, tmpl_root.file_string().length()); + if (tpath.length() == 0) { + tpath = "/"; + } + return tpath; +} + + +////// private functions +void +UnionfsCstore::push_path(b_fs::path& old_path, const string& new_comp) +{ + string comp = _escape_path_name(new_comp); + old_path /= comp; +} + +string +UnionfsCstore::pop_path(b_fs::path& path) +{ + string ret = _unescape_path_name(path.filename()); + /* note: contrary to documentation, remove_filename() does not remove + * trailing slash. + */ + path = path.parent_path(); + return ret; +} + +void +UnionfsCstore::get_all_child_dir_names(b_fs::path root, vector<string>& nodes) +{ + if (!b_fs::exists(root) || !b_fs::is_directory(root)) { + // not a valid root. nop. + return; + } + b_fs::directory_iterator di(root); + for (; di != b_fs::directory_iterator(); ++di) { + // must be directory + if (!b_fs::is_directory(di->path())) { + continue; + } + // name cannot start with "." + if (di->path().file_string().substr(0, 1) == ".") { + continue; + } + // found one + nodes.push_back(_unescape_path_name(di->path().filename())); + } +} + +bool +UnionfsCstore::write_file(const string& file, const string& data) +{ + try { + // make sure the path exists + b_fs::path fpath(file); + b_fs::create_directories(fpath.parent_path()); + + // write the file + ofstream fout; + fout.exceptions(ofstream::failbit | ofstream::badbit); + fout.open(file.c_str(), ios_base::out | ios_base::trunc); + fout << data; + fout.close(); + } catch (...) { + return false; + } + return true; +} + +bool +UnionfsCstore::read_whole_file(const b_fs::path& fpath, string& data) +{ + /* must exist, be a regular file, and smaller than limit (we're going + * to read the whole thing). + */ + if (!b_fs::exists(fpath) || !b_fs::is_regular(fpath) + || b_fs::file_size(fpath) > MAX_FILE_READ_SIZE) { + return false; + } + stringbuf sbuf; + ifstream fin(fpath.file_string().c_str()); + fin >> &sbuf; + fin.close(); + /* note: if file contains just a newline => (eof() && fail()) + * so only checking bad() and eof() (we want whole file). + */ + if (fin.bad() || !fin.eof()) { + // read failed + return false; + } + data = sbuf.str(); + return true; +} + +/* return whether specified "commited marker" exists in the + * "committed marker file". + */ +bool +UnionfsCstore::committed_marker_exists(const string& marker) +{ + bool ret = false; + ifstream fin(C_COMMITTED_MARKER_FILE.c_str()); + while (!fin.eof() && !fin.bad() && !fin.fail()) { + string line; + getline(fin, line); + if (line == marker) { + ret = true; + break; + } + } + fin.close(); + return ret; +} + +/* recursively copy source directory to destination. + * will throw exception (from b_fs) if fail. + */ +void +UnionfsCstore::recursive_copy_dir(const b_fs::path& src, const b_fs::path& dst) +{ + string src_str = src.file_string(); + string dst_str = dst.file_string(); + b_fs::create_directory(dst); + + b_fs::recursive_directory_iterator di(src); + for (; di != b_fs::recursive_directory_iterator(); ++di) { + b_fs::path opath = di->path(); + string nname = opath.file_string(); + nname.replace(0, src_str.length(), dst_str); + b_fs::path npath = nname; + if (b_fs::is_directory(opath)) { + b_fs::create_directory(npath); + } else { + b_fs::copy_file(opath, npath); + } + } +} + diff --git a/src/cstore/unionfs/cstore-unionfs.hpp b/src/cstore/unionfs/cstore-unionfs.hpp new file mode 100644 index 0000000..357e307 --- /dev/null +++ b/src/cstore/unionfs/cstore-unionfs.hpp @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2010 Vyatta, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _CSTORE_UNIONFS_H_ +#define _CSTORE_UNIONFS_H_ +#include <vector> +#include <string> + +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <boost/filesystem.hpp> + +#include <cstore/cstore.hpp> + +extern "C" { +#include <cli_val.h> +#include <cli_val_engine.h> +} + +namespace b_fs = boost::filesystem; + +class UnionfsCstore : public Cstore { +public: + UnionfsCstore(bool use_edit_level = false); + UnionfsCstore(const string& session_id, string& env); + ~UnionfsCstore(); + + ////// public virtual functions declared in base class + bool markSessionUnsaved(); + bool unmarkSessionUnsaved(); + bool sessionUnsaved(); + bool sessionChanged(); + bool setupSession(); + bool teardownSession(); + bool inSession(); + +private: + // constants + static const string C_ENV_TMPL_ROOT; + static const string C_ENV_WORK_ROOT; + static const string C_ENV_ACTIVE_ROOT; + static const string C_ENV_CHANGE_ROOT; + static const string C_ENV_TMP_ROOT; + + static const string C_DEF_TMPL_ROOT; + static const string C_DEF_CFG_ROOT; + static const string C_DEF_ACTIVE_ROOT; + static const string C_DEF_CHANGE_PREFIX; + static const string C_DEF_WORK_PREFIX; + static const string C_DEF_TMP_PREFIX; + + static const string C_MARKER_DEF_VALUE; + static const string C_MARKER_DEACTIVATE; + static const string C_MARKER_CHANGED; + static const string C_MARKER_UNSAVED; + static const string C_COMMITTED_MARKER_FILE; + static const string C_COMMENT_FILE; + + static const size_t MAX_FILE_READ_SIZE = 8192; + + // root dirs (constant) + b_fs::path work_root; // working root (union) + b_fs::path active_root; // active root (readonly part of union) + b_fs::path change_root; // change root (r/w part of union) + b_fs::path tmp_root; // temp root + b_fs::path tmpl_root; // template root + + // path buffers + b_fs::path mutable_cfg_path; // mutable part of config path + b_fs::path tmpl_path; // whole template path + map<const void *, pair<b_fs::path, b_fs::path> > saved_paths; + // saved mutable part of cfg path and whole template path + + ////// virtual functions defined in base class + // begin path modifiers + void push_tmpl_path(const string& new_comp) { + push_path(tmpl_path, new_comp); + }; + void push_tmpl_path_tag() { + push_tmpl_path(TAG_NAME); + }; + string pop_tmpl_path() { + return pop_path(tmpl_path); + }; + void push_cfg_path(const string& new_comp) { + push_path(mutable_cfg_path, new_comp); + }; + void push_cfg_path_val() { + push_cfg_path(VAL_NAME); + }; + string pop_cfg_path() { + return pop_path(mutable_cfg_path); + }; + void append_cfg_path(const vector<string>& path_comps) { + for (unsigned int i = 0; i < path_comps.size(); i++) { + push_cfg_path(path_comps[i]); + } + }; + void reset_paths() { + tmpl_path = tmpl_root; + mutable_cfg_path = ""; + }; + void save_paths(const void *handle = NULL) { + pair<b_fs::path, b_fs::path> p; + p.first = mutable_cfg_path; + p.second = tmpl_path; + saved_paths[handle] = p; + }; + void restore_paths(const void *handle = NULL) { + map<const void *, pair<b_fs::path, b_fs::path> >::iterator it + = saved_paths.find(handle); + if (it == saved_paths.end()) { + exit_internal("restore_paths: handle not found\n"); + } + pair<b_fs::path, b_fs::path> p = saved_paths[handle]; + mutable_cfg_path = p.first; + tmpl_path = p.second; + }; + bool cfg_path_at_root() { + return (!mutable_cfg_path.has_parent_path()); + }; + bool tmpl_path_at_root() { + return (tmpl_path.file_string() == tmpl_root.file_string()); + }; + // end path modifiers + + // these operate on current tmpl path + bool tmpl_node_exists(); + bool tmpl_parse(vtw_def& def); + + // these operate on current work path + bool add_node(); + bool remove_node(); + void get_all_child_node_names_impl(vector<string>& cnodes, bool active_cfg); + void get_all_tmpl_child_node_names(vector<string>& cnodes) { + get_all_child_dir_names(tmpl_path, cnodes); + }; + bool write_value_vec(const vector<string>& vvec, bool active_cfg); + bool rename_child_node(const string& oname, const string& nname); + bool copy_child_node(const string& oname, const string& nname); + bool mark_display_default(); + bool unmark_display_default(); + bool mark_deactivated(); + bool unmark_deactivated(); + bool unmark_deactivated_descendants(); + bool mark_changed(); + bool remove_comment(); + bool set_comment(const string& comment); + bool discard_changes(unsigned long long& num_removed); + + // observers for work path + bool marked_changed(); + bool marked_display_default(); + + // observers for work path or active path + bool cfg_node_exists(bool active_cfg); + bool read_value_vec(vector<string>& vvec, bool active_cfg); + bool marked_deactivated(bool active_cfg); + bool get_comment(string& comment, bool active_cfg); + + // observers during commit operation + bool marked_committed(const vtw_def& def, bool is_set); + + // these operate on both current tmpl and work paths + bool validate_val_impl(vtw_def *def, char *value); + + // observers for "edit/tmpl levels" (for "edit"-related operations). + // note that these should be moved to base class in the future. + string get_edit_level_path() { + return cfg_path_to_str(); + }; + string get_tmpl_level_path() { + return tmpl_path_to_str(); + }; + void get_edit_level(vector<string>& path_comps); + bool edit_level_at_root() { + return cfg_path_at_root(); + }; + + // for testing/debugging + string cfg_path_to_str(); + string tmpl_path_to_str(); + + ////// private functions + b_fs::path get_work_path() { return (work_root / mutable_cfg_path); }; + b_fs::path get_active_path() { return (active_root / mutable_cfg_path); }; + b_fs::path get_change_path() { return (change_root / mutable_cfg_path); }; + void push_path(b_fs::path& old_path, const string& new_comp); + string pop_path(b_fs::path& path); + void get_all_child_dir_names(b_fs::path root, vector<string>& nodes); + bool write_file(const string& file, const string& data); + bool create_file(const string& file) { + return write_file(file, ""); + }; + bool read_whole_file(const b_fs::path& file, string& data); + bool committed_marker_exists(const string& marker); + void recursive_copy_dir(const b_fs::path& src, const b_fs::path& dst); +}; + +#endif /* _CSTORE_UNIONFS_H_ */ + diff --git a/src/delete.c b/src/delete.c deleted file mode 100644 index f2d3d31..0000000 --- a/src/delete.c +++ /dev/null @@ -1,391 +0,0 @@ -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <stdarg.h> -#include <sys/stat.h> -#include <dirent.h> - -#include "cli_val.h" -#include "cli_objects.h" - -static void remove_rf(boolean do_umount) -{ - char *command = NULL; - touch(); - if (do_umount) { - command = malloc(strlen(get_mdirp()) + 20); - sprintf(command, "sudo umount %s", get_mdirp()); - system(command); - } - - command = realloc(command, strlen(m_path.path) + 10); - sprintf(command, "rm -rf %s", m_path.path); - system(command); - - if (do_umount) { - command = realloc(command, strlen(get_mdirp()) + strlen(get_cdirp()) + - strlen(get_mdirp()) + 100); - sprintf(command, "sudo mount -t $UNIONFS -o dirs=%s=rw:%s=ro" - " $UNIONFS %s", get_cdirp(), get_adirp(), get_mdirp()); - system(command); - } - free(command); -} - -static boolean has_default(char **def, int size) -{ - char *buf_ptr; - FILE *fp = fopen(t_path.path, "r"); - - if (fp) { - char buf[1025]; - while (fgets(buf, 1024, fp)) { - if (strncmp(buf, "default:", 8) == 0) { - buf_ptr = index(buf,':'); - if (buf_ptr == NULL) { - break; - } - - //iterate up to non-whitespace character - buf_ptr++; - while (buf_ptr < (buf + size)) { - if (*buf_ptr == ' ') { - buf_ptr++; - } - else { - break; - } - } - - if (size < strlen(buf_ptr)-1) { - bye("default buffer size is too small\n"); - } - memcpy(*def, buf_ptr, strlen(buf_ptr)-1); - fclose(fp); - return 0; - } - } - fclose(fp); - } - - return 1; -} - -static void reset_default(const char *def_val) -{ - if (def_val == NULL) - return; - - //strip off quotes - char tmp_val[1025]; - char *ptr = index(def_val,'"'); - if (ptr != NULL) { - strcpy(tmp_val,ptr+1); - ptr = rindex(tmp_val,'"'); - if (ptr != NULL) { - *ptr = '\0'; - } - else { - strcpy(tmp_val,def_val); //go with original value. - } - } - else { - strcpy(tmp_val,def_val); - } - - char filename[strlen(m_path.path) + 10]; - touch(); - sprintf(filename, "%s/node.val", m_path.path); - - FILE *fp = fopen(filename, "w"); - if (fp == NULL) - bye("can not open: %s", filename); - fputs(tmp_val, fp); - fclose(fp); - - sprintf(filename, "%s/def", m_path.path); - touch_file(filename); -} -/*************************************************** - set_validate: - validate value against definition - return TRUE if OK, FALSE otherwise -****************************************************/ -boolean set_validate(vtw_def *defp, char *valp) -{ - boolean res; - int status; - struct stat statbuf; - - pop_path(&t_path); /* it was tag or real value */ - push_path(&t_path, DEF_NAME); - if (lstat(t_path.path, &statbuf) < 0 || - (statbuf.st_mode & S_IFMT) != S_IFREG) { - fprintf(out_stream, "The specified configuration node is not valid\n"); - bye("Can not set value (2), no definition for %s", m_path.path); - } - /* defniition present */ - if ((status = parse_def(defp, t_path.path, FALSE))) { - bye("parse error: %d\n", status); - } - res = validate_value(defp, valp); - pop_path(&t_path); - return res; -} - -int main(int argc, char **argv) -{ - int ai; - struct stat statbuf; - vtw_def def; - boolean last_tag=0; - int status; - FILE *fp; - boolean res; - char *cp, *delp, *endp; - boolean do_umount; - - /* this is needed before calling certain glib functions */ - g_type_init(); - - if (initialize_output("Delete") == -1) { - bye("can't initialize output\n"); - } - - if (argc < 2) { - fprintf(out_stream, "Need to specify the config node to delete\n"); - bye("nothing to delete\n"); - } - - dump_log( argc, argv); - do_umount = FALSE; - init_edit(); - /* extend both paths per arguments given */ - /* last argument is new value */ - for (ai = 1; ai < argc; ++ai) { - push_path(&t_path, argv[ai]); - push_path(&m_path, argv[ai]); - if (lstat(t_path.path, &statbuf) >= 0) { - if ((statbuf.st_mode & S_IFMT) != S_IFDIR) { - bye("INTERNAL:regular file %s in templates", t_path.path); - } - last_tag = FALSE; - continue; - } /*else */ - pop_path(&t_path); - push_path(&t_path, TAG_NAME); - if (lstat(t_path.path, &statbuf) >= 0) { - if ((statbuf.st_mode & S_IFMT) != S_IFDIR) { - bye("INTERNAL:regular file %s in templates", t_path.path); - } - last_tag = TRUE; - continue; - } - /* no match */ - break; - } - /* - cases: - multiple tag-value - not achild - mutilple tag-value - not the last child - multiple tag-value - last child - single value modified - signle value unmodified - multiple non-tag value - the last value - multiple non-tag value - not the last value - regular child - */ - if (ai == argc) { - /* full path found */ - /* all cases except multiple non-tag value */ - /* check for single value */ - if (last_tag) { - /* case of multiple tag-value - was this a real child? - was it the last child? - */ - struct dirent *dirp; - DIR *dp; - - if (lstat(m_path.path, &statbuf) < 0) { - fprintf(out_stream, "Nothing to delete\n"); - bye("Nothing to delete at %s", m_path.path); - } - - remove_rf(FALSE); - pop_path(&m_path); - if ((dp = opendir(m_path.path)) == NULL){ - INTERNAL; - } - while ((dirp = readdir(dp)) != NULL) { - /*do we have real child */ - if (strcmp(dirp->d_name, ".") && - strcmp(dirp->d_name, "..") && - strcmp(dirp->d_name, MOD_NAME) && /* XXX */ - strncmp(dirp->d_name, ".wh.", 4) ) - break; - } - if (dirp == NULL) { - /* no real children left */ - /* kill parent also */ - remove_rf(FALSE); - } - exit(0); - } - /*not tag */ - push_path(&t_path, DEF_NAME); - if (lstat(t_path.path, &statbuf) >= 0 && - (statbuf.st_mode & S_IFMT) == S_IFREG) { - /* defniition present */ - if ((status = parse_def(&def, t_path.path, FALSE))) { - bye("parse error: %d\n", status); - } - if (!def.tag && !def.multi && def.def_type!= ERROR_TYPE) { - /* signgle value */ - /* is it modified == - it is in C, but not OPAQUE */ - switch_path(CPATH); - if(lstat(m_path.path, &statbuf) >= 0) { - push_path(&m_path, OPQ_NAME); - if(lstat(m_path.path, &statbuf) < 0) { - /* yes remove from C only */ - pop_path(&m_path); - remove_rf(TRUE); - exit(0); - } - pop_path(&m_path); /*OPQ_NAME */ - } - switch_path(MPATH); - } - } - /* else no defnition, remove it also */ - char *def_val; - def_val = malloc(1025); - if (has_default(&def_val,1024) == 0) { - reset_default(def_val); - free(def_val); - } - else { - remove_rf(FALSE); - } - - // remove_rf(FALSE); - exit(0); - } - if(ai < argc -1 || last_tag) { - fprintf(out_stream, "The specified configuration node is not valid\n"); - bye("There is no appropriate template for %s", - m_path.path + strlen(get_mdirp())); - } - /*ai == argc -1, must be actual value */ - pop_path(&m_path); /*it was value, not path segment */ - push_path(&m_path, VAL_NAME); - /* set value */ - if (lstat(m_path.path, &statbuf) < 0) { - fprintf(out_stream, "Nothing to delete\n"); - bye("Nothing to delete at %s", m_path.path); - } - if ((statbuf.st_mode & S_IFMT) != S_IFREG) - bye("Not a regular file %s", m_path.path); - /* get definition to deal with potential multi */ - pop_path(&t_path); /* it was tag or real value */ - push_path(&t_path, DEF_NAME); - if (lstat(t_path.path, &statbuf) < 0 || - (statbuf.st_mode & S_IFMT) != S_IFREG) { - fprintf(out_stream, "The specified configuration node is not valid\n"); - bye("Can not delete value, no definition for %s", m_path.path); - } - /* defniition present */ - if ((status = parse_def(&def, t_path.path, FALSE))) { - bye("parse error: %d\n", status); - } - if (def.multi) { - /* delete from multivalue */ - valstruct new_value, old_value; - status = char2val(&def, argv[argc - 1], &new_value); - if (status) - exit(0); - cp = NULL; - pop_path(&m_path); /* get_value will push VAL_NAME */ - status = get_value(&cp, &m_path); - if (status != VTWERR_OK) - bye("Cannot read old value %s\n", m_path.path); - status = char2val(&def, cp, &old_value); - if (status != VTWERR_OK) - bye("Corrupted old value ---- \n%s\n-----\n", cp); - res = val_cmp(&new_value, &old_value, IN_COND); - if (!res) { - fprintf(out_stream, "%s is not a configured value, %s\n", new_value.val,old_value.val); - bye("Not in multivalue"); - } - touch(); - if (old_value.cnt) { - push_path(&m_path, VAL_NAME); - fp = fopen(m_path.path, "w"); - if (fp == NULL) - bye("Can not open value file %s", m_path.path); - if (is_in_cond_tik()) { - for(delp=cp;delp && is_in_cond_tik(); dec_in_cond_tik()) { - delp = strchr(delp, '\n'); - if (!delp) - INTERNAL; - ++delp; /* over \n */ - } - /* write "left" of deleted */ - fwrite(cp, 1, delp-cp, fp); - }else - delp = cp; - /* find end of value */ - endp = strchr(delp, '\n'); - if (endp && *++endp) { - /* write "right" of deleted */ - fwrite(endp, 1, strlen(endp), fp); - /* need the final '\n' */ - fwrite("\n", 1, 1, fp); - } - fclose(fp); - return 0; - } - /* it multi with only 1 value, remove */ - remove_rf(FALSE); - return 0; - } - - /* - let's do a new check here: - -> if this is a leaf and there is a value look for a match of the value - -> make sure to check existing configuration as well as uncommitted config - */ - if (ai+1 == argc) { - //does this work up until the last value - pop_path(&m_path); - if(lstat(m_path.path, &statbuf) == 0) { - //now compare last value with that in the node.def file to determine whether to accept this delete - status = get_value(&cp, &m_path); - if (status != VTWERR_OK) { - bye("Cannot read old value %s\n", m_path.path); - } - if (!strcmp(cp,argv[argc - 1])) { - /* Also need to handle the case where the value is not specified. */ - char *def_val; - def_val = malloc(1025); - if (has_default(&def_val,1024) == 0) { - reset_default(def_val); - free(def_val); - } - else { - remove_rf(FALSE); - } - return 0; - } - } - } - - - fprintf(out_stream, "The specified configuration node is not valid\n"); - bye("There is no appropriate template for %s", - m_path.path + strlen(get_mdirp())); - - return 1; -} - diff --git a/src/set.c b/src/set.c deleted file mode 100644 index fb2d613..0000000 --- a/src/set.c +++ /dev/null @@ -1,498 +0,0 @@ -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <dirent.h> - -#include "cli_val.h" -#include "cli_objects.h" -#include "cli_path_utils.h" - -static void handle_defaults(void); - -static void make_dir() -{ - mkdir_p(m_path.path); -} -/*************************************************** - set_validate: - validate value against definition - return TRUE if OK, FALSE otherwise -****************************************************/ -boolean set_validate(vtw_def *defp, char *valp, boolean empty_val) -{ - boolean res; - int status; - struct stat statbuf; - char* path_end=NULL; - - if (!empty_val) { - { - clind_path_ref tp = clind_path_construct(t_path.path); - if(tp) { - path_end=clind_path_pop_string(tp); - } - clind_path_destruct(&tp); - } - - if (strcmp(path_end, TAG_NAME) == 0) { - /* it was a tag, so the def is at 1 level up */ - pop_path(&t_path); - } else { - /* it's actual value, so the tmpl path is fine */ - free(path_end); - path_end = NULL; - } - } - push_path(&t_path, DEF_NAME); - if (lstat(t_path.path, &statbuf) < 0 || - (statbuf.st_mode & S_IFMT) != S_IFREG) { - fprintf(out_stream, "The specified configuration node is not valid\n"); - bye("Can not set value (4), no definition for %s, template %s", - m_path.path,t_path.path); - } - /* defniition present */ - if ((status = parse_def(defp, t_path.path, FALSE))) { - bye("parse error: %d\n", status); - } - pop_path(&t_path); - if(path_end) { - push_path(&t_path,path_end); - free(path_end); - path_end=NULL; - } - if (defp->def_type == ERROR_TYPE) { - /* no type in def. setting value not allowed. */ - fprintf(out_stream, "The specified configuration node is not valid\n"); - bye("Can not set value: no type for %s, template %s", - m_path.path, t_path.path); - } - if (empty_val) { - if (defp->def_type != TEXT_TYPE || defp->tag || defp->multi){ - printf("Empty string may be assigned only to TEXT type leaf node\n"); - fprintf(out_stream, "Empty value is not allowed\n"); - return FALSE; - } - return TRUE; - } - - //apply limit count here, needs to be defined as a tag,multi and have a value set - - //NOTE: changed behavior for def_tag==1. Needs signed 32 support in parser where -1 - //represents embedded tag node... TODO - if ((defp->tag || defp->multi) && (defp->def_tag != 0 || defp->def_multi != 0)) { - //get count of siblings - - char path[2048]; - char val[2048]; - char last_val[2048]; - char *pos = rindex(m_path.path,'/'); - if (pos != NULL) { - int offset = pos - m_path.path; - strncpy(path,m_path.path,offset); - path[offset] = '\0'; - - strncpy(val,m_path.path + offset + 1, strlen(m_path.path) - offset - 1); - val[strlen(m_path.path) - offset - 1] = '\0'; - - // fprintf(out_stream,"val: %s, offset: %d, path: %s\n",val,offset,m_path.path); - - int entity_count = 0; - if (defp->def_tag) { - struct dirent* entry; - DIR* dirp = opendir(path); /* There should be error handling after this */ - if (dirp != NULL) { - while ((entry = readdir(dirp)) != NULL) { - if (strcmp(entry->d_name,".") != 0 && - strcmp(entry->d_name,"..") != 0 && - strcmp(val,entry->d_name) != 0) { - strcpy(last_val,entry->d_name); - entity_count++; - } - } - closedir(dirp); - } - } - else if (defp->def_multi) { - //fopen file and count number of lines - char buf[4096]; - sprintf(buf,"%s/node.val",m_path.path); - FILE *fp = fopen(buf,"r"); - if (fp) { - char *line = NULL; - size_t len = 0; - while (getline(&line,&len,fp) != -1) { - ++entity_count; - } - if (line) { - free(line); - } - fclose(fp); - } - } - - if (defp->tag && entity_count == 1 && defp->def_tag < 0) { - //this is the special case, where the previous value should be deleted here... - char command[8192]; - //let unionfs handle the diff - sprintf(command, "mv %s/%s %s/%s", path,last_val,path,val); - system(command); - } - - if (defp->tag) { - if (defp->def_tag > 0 && entity_count >= defp->def_tag) { - char *p = rindex(path,'/'); - char tmp[2048]; - if (p != NULL) { - int off = p - path; - strncpy(tmp,path + off + 1, strlen(path) - off - 1); - tmp[strlen(path) - off - 1] = '\0'; - fprintf(out_stream,"Number of values exceeded for '%s', allowed: %d, actual: %d\n",tmp,defp->def_tag,entity_count); - } - else { - fprintf(out_stream,"Number of values exceeded, allowed: %d, actual: %d\n",defp->def_tag,entity_count); - } - return FALSE; - } - } - else { - if (defp->def_multi > 0 && entity_count >= defp->def_multi) { - fprintf(out_stream,"Number of values exceeded, allowed: %d, actual: %d\n",defp->def_multi,entity_count); - return FALSE; - } - } - } - } - - res = validate_value(defp, valp); - return res; -} - -int main(int argc, char **argv) -{ - - int ai; - struct stat statbuf; - vtw_def def; - boolean last_tag; - int status; - FILE *fp; - boolean res; - char *cp; - boolean need_mod = FALSE, not_new = FALSE; - boolean empty_val = FALSE; - - /* this is needed before calling certain glib functions */ - g_type_init(); - - if (initialize_output("Set") == -1) { - bye("can't initialize output\n"); - } - - dump_log( argc, argv); - init_edit(); - last_tag = FALSE; - - /* extend both paths per arguments given */ - /* last argument is new value */ - for (ai = 1; ai < argc; ++ai) { - if (!*argv[ai]) { /* empty string */ - if (ai < argc -1) { - fprintf(out_stream, "Empty value is not allowed after \"%s\"\n", - argv[ai - 1]); - bye("empty string in argument list \n"); - } - empty_val = TRUE; - last_tag = FALSE; - break; - } - push_path(&t_path, argv[ai]); - push_path(&m_path, argv[ai]); - if (lstat(t_path.path, &statbuf) >= 0) { - if ((statbuf.st_mode & S_IFMT) != S_IFDIR) { - bye("INTERNAL:regular file %s in templates", t_path.path); - } - last_tag = FALSE; - continue; - } /*else */ - pop_path(&t_path); - push_path(&t_path, TAG_NAME); - if (lstat(t_path.path, &statbuf) >= 0) { - if ((statbuf.st_mode & S_IFMT) != S_IFDIR) { - bye("INTERNAL:regular file %s in templates", t_path.path); - } - last_tag = TRUE; - /* every time tag match, need to verify*/ - if(!set_validate(&def, argv[ai], FALSE)) { - bye("value \"%s\" is not valid\n", argv[ai]); - } - continue; - } - /* no match */ - break; - } - - if (ai == argc) { - /* full path found */ - /* every tag match validated already */ - /* non tag matches are OK by definition */ - /* do we already have it? */ - if (lstat(m_path.path, &statbuf) >= 0) { - printf("Node [%s] already exists\n", m_path.path + strlen(get_mdirp())); - /* not an error */ - exit(0); - } - /* else */ - /* prevent value node without actual value */ - push_path(&t_path, DEF_NAME); - if (lstat(t_path.path, &statbuf) >= 0) { - if ((status = parse_def(&def, t_path.path, FALSE))) { - bye("parse error: %d\n", status); - } - if (def.def_type != ERROR_TYPE && !def.tag) { - fprintf(out_stream, - "The specified configuration node requires a value\n"); - bye("Must provide actual value\n"); - } - if (def.def_type == ERROR_TYPE && !def.tag) { - pop_path(&t_path); - if(!validate_value(&def, "")) { - bye("validate_value failed\n"); - } - push_path(&t_path, DEF_NAME); - } - } - touch(); - pop_path(&t_path); - make_dir(); - handle_defaults(); - exit(0); - } - if(ai < argc -1 || last_tag) { - fprintf(out_stream, "The specified configuration node is not valid\n"); - bye("There is no appropriate template for %s", - m_path.path + strlen(get_mdirp())); - } - /*ai == argc -1, must be actual value */ - if (!empty_val) { - pop_path(&m_path); /* pop the actual value at the end */ - pop_path(&t_path); /* pop the "node.tag" */ - } - handle_defaults(); - - if(!set_validate(&def, argv[argc-1], empty_val)) { - bye("value \"%s\" is not valid\n", argv[argc - 1]); - } - push_path(&m_path, VAL_NAME); - /* set value */ - if (lstat(m_path.path, &statbuf) >= 0) { - valstruct new_value, old_value; - not_new = TRUE; - if ((statbuf.st_mode & S_IFMT) != S_IFREG) - bye("Not a regular file at path \"%s\"", m_path.path); - /* check if this new value */ - status = char2val(&def, argv[argc - 1], &new_value); - if (status) - exit(0); - cp = NULL; - pop_path(&m_path); /* get_value will push VAL_NAME */ - status = get_value(&cp, &m_path); - if (status != VTWERR_OK) - bye("Cannot read old value %s\n", m_path.path); - status = char2val(&def, cp, &old_value); - if (status != VTWERR_OK) - bye("Corrupted old value ---- \n%s\n-----\n", cp); - res = val_cmp(&new_value, &old_value, IN_COND); - if (res) { - if (def.multi) { - printf("Already in multivalue\n"); - } else { - printf("The same value \"%s\" for path \"%s\"\n", cp, m_path.path); - } - /* not treating as error */ - exit(0); - } - } else { - pop_path(&m_path); - } - make_dir(); - - - if (!def.multi) { - char path[strlen(m_path.path)+5]; - sprintf(path, "%s/def",m_path.path); - unlink(path); - } - - push_path(&m_path, VAL_NAME); - if(not_new && !def.multi) { - /* it is not multi and seen from M */ - /* is it in C */ - switch_path(CPATH); - if (lstat(m_path.path, &statbuf) < 0) - /* yes, we are modifying original value */ - need_mod = TRUE; - switch_path(MPATH); - } - touch(); - /* in case of multi we always append, never overwrite */ - /* in case of single we always overwrite */ - /* append and overwrite work the same for new file */ - fp = fopen(m_path.path, def.multi?"a":"w"); - if (fp == NULL) - bye("Can not open value file %s", m_path.path); - if (fputs(argv[argc-1], fp) < 0 || fputc('\n',fp) < 0) - bye("Error writing file %s", m_path.path); - if (need_mod) { - pop_path(&m_path); /* get rid of "value" */ - char filename[strlen(m_path.path) + sizeof(MOD_NAME)+1]; - sprintf(filename, "%s/" MOD_NAME, m_path.path); - touch_file(filename); - } - return 0; -} - -static char * -last_path_segment(const char *path) -{ - char *tmp = strrchr(path, '/'); - return ((tmp) ? (tmp + 1) : tmp); -} - -/* handle_default(mpath, tpath, exclude) - * create any nodes with default values at the current level. - * mpath: working path - * tpath: template path - * exclude: path to exclude - */ -static void -handle_default(vtw_path *mpath, vtw_path *tpath, char *exclude) -{ - DIR *dp; - struct dirent *dirp; - char *uename = NULL; - struct stat statbuf; - vtw_def def; - int status; - FILE *fp; - - if ((dp = opendir(tpath->path)) == NULL) { - perror("handle_default: opendir"); - bye("opendir failed\n"); - } - - while ((dirp = readdir(dp)) != NULL) { - if (strcmp(dirp->d_name, ".") == 0 - || strcmp(dirp->d_name, "..") == 0 - || strcmp(dirp->d_name, MOD_NAME) == 0 - || strcmp(dirp->d_name, DEF_NAME) == 0 - || strcmp(dirp->d_name, TAG_NAME) == 0 - || strcmp(dirp->d_name, exclude) == 0) { - continue; - } - if (uename) { - free(uename); - uename = NULL; - } - uename = clind_unescape(dirp->d_name); - push_path(tpath, uename); - if (lstat(tpath->path, &statbuf) < 0) { - fprintf(stderr, "no template directory [%s]\n", tpath->path); - pop_path(tpath); - continue; - } - if ((statbuf.st_mode & S_IFMT) != S_IFDIR) { - fprintf(stderr, "non-directory [%s]\n", tpath->path); - pop_path(tpath); - continue; - } - push_path(tpath, DEF_NAME); - if (lstat(tpath->path, &statbuf) < 0) { - /* no definition */ - pop_path(tpath); /* definition */ - pop_path(tpath); /* child */ - continue; - } - if ((status = parse_def(&def, tpath->path, FALSE))) { - /* template parse error. abort. */ - bye("Parse error in [%s]\n", tpath->path); - } - if (def.def_default) { - push_path(mpath, uename); - - push_path(mpath, VAL_NAME); - if (lstat(mpath->path, &statbuf) < 0) { - /* no value. add the default */ - pop_path(mpath); - touch_dir(mpath->path); /* make sure directory exist */ - - //create def marker - char def_file[strlen(mpath->path)+8]; - sprintf(def_file,"%s/def",mpath->path); - fp = fopen(def_file, "w"); - if (fp == NULL) - bye("Can not open def file %s", def_file); - fputs("empty\n", fp); - fclose(fp); - - push_path(mpath, VAL_NAME); - fp = fopen(mpath->path, "w"); - if (fp == NULL) { - bye("Can not open value file %s", mpath->path); - } - if (fputs(def.def_default, fp) < 0 - || fputc('\n',fp) < 0) { - bye("Error writing file %s", mpath->path); - } - fclose(fp); - - } - pop_path(mpath); /* value */ - pop_path(mpath); /* child */ - } - free_def(&def); - pop_path(tpath); /* definition */ - pop_path(tpath); /* child */ - } - if (uename) { - free(uename); - } - closedir(dp); -} - -/* handle_defaults() - * create any nodes with default values along the current "global" - * configuration/template path (m_path/t_path). - */ -static void -handle_defaults() -{ - vtw_path mpath; - vtw_path tpath; - char *path_end = strdup(""); - - memset(&mpath, 0, sizeof(mpath)); - memset(&tpath, 0, sizeof(tpath)); - copy_path(&mpath, &m_path); - copy_path(&tpath, &t_path); - - while (mpath.path_lev > 0) { - handle_default(&mpath, &tpath, path_end); - - if (mpath.path_lev == 1) { - break; - } - - free(path_end); - path_end = strdup(last_path_segment(tpath.path)); - pop_path(&mpath); - pop_path(&tpath); - } - - free(path_end); - free_path(&mpath); - free_path(&tpath); -} - |