summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAn-Cheng Huang <ancheng@vyatta.com>2010-07-28 14:30:32 -0700
committerAn-Cheng Huang <ancheng@vyatta.com>2010-07-28 14:30:32 -0700
commit639c835bc2730a4fbffd915f5b2028a68375ee7a (patch)
tree203d61e1d5e8ef422d6aba3851d2f83a1f838b6b
parent0247864ef578ac05bbac8dc5175e674ce7b82714 (diff)
downloadvyatta-cfg-639c835bc2730a4fbffd915f5b2028a68375ee7a.tar.gz
vyatta-cfg-639c835bc2730a4fbffd915f5b2028a68375ee7a.zip
add new cstore library
-rw-r--r--.gitignore4
-rw-r--r--Makefile.am58
-rw-r--r--configure.ac2
-rwxr-xr-xdebian/autogen.sh11
-rw-r--r--debian/control20
-rw-r--r--debian/libvyatta-cfg-dev.install3
-rw-r--r--debian/linda1
-rw-r--r--debian/lintian6
-rwxr-xr-xdebian/rules125
-rw-r--r--debian/vyatta-cfg.install3
-rw-r--r--debian/vyatta-cfg.lintian-overrides5
-rw-r--r--debian/vyatta-cfg.postinst.in25
-rw-r--r--debian/vyatta-cfg.postrm.in4
-rwxr-xr-xetc/bash_completion.d/20vyatta-cfg1108
-rw-r--r--etc/default/vyatta-cfg11
-rwxr-xr-xlib/Vyatta/Config.pm1309
-rwxr-xr-xlib/Vyatta/ConfigDOMTree.pm366
-rwxr-xr-xlib/Vyatta/ConfigLoad.pm23
-rwxr-xr-xlib/Vyatta/ConfigOutput.pm230
-rw-r--r--perl_dmod/.gitignore2
-rw-r--r--perl_dmod/Cstore/.gitignore5
-rw-r--r--perl_dmod/Cstore/Changes6
-rw-r--r--perl_dmod/Cstore/Cstore.xs299
-rw-r--r--perl_dmod/Cstore/MANIFEST7
-rw-r--r--perl_dmod/Cstore/Makefile.PL88
-rw-r--r--perl_dmod/Cstore/README33
-rw-r--r--perl_dmod/Cstore/lib/Cstore.pm96
-rw-r--r--perl_dmod/Cstore/t/Cstore.t15
-rw-r--r--perl_dmod/Cstore/typemap68
-rw-r--r--perl_dmod/Makefile.am25
-rwxr-xr-xscripts/vyatta-activate-config.pl163
-rwxr-xr-xscripts/vyatta-cfg-cmd-wrapper216
-rwxr-xr-xscripts/vyatta-comment-config.pl92
-rwxr-xr-xscripts/vyatta-load-config.pl38
-rw-r--r--src/cli_bin.cpp237
-rw-r--r--src/cli_def.l7
-rw-r--r--src/cli_new.c343
-rw-r--r--src/cli_parse.y47
-rw-r--r--src/cli_shell_api.cpp316
-rw-r--r--src/cli_val.h19
-rw-r--r--src/cli_val_engine.c112
-rw-r--r--src/cli_val_engine.h7
-rw-r--r--src/cstore/cstore-c.cpp159
-rw-r--r--src/cstore/cstore-c.h55
-rw-r--r--src/cstore/cstore-varref.cpp288
-rw-r--r--src/cstore/cstore-varref.hpp48
-rw-r--r--src/cstore/cstore.cpp2496
-rw-r--r--src/cstore/cstore.hpp409
-rw-r--r--src/cstore/unionfs/cstore-unionfs.cpp1078
-rw-r--r--src/cstore/unionfs/cstore-unionfs.hpp217
-rw-r--r--src/delete.c391
-rw-r--r--src/set.c498
52 files changed, 7331 insertions, 3863 deletions
diff --git a/.gitignore b/.gitignore
index 306fe77..4ff1d49 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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);
-}
-