diff --git a/.gitignore b/.gitignore index 8c84a2303..a7667162b 100644 --- a/.gitignore +++ b/.gitignore @@ -24,45 +24,63 @@ libtool lxc.spec lxc.pc -templates/lxc-debian -templates/lxc-lucid -templates/lxc-maverick -templates/lxc-natty -templates/lxc-oneiric -templates/lxc-fedora templates/lxc-altlinux -templates/lxc-sshd -templates/lxc-busybox templates/lxc-archlinux +templates/lxc-busybox +templates/lxc-debian +templates/lxc-fedora +templates/lxc-lenny +templates/lxc-opensuse +templates/lxc-oracle +templates/lxc-sshd +templates/lxc-ubuntu +templates/lxc-ubuntu-cloud + src/lxc/lxc-attach src/lxc/lxc-cgroup src/lxc/lxc-checkconfig src/lxc/lxc-checkpoint -src/lxc/lxc-cinit -src/lxc/lxc-cmd +src/lxc/lxc-clone src/lxc/lxc-console src/lxc/lxc-create src/lxc/lxc-destroy -src/lxc/lxc-enter -src/lxc/lxc-exec src/lxc/lxc-execute src/lxc/lxc-freeze src/lxc/lxc-info src/lxc/lxc-init src/lxc/lxc-kill -src/lxc/lxc-ls src/lxc/lxc-monitor src/lxc/lxc-netstat -src/lxc/lxc-setcap src/lxc/lxc-ps src/lxc/lxc-restart +src/lxc/lxc-setcap +src/lxc/lxc-setuid +src/lxc/lxc-shutdown src/lxc/lxc-start +src/lxc/lxc-start-ephemeral src/lxc/lxc-stop src/lxc/lxc-unfreeze src/lxc/lxc-unshare src/lxc/lxc-version src/lxc/lxc-wait +src/lxc/legacy/lxc-ls + +src/python-lxc/build/ +src/python-lxc/examples/api_test.py +src/python-lxc/lxc/__init__.py +src/python-lxc/lxc/__pycache__/ + +src/tests/lxc-test-containertests +src/tests/lxc-test-createtest +src/tests/lxc-test-destroytest +src/tests/lxc-test-get_item +src/tests/lxc-test-getkeys +src/tests/lxc-test-locktests +src/tests/lxc-test-saveconfig +src/tests/lxc-test-shutdowntest +src/tests/lxc-test-startone + config/compile config/config.guess diff --git a/CONTRIBUTING b/CONTRIBUTING index 10c66b1fc..f6447c12c 100644 --- a/CONTRIBUTING +++ b/CONTRIBUTING @@ -1,5 +1,5 @@ - Contributing to this project + Contributing to this project ---------------------------- @@ -81,7 +81,6 @@ By making a contribution to this project, I certify that: then you just add a line saying - Signed-off-by: Random J Developer + Signed-off-by: Random J Developer -using your real name (sorry, no pseudonyms or anonymous -contributions.) \ No newline at end of file +using your real name (sorry, no pseudonyms or anonymous contributions.) diff --git a/COPYING b/COPYING index 5ab7695ab..4362b4915 100644 --- a/COPYING +++ b/COPYING @@ -1,5 +1,5 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA @@ -10,7 +10,7 @@ as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] - Preamble + Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public @@ -112,7 +112,7 @@ modification follow. Pay close attention to the difference between a former contains code derived from the library, whereas the latter must be combined with the library in order to run. - GNU LESSER GENERAL PUBLIC LICENSE + GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other @@ -146,7 +146,7 @@ such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. - + 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an @@ -432,7 +432,7 @@ decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. - NO WARRANTY + NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. @@ -455,7 +455,7 @@ FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - END OF TERMS AND CONDITIONS + END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries @@ -500,5 +500,3 @@ necessary. Here is a sample; alter the names: Ty Coon, President of Vice That's all there is to it! - - diff --git a/INSTALL b/INSTALL index 4f3847c37..aad607199 100644 --- a/INSTALL +++ b/INSTALL @@ -10,8 +10,8 @@ unlimited permission to copy, distribute and modify it. Basic Installation ================== -Briefly, the shell commands `./configure; make; make install' should -configure, build, and install this package. The following +Briefly, the shell commands `./autogen.sh; ./configure; make; make install' +should configure, build, and install this package. The following more-detailed instructions are generic; see the `README' file for instructions specific to this package. diff --git a/Makefile.am b/Makefile.am index 05a03f499..7b3232646 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,13 +2,17 @@ ACLOCAL_AMFLAGS = -I config -SUBDIRS = src templates doc +SUBDIRS = config src templates doc DIST_SUBDIRS = config src templates doc EXTRA_DIST = autogen.sh lxc.spec CONTRIBUTING MAINTAINERS ChangeLog pcdatadir = $(libdir)/pkgconfig pcdata_DATA = lxc.pc +install-data-local: + $(MKDIR_P) $(DESTDIR)$(LXCPATH) + $(MKDIR_P) $(DESTDIR)$(localstatedir)/cache/lxc + ChangeLog:: @touch ChangeLog diff --git a/README b/README index 74fa6ff44..cedb50d50 100644 --- a/README +++ b/README @@ -7,7 +7,7 @@ What is lxc: kernel. It provides the resource management through the control groups aka process containers and resource isolation through the namespaces. - The linux containers, lxc, aims to use these new functionnalities to pro- + The linux containers, lxc, aims to use these new functionalities to pro- vide an userspace container object which provides full resource isolation and resource control for an applications or a system. @@ -29,9 +29,14 @@ Downloading the current source code: You can browse the up to the minute source code and change history online. http://lxc.git.sourceforge.net + For an even more bleeding edge experience, you may want to look at the + staging branch where all changes aimed at the next release land before + getting pulled into the master branch. + http://github.com/lxc/lxc + For detailed build instruction refer to INSTALL and man lxc man page but a short command line should work: - ./configure && make && sudo make install && sudo lxc-setcap + ./autogen.sh && ./configure && make && sudo make install && sudo lxc-setcap preceded by ./autogen.sh if configure do not exist yet. Getting help: @@ -48,7 +53,33 @@ Portability: lxc is developed and tested on Linux since kernel mainline version 2.6.27 (without network) and 2.6.29 with network isolation. - is compiled with gcc, and supports i686, x86_64, ppc, ppc64, S390 archi. + It's compiled with gcc, and should work on most architectures as long as the + required kernel features are available. This includes (but isn't limited to): + i686, x86_64, ppc, ppc64, S390, armel and armhf. AUTHOR Daniel Lezcano + +Seccomp with LXC +---------------- + +To restrict a container with seccomp, you must specify a profile which is +basically a whitelist of system calls it may execute. In the container +config file, add a line like + +lxc.seccomp = /var/lib/lxc/q1/seccomp.full + +I created a usable (but basically worthless) seccomp.full file using + +cat > seccomp.full << EOF +1 +whitelist +EOF +for i in `seq 0 300`; do + echo $i >> seccomp.full +done +for i in `seq 1024 1079`; do + echo $i >> seccomp.full +done + + -- Serge Hallyn Fri, 27 Jul 2012 15:47:02 +0600 diff --git a/TODO b/TODO index 3b6e95638..56d505698 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,6 @@ * create an interactive configuration with the lxc-create command - line, like the 'make menuconfig' of the kernel. + line, like the 'make menuconfig' of the kernel. = lxc-create [-n foo] -m|--menuconfig diff --git a/config/Makefile.am b/config/Makefile.am index 783ba738d..1611b7d77 100644 --- a/config/Makefile.am +++ b/config/Makefile.am @@ -1,2 +1,15 @@ -distclean: +configdir = $(sysconfdir)/lxc +config_DATA = lxc.conf +conffile = @LXC_CONFFILE@ + +EXTRA_DIST = lxc.conf.ubuntu lxc.conf.libvirt lxc.conf.unknown + +lxc.conf: + cp $(conffile) $@ + +clean-local: + @$(RM) -f lxc.conf + +distclean-local: + @$(RM) -f lxc.conf @$(RM) -f compile config.guess config.sub depcomp install-sh ltmain.sh missing Makefile.in Makefile diff --git a/config/acinclude.m4 b/config/acinclude.m4 index a36fdf3cc..d718b5e76 100644 --- a/config/acinclude.m4 +++ b/config/acinclude.m4 @@ -2,21 +2,21 @@ dnl as-ac-expand.m4 0.2.0 dnl autostars m4 macro for expanding directories using configure's prefix dnl thomas@apestaart.org dnl - + dnl AS_AC_EXPAND(VAR, CONFIGURE_VAR) dnl example dnl AS_AC_EXPAND(SYSCONFDIR, $sysconfdir) dnl will set SYSCONFDIR to /usr/local/etc if prefix=/usr/local - + AC_DEFUN([AS_AC_EXPAND], [ EXP_VAR=[$1] FROM_VAR=[$2] - + dnl first expand prefix and exec_prefix if necessary prefix_save=$prefix exec_prefix_save=$exec_prefix - + dnl if no prefix given, then use /usr/local, the default prefix if test "x$prefix" = "xNONE"; then prefix="$ac_default_prefix" @@ -25,7 +25,7 @@ AC_DEFUN([AS_AC_EXPAND], if test "x$exec_prefix" = "xNONE"; then exec_prefix=$prefix fi - + full_var="$FROM_VAR" dnl loop until it doesn't change anymore while true; do @@ -33,11 +33,11 @@ AC_DEFUN([AS_AC_EXPAND], if test "x$new_full_var" = "x$full_var"; then break; fi full_var=$new_full_var done - + dnl clean up full_var=$new_full_var AC_SUBST([$1], "$full_var") - + dnl restore prefix and exec_prefix prefix=$prefix_save exec_prefix=$exec_prefix_save @@ -94,7 +94,7 @@ x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"` # Determine the number of characters in A and B. ax_compare_version_len_A=`echo "$A" | awk '{print(length)}'` ax_compare_version_len_B=`echo "$B" | awk '{print(length)}'` - + # Set A to no more than B's length and B to no more than A's length. A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"` B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"` diff --git a/config/lxc.conf.libvirt b/config/lxc.conf.libvirt new file mode 100644 index 000000000..6950dca9d --- /dev/null +++ b/config/lxc.conf.libvirt @@ -0,0 +1,3 @@ +lxc.network.type = veth +lxc.network.link = virbr0 +lxc.network.flags = up diff --git a/config/lxc.conf.ubuntu b/config/lxc.conf.ubuntu new file mode 100644 index 000000000..0a5ac711f --- /dev/null +++ b/config/lxc.conf.ubuntu @@ -0,0 +1,3 @@ +lxc.network.type = veth +lxc.network.link = lxcbr0 +lxc.network.flags = up diff --git a/config/lxc.conf.unknown b/config/lxc.conf.unknown new file mode 100644 index 000000000..6c880103f --- /dev/null +++ b/config/lxc.conf.unknown @@ -0,0 +1 @@ +lxc.network.type = empty diff --git a/configure.ac b/configure.ac index 900e1e799..bdfcacf39 100644 --- a/configure.ac +++ b/configure.ac @@ -6,40 +6,119 @@ AC_INIT([lxc], [0.8.0]) AC_CONFIG_SRCDIR([configure.ac]) AC_CONFIG_AUX_DIR([config]) AM_CONFIG_HEADER([src/config.h]) -AM_INIT_AUTOMAKE([-Wno-portability]) +AM_INIT_AUTOMAKE([-Wall -Werror -Wno-portability]) AC_CANONICAL_HOST AM_PROG_CC_C_O AC_GNU_SOURCE AC_CHECK_PROG(SETCAP, setcap, yes, no, $PATH$PATH_SEPARATOR/sbin) +AC_MSG_CHECKING([host distribution]) +AC_ARG_WITH(distro, AS_HELP_STRING([--with-distro=DISTRO], [Specify the Linux distribution to target: One of redhat, oracle, fedora, suse, gentoo, debian, arch, slackware, paldo, mandriva or pardus])) +if test "z$with_distro" = "z"; then + with_distro=`lsb_release -is` +fi +if test "z$with_distro" = "z"; then + AC_CHECK_FILE(/etc/redhat-release,with_distro="redhat") + AC_CHECK_FILE(/etc/oracle-release,with_distro="oracle") + AC_CHECK_FILE(/etc/fedora-release,with_distro="fedora") + AC_CHECK_FILE(/etc/SuSE-release,with_distro="suse") + AC_CHECK_FILE(/etc/gentoo-release,with_distro="gentoo") + AC_CHECK_FILE(/etc/debian_version,with_distro="debian") + AC_CHECK_FILE(/etc/arch-release,with_distro="arch") + AC_CHECK_FILE(/etc/slackware-version,with_distro="slackware") + AC_CHECK_FILE(/etc/frugalware-release,with_distro="frugalware") + AC_CHECK_FILE(/etc/mandrakelinux-release, with_distro="mandriva") + AC_CHECK_FILE(/etc/mandriva-release,with_distro="mandriva") + AC_CHECK_FILE(/etc/pardus-release,with_distro="pardus") +fi +with_distro=`echo ${with_distro} | tr '[[:upper:]]' '[[:lower:]]'` + +if test "z$with_distro" = "z"; then + with_distro="unknown" +fi +case $with_distro in + ubuntu) + conffile=lxc.conf.ubuntu + ;; + redhat|fedora|oracle|oracleserver) + conffile=lxc.conf.libvirt + ;; + *) + echo -n "Linux distribution network config unknown, defaulting to lxc.network.type = empty" + conffile=lxc.conf.unknown + ;; +esac +AC_MSG_RESULT([$with_distro]) + +AM_CONDITIONAL([HAVE_DEBIAN], [test x"$with_distro" = "xdebian" -o x"$with_distro" = "xubuntu"]) + AC_ARG_ENABLE([rpath], [AC_HELP_STRING([--disable-rpath], [do not set rpath in executables])], [], [enable_rpath=yes]) AM_CONDITIONAL([ENABLE_RPATH], [test "x$enable_rpath" = "xyes"]) -AC_ARG_ENABLE([apparmor], - [AC_HELP_STRING([--enable-apparmor], [enable apparmor])], - [], [enable_apparmor=yes]) -AM_CONDITIONAL([ENABLE_APPARMOR], [test "x$enable_apparmor" = "xyes"]) - AC_ARG_ENABLE([doc], - [AC_HELP_STRING([--enable-doc], [make mans (require docbook2man installed) [default=auto]])], + [AC_HELP_STRING([--enable-doc], [make mans (require docbook2x-man installed) [default=auto]])], [], [enable_doc=auto]) if test "x$enable_doc" = "xyes" -o "x$enable_doc" = "xauto"; then - AC_CHECK_PROG(have_docbook, [docbook2man], [yes], [no]) + db2xman="" - test "x$have_docbook" = "xno" -a "x$enable_doc" = "xyes" && \ - AC_MSG_ERROR([docbook2man required by man request, but not found]) + AC_MSG_CHECKING(for docbook2x-man) + for name in docbook2x-man db2x_docbook2man; do + if "$name" --help >/dev/null 2>&1; then + db2xman="$name" + break; + fi + done + + if test -n "${db2xman}"; then + AC_MSG_RESULT(${db2xman}) + else + AC_MSG_RESULT(no) + if test "x$enable_doc" = "xyes"; then + AC_MSG_ERROR([docbook2x-man required by man request, but not found]) + fi + fi + + AC_SUBST(db2xman) fi +AC_ARG_ENABLE([apparmor], + [AC_HELP_STRING([--enable-apparmor], [enable apparmor])], + [], [enable_apparmor=check]) + +if test "$enable_apparmor" = "check" ; then + AC_CHECK_LIB([apparmor],[aa_change_profile],[enable_apparmor=yes], [enable_apparmor=no]) +fi + +AM_CONDITIONAL([ENABLE_APPARMOR], [test "x$enable_apparmor" = "xyes"]) + AM_COND_IF([ENABLE_APPARMOR], [AC_CHECK_HEADER([sys/apparmor.h],[],[AC_MSG_ERROR([You must install the AppArmor development package in order to compile lxc])]) AC_CHECK_LIB([apparmor], [aa_change_profile],[],[AC_MSG_ERROR([You must install the AppArmor development package in order to compile lxc])]) AC_SUBST([APPARMOR_LIBS], [-lapparmor])]) -AM_CONDITIONAL([ENABLE_DOCBOOK], [test "x$have_docbook" = "xyes"]) +AC_ARG_ENABLE([seccomp], + [AC_HELP_STRING([--enable-seccomp], [enable seccomp])], + [], [enable_seccomp=check]) + +if test "$enable_seccomp" = "check" ; then + AC_CHECK_LIB([seccomp],[seccomp_init],[enable_seccomp=yes],[enable_seccomp=no]) +fi + +AM_CONDITIONAL([ENABLE_SECCOMP], [test "x$enable_seccomp" = "xyes"]) + +AM_COND_IF([ENABLE_SECCOMP], + [AC_CHECK_HEADER([seccomp.h],[],[AC_MSG_ERROR([You must install the seccomp development package in order to compile lxc])]) + AC_CHECK_LIB([seccomp], [seccomp_init],[],[AC_MSG_ERROR([You must install the seccomp development package in order to compile lxc])]) + AC_SUBST([SECCOMP_LIBS], [-lseccomp])]) + +# HAVE_SCMP_FILTER_CTX=1 will tell us we have libseccomp api >= 1.0.0 +AC_CHECK_TYPES([scmp_filter_ctx], [], [], [#include ]) + +AM_CONDITIONAL([ENABLE_DOCBOOK], [test "x$db2xman" != "x"]) AC_ARG_ENABLE([examples], [AC_HELP_STRING([--disable-examples], [do not install configuration examples])], @@ -47,6 +126,23 @@ AC_ARG_ENABLE([examples], AM_CONDITIONAL([ENABLE_EXAMPLES], [test "x$enable_examples" = "xyes"]) +AC_ARG_ENABLE([python], + [AC_HELP_STRING([--enable-python], [enable python binding])], + [enable_python=yes], [enable_python=no]) + +AM_CONDITIONAL([ENABLE_PYTHON], [test "x$enable_python" = "xyes"]) + +AM_COND_IF([ENABLE_PYTHON], + [AM_PATH_PYTHON([3.2], [], [AC_MSG_ERROR([You must install python3])]) + AC_CHECK_HEADER([python$PYTHON_VERSION/Python.h],[],[AC_MSG_ERROR([You must install python3-dev])]) + AC_DEFINE_UNQUOTED([ENABLE_PYTHON], 1, [Python3 is available])]) + +AC_ARG_ENABLE([tests], + [AC_HELP_STRING([--enable-tests], [build test/example binaries])], + [enable_tests=yes], [enable_tests=no]) + +AM_CONDITIONAL([ENABLE_TESTS], [test "x$enable_tests" = "xyes"]) + AS_AC_EXPAND(PREFIX, $prefix) AS_AC_EXPAND(LIBDIR, $libdir) AS_AC_EXPAND(BINDIR, $bindir) @@ -69,12 +165,13 @@ AC_ARG_WITH([rootfs-path], [lxc rootfs mount point] )], [], [with_rootfs_path=['${libdir}/lxc/rootfs']]) +AS_AC_EXPAND(LXC_CONFFILE, $conffile) AS_AC_EXPAND(LXC_GENERATE_DATE, "$(date)") AS_AC_EXPAND(LXCPATH, "${with_config_path}") AS_AC_EXPAND(LXCROOTFSMOUNT, "${with_rootfs_path}") AS_AC_EXPAND(LXCTEMPLATEDIR, ['${datadir}/lxc/templates']) -AC_SUBST(LXCINITDIR, ['${libexecdir}']) +AS_AC_EXPAND(LXCINITDIR, ['${libexecdir}']) AC_CHECK_HEADERS([linux/unistd.h linux/netlink.h linux/genetlink.h], [], @@ -101,9 +198,10 @@ AC_CHECK_DECLS([PR_CAPBSET_DROP], [], [], [#include ]) AC_CHECK_HEADERS([sys/signalfd.h]) AC_PROG_GCC_TRADITIONAL +AC_PROG_SED if test "x$GCC" = "xyes"; then - CFLAGS="$CFLAGS -Wall" + CFLAGS="$CFLAGS -Wall -Werror" fi AC_CONFIG_FILES([ @@ -134,6 +232,7 @@ AC_CONFIG_FILES([ doc/lxc.sgml doc/common_options.sgml doc/see_also.sgml + doc/legacy/lxc-ls.sgml doc/rootfs/Makefile @@ -154,6 +253,7 @@ AC_CONFIG_FILES([ templates/lxc-opensuse templates/lxc-busybox templates/lxc-fedora + templates/lxc-oracle templates/lxc-altlinux templates/lxc-sshd templates/lxc-archlinux @@ -161,7 +261,6 @@ AC_CONFIG_FILES([ src/Makefile src/lxc/Makefile src/lxc/lxc-ps - src/lxc/lxc-ls src/lxc/lxc-netstat src/lxc/lxc-checkconfig src/lxc/lxc-setcap @@ -170,7 +269,15 @@ AC_CONFIG_FILES([ src/lxc/lxc-create src/lxc/lxc-clone src/lxc/lxc-shutdown + src/lxc/lxc-start-ephemeral src/lxc/lxc-destroy + src/lxc/legacy/lxc-ls + + src/python-lxc/Makefile + src/python-lxc/lxc/__init__.py + src/python-lxc/examples/api_test.py + + src/tests/Makefile ]) AC_CONFIG_COMMANDS([default],[[]],[[]]) diff --git a/doc/FAQ.txt b/doc/FAQ.txt index fce5eef5d..50238ffde 100644 --- a/doc/FAQ.txt +++ b/doc/FAQ.txt @@ -32,8 +32,8 @@ error when starting a container. "[syserr] lxc_start:96: Invalid argument - failed to fork into a new namespace" -Answer: -------- +Answer: +------- read the lxc man page about kernel version prereq :) most probably your kernel is not configured to support the container options you diff --git a/doc/Makefile.am b/doc/Makefile.am index b18c5ebab..86de2fedb 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -19,7 +19,6 @@ man_MANS = \ lxc-unfreeze.1 \ lxc-monitor.1 \ lxc-wait.1 \ - lxc-ls.1 \ lxc-ps.1 \ lxc-cgroup.1 \ lxc-kill.1 \ @@ -29,15 +28,23 @@ man_MANS = \ \ lxc.7 +if ENABLE_PYTHON + man_MANS += lxc-ls.1 +else + man_MANS += legacy/lxc-ls.1 +endif -%.1 : %.sgml - docbook2man -w all $< +%.1 : %.sgml + $(db2xman) $< + test "$(shell basename $@)" != "$@" && mv $(shell basename $@) $@ || true -%.5 : %.sgml - docbook2man -w all $< +%.5 : %.sgml + $(db2xman) $< + test "$(shell basename $@)" != "$@" && mv $(shell basename $@) $@ || true -%.7 : %.sgml - docbook2man -w all $< +%.7 : %.sgml + $(db2xman) $< + test "$(shell basename $@)" != "$@" && mv $(shell basename $@) $@ || true lxc-%.sgml : common_options.sgml see_also.sgml diff --git a/doc/legacy/lxc-ls.sgml.in b/doc/legacy/lxc-ls.sgml.in new file mode 100644 index 000000000..c04a4a4c2 --- /dev/null +++ b/doc/legacy/lxc-ls.sgml.in @@ -0,0 +1,156 @@ + + + + +]> + + + + @LXC_GENERATE_DATE@ + + + lxc-ls + 1 + + + + lxc-ls + + + list the containers existing on the system + + + + + + lxc-ls + --active + ls option + + + + + Description + + lxc-ls list the containers existing on the + system. + + + + + Options + + + + + + + + + List active containers. + + + + + + + + + + + The option passed to lxc-ls are the + same as the ls command. + + + + + + + + + + Examples + + + lxc-ls -l + + + list all the container and their permissions. + + + + + + lxc-ls --active -1 + + + list active containers and display the list in one column. + + + + + + + + + See Also + + + + ls + 1 + , + + + + + &seealso; + + + Author + Daniel Lezcano daniel.lezcano@free.fr + + + + + diff --git a/doc/lxc-attach.sgml.in b/doc/lxc-attach.sgml.in index 5d6263f13..549c22fe1 100644 --- a/doc/lxc-attach.sgml.in +++ b/doc/lxc-attach.sgml.in @@ -23,7 +23,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> - @@ -52,6 +52,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -n name -a arch -e + -s namespaces + -R -- command @@ -125,7 +127,53 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - + + + + + + + Specify the namespaces to attach to, as a pipe-separated list, + e.g. NETWORK|IPC. Allowed values are + MOUNT, PID, + UTSNAME, IPC, + USER and + NETWORK. This allows one to change + the context of the process to e.g. the network namespace of the + container while retaining the other namespaces as those of the + host. + + + Important: This option implies + . + + + + + + + + + + + When using and the mount namespace is not + included, this flag will cause lxc-attach + to remount /proc and + /sys to reflect the current other + namespace contexts. + + + Please see the Notes section for more + details. + + + This option will be ignored if one tries to attach to the + mount namespace anyway. + + + + + @@ -147,19 +195,86 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA To deactivate the network link eth1 of a running container that - does not have the NET_ADMIN capability, use the - option to use increased capabilities: + does not have the NET_ADMIN capability, use either the + option to use increased capabilities, + assuming the ip tool is installed: lxc-attach -n container -e -- /sbin/ip link delete eth1 + Or, alternatively, use the to use the + tools installed on the host outside the container: + + lxc-attach -n container -s NETWORK -- /sbin/ip link delete eth1 + + + Compatibility + + Attaching completely (including the pid and mount namespaces) to a + container requires a patched kernel, please see the lxc website for + details. lxc-attach will fail in that case if + used with an unpatched kernel. + + + Nevertheless, it will succeed on an unpatched kernel of version 3.0 + or higher if the option is used to restrict the + namespaces that the process is to be attached to to one or more of + NETWORK, IPC + and UTSNAME. + + + Attaching to user namespaces is currently completely unsupported + by the kernel. lxc-attach should however be able + to do this once once future kernel versions implement this. + + + + + Notes + + The Linux /proc and + /sys filesystems contain information + about some quantities that are affected by namespaces, such as + the directories named after process ids in + /proc or the network interface infromation + in /sys/class/net. The namespace of the + process mounting the pseudo-filesystems determines what information + is shown, not the namespace of the process + accessing /proc or + /sys. + + + If one uses the option to only attach to + the pid namespace of a container, but not its mount namespace + (which will contain the /proc of the + container and not the host), the contents of + will reflect that of the host and not the container. Analogously, + the same issue occurs when reading the contents of + /sys/class/net and attaching to just + the network namespace. + + + To work around this problem, the flag provides + the option to remount /proc and + /sys in order for them to reflect the + network/pid namespace context of the attached process. In order + not to interfere with the host's actual filesystem, the mount + namespace will be unshared (like lxc-unshare + does) before this is done, esentially giving the process a new + mount namespace, which is identical to the hosts's mount namespace + except for the /proc and + /sys filesystems. + + + Security - The should be used with care, as it may break - the isolation of the containers if used improperly. + The and options should + be used with care, as it may break the isolation of the containers + if used improperly. diff --git a/doc/lxc-cgroup.sgml.in b/doc/lxc-cgroup.sgml.in index 9d49aa8ed..53e503542 100644 --- a/doc/lxc-cgroup.sgml.in +++ b/doc/lxc-cgroup.sgml.in @@ -1,4 +1,4 @@ - - @@ -143,7 +143,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA The container is not running. - + diff --git a/doc/lxc-checkpoint.sgml.in b/doc/lxc-checkpoint.sgml.in index 9cf0c79b1..78a6c9d38 100644 --- a/doc/lxc-checkpoint.sgml.in +++ b/doc/lxc-checkpoint.sgml.in @@ -23,7 +23,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> - diff --git a/doc/lxc-console.sgml.in b/doc/lxc-console.sgml.in index 2afba86ea..7b32e0820 100644 --- a/doc/lxc-console.sgml.in +++ b/doc/lxc-console.sgml.in @@ -1,4 +1,4 @@ - - @@ -67,7 +67,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA The available tty are free slots taken by this command. That means if the container has four ttys available and the command has been launched four times taking the different tty, the fifth - command will fail because no console will be available. + command will fail because no console will be available. @@ -114,7 +114,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA user "foo" and "bar" is trying to open a console to it. - + diff --git a/doc/lxc-create.sgml.in b/doc/lxc-create.sgml.in index 0ffb21c24..bcf0545cb 100644 --- a/doc/lxc-create.sgml.in +++ b/doc/lxc-create.sgml.in @@ -1,4 +1,4 @@ - - @@ -113,6 +113,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA eg. busybox, debian, fedora, ubuntu or sshd. Refer to the examples in @LXCTEMPLATEDIR@ for details of the expected script structure. + Alternatively, the full path to an executable template script + can also be passed as a parameter. @@ -123,9 +125,13 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - 'backingstore' is one of 'none', 'lvm', or 'btrfs'. The + 'backingstore' is one of 'none', 'dir', 'lvm', or 'btrfs'. The default is 'none', meaning that the container root filesystem will be a directory under @LXCPATH@/container/rootfs. + 'dir' has the same meaning as 'none', but also allows the optional + --dir ROOTFS to be specified, meaning + that the container rootfs should be placed under the specified path, + rather than the default. The option 'btrfs' need not be specified as it will be used automatically if the @LXCPATH@ filesystem is found to be btrfs. If backingstore is 'lvm', then an lvm block device will be @@ -141,6 +147,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA filesystem) of size SIZE rather than the default, which is 1G. + @@ -175,7 +182,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA available containers on the system. - + diff --git a/doc/lxc-destroy.sgml.in b/doc/lxc-destroy.sgml.in index a51e484b6..fe06f5276 100644 --- a/doc/lxc-destroy.sgml.in +++ b/doc/lxc-destroy.sgml.in @@ -1,4 +1,4 @@ - - @@ -100,7 +100,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA command to list the available containers on the system. - + diff --git a/doc/lxc-execute.sgml.in b/doc/lxc-execute.sgml.in index 6371751c6..393efe1ec 100644 --- a/doc/lxc-execute.sgml.in +++ b/doc/lxc-execute.sgml.in @@ -1,4 +1,4 @@ - - @@ -162,7 +162,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA container or create a new one. - + diff --git a/doc/lxc-freeze.sgml.in b/doc/lxc-freeze.sgml.in index 7b7be4976..228fd4cb9 100644 --- a/doc/lxc-freeze.sgml.in +++ b/doc/lxc-freeze.sgml.in @@ -1,4 +1,4 @@ - - @@ -81,7 +81,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA the lxc-create command. - + diff --git a/doc/lxc-kill.sgml.in b/doc/lxc-kill.sgml.in index d1e3f69ad..f1f6839ea 100644 --- a/doc/lxc-kill.sgml.in +++ b/doc/lxc-kill.sgml.in @@ -23,7 +23,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> - diff --git a/doc/lxc-ls.sgml.in b/doc/lxc-ls.sgml.in index 5a32f1da6..75c70be52 100644 --- a/doc/lxc-ls.sgml.in +++ b/doc/lxc-ls.sgml.in @@ -1,5 +1,5 @@ - - ]> @@ -49,8 +49,14 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA lxc-ls + -1 --active - ls option + --frozen + --running + --stopped + --fancy + --fancy-format + filter @@ -65,77 +71,136 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Options - - - - - - - List active containers. - - + + + + + + Show one entry per line. (default when /dev/stdout isn't a tty) + + - - - - - - The option passed to lxc-ls are the - same as the ls command. - - - - - - - - - - Examples - - - lxc-ls -l - - - list all the container and their permissions. - - + + + + + + List only active containers (same as --frozen --running). + + - lxc-ls --active -1 - - - list active containers and display the list in one column. - - + + + + + + List only frozen containers. + + + + + + + + + List only running containers. + + + + + + + + + + + List only stopped containers. + + + + + + + + + + + Use a fancy, column-based output. + + + + + + + + + + + Comma separate list of column to show in the fancy output. + Valid values are: name, state, ipv4, ipv6 and pid + Default is: name,state,ipv4,ipv6 + + + + + + + + + + + The filter passed to lxc-ls will be + applied to the container name. The format is a regular expression. + + + See Also - + - ls - 1 + ls + 1 , - + Examples + + + lxc-ls --fancy + + + list all the containers, listing one per line along with its + name, state, ipv4 and ipv6 addresses. + + + - &seealso; + + lxc-ls --active -1 + + + list active containers and display the list in one column. + + + + + Author - Daniel Lezcano daniel.lezcano@free.fr + Stéphane Graber stgraber@ubuntu.com - - @@ -135,7 +135,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA the lxc-create command. - + @@ -145,7 +145,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See Also - + regex 7 diff --git a/doc/lxc-ps.sgml.in b/doc/lxc-ps.sgml.in index 401a17c30..fb8e4c7e7 100644 --- a/doc/lxc-ps.sgml.in +++ b/doc/lxc-ps.sgml.in @@ -1,5 +1,5 @@ - - ]> @@ -86,8 +86,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA specify the container name - to limit the output to the processes belonging - to this container name. + to limit the output to the processes belonging + to this container name. @@ -98,7 +98,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - limit the output to the processes belonging + limit the output to the processes belonging to all lxc containers. @@ -139,7 +139,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See Also - + ps 1 diff --git a/doc/lxc-restart.sgml.in b/doc/lxc-restart.sgml.in index ffc0ad2aa..5285b8123 100644 --- a/doc/lxc-restart.sgml.in +++ b/doc/lxc-restart.sgml.in @@ -23,7 +23,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> - diff --git a/doc/lxc-shutdown.sgml.in b/doc/lxc-shutdown.sgml.in index f64f575b3..a4f3accfd 100644 --- a/doc/lxc-shutdown.sgml.in +++ b/doc/lxc-shutdown.sgml.in @@ -20,7 +20,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> - diff --git a/doc/lxc-start.sgml.in b/doc/lxc-start.sgml.in index 2b6778f23..5c98a2592 100644 --- a/doc/lxc-start.sgml.in +++ b/doc/lxc-start.sgml.in @@ -1,4 +1,4 @@ - - @@ -53,6 +53,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -f config_file -c console_file -d + -p pid_file -s KEY=VAL -C command @@ -107,6 +108,17 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + + + + + Create a file with the process id. + + + + @@ -186,7 +198,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA container or create a new one. - + diff --git a/doc/lxc-stop.sgml.in b/doc/lxc-stop.sgml.in index d16cab5c5..64fceb886 100644 --- a/doc/lxc-stop.sgml.in +++ b/doc/lxc-stop.sgml.in @@ -1,4 +1,4 @@ - - @@ -80,7 +80,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA container or create a new one. - + The container was not found @@ -90,7 +90,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA the lxc-create command. - + diff --git a/doc/lxc-unfreeze.sgml.in b/doc/lxc-unfreeze.sgml.in index c4c545e90..51f2c22f5 100644 --- a/doc/lxc-unfreeze.sgml.in +++ b/doc/lxc-unfreeze.sgml.in @@ -1,4 +1,4 @@ - - @@ -78,7 +78,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA the lxc-create command. - + diff --git a/doc/lxc-wait.sgml.in b/doc/lxc-wait.sgml.in index 4160e3812..4a73d058f 100644 --- a/doc/lxc-wait.sgml.in +++ b/doc/lxc-wait.sgml.in @@ -1,5 +1,5 @@ - - @@ -79,6 +79,17 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + + + + + Wait timeout seconds for desired state to be reached. + + + + @@ -122,7 +133,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA the lxc-create command. - + diff --git a/doc/lxc.conf b/doc/lxc.conf index 58cb960cc..4e2491b9f 100644 --- a/doc/lxc.conf +++ b/doc/lxc.conf @@ -16,7 +16,7 @@ lxc.network.type = macvlan # specify the flags to be used for the network, actually only is allowed # which mean the network should be set up when created. If the network is set -# up, the loopback is automatically set up too. +# up, the loopback is automatically set up too. lxc.network.flags = up # specify the physical network device which will communicate with the diff --git a/doc/lxc.conf.sgml.in b/doc/lxc.conf.sgml.in index 05d6bc7f7..96dea89f3 100644 --- a/doc/lxc.conf.sgml.in +++ b/doc/lxc.conf.sgml.in @@ -1,4 +1,4 @@ - - ]> @@ -235,7 +235,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA specify an action to do for the network. - + activates the interface. @@ -374,6 +374,26 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + + + + + + add a configuration option to specify a script to be + executed before destroying the network used from the + host side. The following arguments are passed to the + script: container name and config section name (net) + Additional arguments depend on the config section + employing a script hook; the following are used by the + network system: execution context (down), network type + (empty/veth/macvlan/phys), Depending on the network + type, other arguments may be passed: + veth/macvlan/phys. And finally (host-sided) device name. + + + @@ -481,6 +501,31 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + /dev directory + + By default, lxc does nothing with the container's + /dev. This allows the container's + /dev to be set up as needed in the container + rootfs. If lxc.autodev is set to 1, then after mounting the container's + rootfs LXC will mount a fresh tmpfs under /dev + (limited to 100k) and fill in a minimal set of initial devices. + + + + + + + + + Set this to 1 to have LXC mount and populate a minimal + /dev when starting the container. + + + + + + Mount points @@ -640,6 +685,84 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Startup hooks + + Startup hooks are programs or scripts which can be executed + at various times in a container's lifetime. + + + + + + + + + A hook to be run in the host's namespace before the + container ttys, consoles, or mounts are up. + + + + + + + + + + + + A hook to be run in the container's fs namespace but before + the rootfs has been set up. This allows for manipulation + of the rootfs, i.e. to mount an encrypted filesystem. Mounts + done in this hook will not be reflected on the host (apart from + mounts propagation), so they will be automatically cleaned up + when the container shuts down. + + + + + + + + + + + + A hook to be run in the container's namespace after + mounting has been done, but before the pivot_root. + + + + + + + + + + + + A hook to be run in the container's namespace immediately + before executing the container's init. This requires the + program to be available in the container. + + + + + + + + + + + + A hook to be run in the host's namespace after the + container has been shut down. + + + + + + @@ -726,7 +849,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See Also - + chroot 1 @@ -744,14 +867,14 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - + &seealso; Author Daniel Lezcano daniel.lezcano@free.fr - + - ]> @@ -239,7 +239,7 @@ rootfs /sbin /home/root/sshd/rootfs/sbin none ro,bind 0 0 - + How to run a system in a container ? Running a system inside a container is paradoxically easier @@ -249,7 +249,7 @@ rootfs without configuration because the container will set them up. eg. the ipv4 address will be setup by the system container init scripts. Here is an example of the mount points file: - + [root@lxc debian]$ cat fstab @@ -280,7 +280,7 @@ rootfs - + Configuration The container is configured through a configuration - file, the format of the configuration file is described in + file, the format of the configuration file is described in lxc.conf 5 @@ -363,7 +363,7 @@ rootfs but if needed the lxc-stop command can be used to kill the still running application. - + Running an application inside a container is not exactly the same thing as running a system. For this reason, there are two @@ -434,7 +434,7 @@ rootfs lxc-freeze -n foo - will put all the processes in an uninteruptible state and + will put all the processes in an uninteruptible state and lxc-unfreeze -n foo @@ -570,7 +570,7 @@ rootfs to the background. - + diff --git a/doc/rootfs/Makefile.am b/doc/rootfs/Makefile.am index 98fb0e07a..44d24ed9e 100644 --- a/doc/rootfs/Makefile.am +++ b/doc/rootfs/Makefile.am @@ -1,3 +1,3 @@ READMEdir=@LXCROOTFSMOUNT@ -README_DATA=README \ No newline at end of file +README_DATA=README diff --git a/lxc.spec.in b/lxc.spec.in index e55c69a56..c7470b855 100644 --- a/lxc.spec.in +++ b/lxc.spec.in @@ -29,8 +29,8 @@ Summary: %{name} : Linux Container Group: Applications/System License: LGPL BuildRoot: %{_tmppath}/%{name}-%{version}-build -Requires: libcap -BuildRequires: libcap libcap-devel docbook-utils +Requires: libcap openssl rsync +BuildRequires: libcap libcap-devel docbook2X %description @@ -91,11 +91,13 @@ rm -rf %{buildroot} %{_mandir}/* %{_datadir}/doc/* %{_datadir}/lxc/* +%{_sysconfdir}/lxc/* %files libs %defattr(-,root,root) %{_libdir}/*.so.* %{_libdir}/%{name} +%{_localstatedir}/* %attr(4555,root,root) %{_libexecdir}/%{name}/lxc-init %files devel diff --git a/runapitests.sh b/runapitests.sh new file mode 100644 index 000000000..116938719 --- /dev/null +++ b/runapitests.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +cleanup() { + rm -f /etc/lxc/test-busybox.conf + rm -f liblxc.so.0 +} + +if [ `id -u` -ne 0 ]; then + echo "Run as root" + exit 1 +fi + +cat > /etc/lxc/test-busybox.conf << EOF +lxc.network.type=veth +lxc.network.link=lxcbr0 +lxc.network.flags=up +EOF + +[ -f liblxc.so.0 ] || ln -s src/lxc/liblxc.so ./liblxc.so.0 +export LD_LIBRARY_PATH=. +TESTS="lxc-test-containertests lxc-test-locktests lxc-test-startone" +for curtest in $TESTS; do + echo "running $curtest" + ./src/tests/$curtest + if [ $? -ne 0 ]; then + echo "Test $curtest failed. Stopping" + cleanup + exit 1 + fi +done +echo "All tests passed" +cleanup diff --git a/src/Makefile.am b/src/Makefile.am index 7dd344abc..4e4d66b5e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1 +1 @@ -SUBDIRS = lxc +SUBDIRS = lxc tests python-lxc diff --git a/src/lxc/Makefile.am b/src/lxc/Makefile.am index 50e67bbf2..bf675f9d0 100644 --- a/src/lxc/Makefile.am +++ b/src/lxc/Makefile.am @@ -13,7 +13,9 @@ pkginclude_HEADERS = \ list.h \ log.h \ state.h \ - attach.h + attach.h \ + lxccontainer.h \ + lxclock.h sodir=$(libdir) # use PROGRAMS to avoid complains from automake @@ -50,33 +52,41 @@ liblxc_so_SOURCES = \ genl.c genl.h \ \ caps.c caps.h \ + lxcseccomp.h \ mainloop.c mainloop.h \ af_unix.c af_unix.h \ \ utmp.c utmp.h \ - apparmor.c apparmor.h + apparmor.c apparmor.h \ + lxclock.h lxclock.c \ + lxccontainer.c lxccontainer.h AM_CFLAGS=-I$(top_srcdir)/src \ -DLXCROOTFSMOUNT=\"$(LXCROOTFSMOUNT)\" \ -DLXCPATH=\"$(LXCPATH)\" \ - -DLXCINITDIR=\"$(LXCINITDIR)\" + -DLXCINITDIR=\"$(LXCINITDIR)\" \ + -DLXCTEMPLATEDIR=\"$(LXCTEMPLATEDIR)\" if ENABLE_APPARMOR AM_CFLAGS += -DHAVE_APPARMOR endif +if ENABLE_SECCOMP +AM_CFLAGS += -DHAVE_SECCOMP +liblxc_so_SOURCES += seccomp.c +endif + liblxc_so_CFLAGS = -fPIC -DPIC $(AM_CFLAGS) liblxc_so_LDFLAGS = \ -shared \ -Wl,-soname,liblxc.so.$(firstword $(subst ., ,$(VERSION))) -liblxc_so_LDADD = -lutil $(CAP_LIBS) $(APPARMOR_LIBS) +liblxc_so_LDADD = -lutil $(CAP_LIBS) $(APPARMOR_LIBS) $(SECCOMP_LIBS) -lrt bin_SCRIPTS = \ lxc-ps \ lxc-netstat \ - lxc-ls \ lxc-checkconfig \ lxc-setcap \ lxc-setuid \ @@ -86,6 +96,14 @@ bin_SCRIPTS = \ lxc-shutdown \ lxc-destroy +if ENABLE_PYTHON + bin_SCRIPTS += lxc-device + bin_SCRIPTS += lxc-ls + bin_SCRIPTS += lxc-start-ephemeral +else + bin_SCRIPTS += legacy/lxc-ls +endif + bin_PROGRAMS = \ lxc-attach \ lxc-unshare \ @@ -110,7 +128,7 @@ AM_LDFLAGS = -Wl,-E if ENABLE_RPATH AM_LDFLAGS += -Wl,-rpath -Wl,$(libdir) endif -LDADD=liblxc.so @CAP_LIBS@ @APPARMOR_LIBS@ +LDADD=liblxc.so @CAP_LIBS@ @APPARMOR_LIBS@ @SECCOMP_LIBS@ -lrt lxc_attach_SOURCES = lxc_attach.c lxc_cgroup_SOURCES = lxc_cgroup.c diff --git a/src/lxc/af_unix.c b/src/lxc/af_unix.c index 6d6cca1d6..2a3482847 100644 --- a/src/lxc/af_unix.c +++ b/src/lxc/af_unix.c @@ -52,7 +52,7 @@ int lxc_af_unix_open(const char *path, int type, int flags) addr.sun_family = AF_UNIX; /* copy entire buffer in case of abstract socket */ - memcpy(addr.sun_path, path, + memcpy(addr.sun_path, path, path[0]?strlen(path):sizeof(addr.sun_path)); if (bind(fd, (struct sockaddr *)&addr, sizeof(addr))) { @@ -73,7 +73,7 @@ int lxc_af_unix_close(int fd) struct sockaddr_un addr; socklen_t addrlen; - if (!getsockname(fd, (struct sockaddr *)&addr, &addrlen) && + if (!getsockname(fd, (struct sockaddr *)&addr, &addrlen) && addr.sun_path[0]) unlink(addr.sun_path); @@ -95,7 +95,7 @@ int lxc_af_unix_connect(const char *path) addr.sun_family = AF_UNIX; /* copy entire buffer in case of abstract socket */ - memcpy(addr.sun_path, path, + memcpy(addr.sun_path, path, path[0]?strlen(path):sizeof(addr.sun_path)); if (connect(fd, (struct sockaddr *)&addr, sizeof(addr))) { @@ -161,7 +161,7 @@ int lxc_af_unix_recv_fd(int fd, int *recvfd, void *data, size_t size) cmsg = CMSG_FIRSTHDR(&msg); - /* if the message is wrong the variable will not be + /* if the message is wrong the variable will not be * filled and the peer will notified about a problem */ *recvfd = -1; diff --git a/src/lxc/arguments.h b/src/lxc/arguments.h index 40f0d6c1b..188c4606b 100644 --- a/src/lxc/arguments.h +++ b/src/lxc/arguments.h @@ -45,6 +45,7 @@ struct lxc_arguments { int daemonize; const char *rcfile; const char *console; + const char *pidfile; /* for lxc-checkpoint/restart */ const char *statefile; @@ -57,6 +58,7 @@ struct lxc_arguments { /* for lxc-wait */ char *states; + long timeout; /* close fds from parent? */ int close_all_fds; diff --git a/src/lxc/attach.c b/src/lxc/attach.c index a95b3d3cc..ec0e08300 100644 --- a/src/lxc/attach.c +++ b/src/lxc/attach.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #if !HAVE_DECL_PR_CAPBSET_DROP @@ -121,13 +122,22 @@ out_error: return NULL; } -int lxc_attach_to_ns(pid_t pid) +int lxc_attach_to_ns(pid_t pid, int which) { char path[MAXPATHLEN]; - char *ns[] = { "pid", "mnt", "net", "ipc", "uts" }; - const int size = sizeof(ns) / sizeof(char *); + /* according to , + * the file for user namepsaces in /proc/$pid/ns will be called + * 'user' once the kernel supports it + */ + static char *ns[] = { "mnt", "pid", "uts", "ipc", "user", "net" }; + static int flags[] = { + CLONE_NEWNS, CLONE_NEWPID, CLONE_NEWUTS, CLONE_NEWIPC, + CLONE_NEWUSER, CLONE_NEWNET + }; + static const int size = sizeof(ns) / sizeof(char *); int fd[size]; - int i; + int i, j, saved_errno; + snprintf(path, MAXPATHLEN, "/proc/%d/ns", pid); if (access(path, X_OK)) { @@ -136,16 +146,39 @@ int lxc_attach_to_ns(pid_t pid) } for (i = 0; i < size; i++) { + /* ignore if we are not supposed to attach to that + * namespace + */ + if (which != -1 && !(which & flags[i])) { + fd[i] = -1; + continue; + } + snprintf(path, MAXPATHLEN, "/proc/%d/ns/%s", pid, ns[i]); fd[i] = open(path, O_RDONLY); if (fd[i] < 0) { + saved_errno = errno; + + /* close all already opened file descriptors before + * we return an error, so we don't leak them + */ + for (j = 0; j < i; j++) + close(fd[j]); + + errno = saved_errno; SYSERROR("failed to open '%s'", path); return -1; } } for (i = 0; i < size; i++) { - if (setns(fd[i], 0)) { + if (fd[i] >= 0 && setns(fd[i], 0) != 0) { + saved_errno = errno; + + for (j = i; j < size; j++) + close(fd[j]); + + errno = saved_errno; SYSERROR("failed to set namespace '%s'", ns[i]); return -1; } @@ -156,6 +189,49 @@ int lxc_attach_to_ns(pid_t pid) return 0; } +int lxc_attach_remount_sys_proc() +{ + int ret; + + ret = unshare(CLONE_NEWNS); + if (ret < 0) { + SYSERROR("failed to unshare mount namespace"); + return -1; + } + + /* assume /proc is always mounted, so remount it */ + ret = umount2("/proc", MNT_DETACH); + if (ret < 0) { + SYSERROR("failed to unmount /proc"); + return -1; + } + + ret = mount("none", "/proc", "proc", 0, NULL); + if (ret < 0) { + SYSERROR("failed to remount /proc"); + return -1; + } + + /* try to umount /sys - if it's not a mount point, + * we'll get EINVAL, then we ignore it because it + * may not have been mounted in the first place + */ + ret = umount2("/sys", MNT_DETACH); + if (ret < 0 && errno != EINVAL) { + SYSERROR("failed to unmount /sys"); + return -1; + } else if (ret == 0) { + /* remount it */ + ret = mount("none", "/sys", "sysfs", 0, NULL); + if (ret < 0) { + SYSERROR("failed to remount /sys"); + return -1; + } + } + + return 0; +} + int lxc_attach_drop_privs(struct lxc_proc_context_info *ctx) { int last_cap = lxc_caps_last_cap(); diff --git a/src/lxc/attach.h b/src/lxc/attach.h index 2d46c83dc..aab47e33f 100644 --- a/src/lxc/attach.h +++ b/src/lxc/attach.h @@ -33,7 +33,8 @@ struct lxc_proc_context_info { extern struct lxc_proc_context_info *lxc_proc_get_context_info(pid_t pid); -extern int lxc_attach_to_ns(pid_t other_pid); +extern int lxc_attach_to_ns(pid_t other_pid, int which); +extern int lxc_attach_remount_sys_proc(); extern int lxc_attach_drop_privs(struct lxc_proc_context_info *ctx); #endif diff --git a/src/lxc/cgroup.c b/src/lxc/cgroup.c index 69ba4e5d1..b6c948b91 100644 --- a/src/lxc/cgroup.c +++ b/src/lxc/cgroup.c @@ -254,13 +254,38 @@ static int cgroup_enable_clone_children(const char *path) return ret; } -static int lxc_one_cgroup_attach(const char *name, - struct mntent *mntent, pid_t pid) +static int lxc_one_cgroup_finish_attach(int fd, pid_t pid) { - FILE *f; + char buf[32]; + int ret; + + snprintf(buf, 32, "%ld", (long)pid); + + ret = write(fd, buf, strlen(buf)); + if (ret <= 0) { + SYSERROR("failed to write pid '%ld' to fd '%d'", (long)pid, fd); + ret = -1; + } else { + ret = 0; + } + + close(fd); + return ret; +} + +static int lxc_one_cgroup_dispose_attach(int fd) +{ + close(fd); + return 0; +} + +static int lxc_one_cgroup_prepare_attach(const char *name, + struct mntent *mntent) +{ + int fd; char tasks[MAXPATHLEN], initcgroup[MAXPATHLEN]; char *cgmnt = mntent->mnt_dir; - int flags, ret = 0; + int flags; int rc; flags = get_cgroup_flags(mntent); @@ -274,31 +299,83 @@ static int lxc_one_cgroup_attach(const char *name, return -1; } - f = fopen(tasks, "w"); - if (!f) { + fd = open(tasks, O_WRONLY); + if (fd < 0) { SYSERROR("failed to open '%s'", tasks); return -1; } - if (fprintf(f, "%d", pid) <= 0) { - SYSERROR("failed to write pid '%d' to '%s'", pid, tasks); - ret = -1; + return fd; +} + +static int lxc_one_cgroup_attach(const char *name, struct mntent *mntent, pid_t pid) +{ + int fd; + + fd = lxc_one_cgroup_prepare_attach(name, mntent); + if (fd < 0) { + return -1; } - fclose(f); + return lxc_one_cgroup_finish_attach(fd, pid); +} + +int lxc_cgroup_dispose_attach(void *data) +{ + int *fds = data; + int ret, err; + + if (!fds) { + return 0; + } + + ret = 0; + + for (; *fds >= 0; fds++) { + err = lxc_one_cgroup_dispose_attach(*fds); + if (err) { + ret = err; + } + } + + free(data); return ret; } -/* - * for each mounted cgroup, attach a pid to the cgroup for the container - */ -int lxc_cgroup_attach(const char *name, pid_t pid) +int lxc_cgroup_finish_attach(void *data, pid_t pid) +{ + int *fds = data; + int err; + + if (!fds) { + return 0; + } + + for (; *fds >= 0; fds++) { + err = lxc_one_cgroup_finish_attach(*fds, pid); + if (err) { + /* get rid of the rest of them */ + lxc_cgroup_dispose_attach(data); + return -1; + } + *fds = -1; + } + + free(data); + + return 0; +} + +int lxc_cgroup_prepare_attach(const char *name, void **data) { struct mntent *mntent; FILE *file = NULL; int err = -1; int found = 0; + int *fds; + int i; + static const int MAXFDS = 256; file = setmntent(MTAB, "r"); if (!file) { @@ -306,7 +383,29 @@ int lxc_cgroup_attach(const char *name, pid_t pid) return -1; } + /* create a large enough buffer for all practical + * use cases + */ + fds = malloc(sizeof(int) * MAXFDS); + if (!fds) { + err = -1; + goto out; + } + for (i = 0; i < MAXFDS; i++) { + fds[i] = -1; + } + + err = 0; + i = 0; while ((mntent = getmntent(file))) { + if (i >= MAXFDS - 1) { + ERROR("too many cgroups to attach to, aborting"); + lxc_cgroup_dispose_attach(fds); + errno = ENOMEM; + err = -1; + goto out; + } + DEBUG("checking '%s' (%s)", mntent->mnt_dir, mntent->mnt_type); if (strcmp(mntent->mnt_type, "cgroup")) @@ -317,19 +416,41 @@ int lxc_cgroup_attach(const char *name, pid_t pid) INFO("[%d] found cgroup mounted at '%s',opts='%s'", ++found, mntent->mnt_dir, mntent->mnt_opts); - err = lxc_one_cgroup_attach(name, mntent, pid); - if (err) + fds[i] = lxc_one_cgroup_prepare_attach(name, mntent); + if (fds[i] < 0) { + err = fds[i]; + lxc_cgroup_dispose_attach(fds); goto out; + } + i++; }; if (!found) ERROR("No cgroup mounted on the system"); + *data = fds; + out: endmntent(file); return err; } +/* + * for each mounted cgroup, attach a pid to the cgroup for the container + */ +int lxc_cgroup_attach(const char *name, pid_t pid) +{ + void *data = NULL; + int ret; + + ret = lxc_cgroup_prepare_attach(name, &data); + if (ret < 0) { + return ret; + } + + return lxc_cgroup_finish_attach(data, pid); +} + /* * rename cgname, which is under cgparent, to a new name starting * with 'cgparent/dead'. That way cgname can be reused. Return @@ -421,7 +542,7 @@ static int lxc_one_cgroup_create(const char *name, /* if cgparent does not exist, create it */ if (access(cgparent, F_OK)) { ret = mkdir(cgparent, 0755); - if (ret == -1 && errno == EEXIST) { + if (ret == -1 && errno != EEXIST) { SYSERROR("failed to create '%s' directory", cgparent); return -1; } @@ -668,6 +789,13 @@ out: return ret; } +/* + * If you pass in NULL value or 0 len, then you are asking for the size + * of the file. Note that we can't get the file size quickly through stat + * or lseek. Therefore if you pass in len > 0 but less than the file size, + * your only indication will be that the return value will be equal to the + * passed-in ret. We will not return the actual full file size. + */ int lxc_cgroup_get(const char *name, const char *filename, char *value, size_t len) { @@ -692,7 +820,18 @@ int lxc_cgroup_get(const char *name, const char *filename, return -1; } - ret = read(fd, value, len); + if (!len || !value) { + char buf[100]; + int count = 0; + while ((ret = read(fd, buf, 100)) > 0) + count += ret; + if (ret >= 0) + ret = count; + } else { + memset(value, 0, len); + ret = read(fd, value, len); + } + if (ret < 0) ERROR("read %s : %s", path, strerror(errno)); diff --git a/src/lxc/cgroup.h b/src/lxc/cgroup.h index 3c90696dc..8167f3920 100644 --- a/src/lxc/cgroup.h +++ b/src/lxc/cgroup.h @@ -31,5 +31,8 @@ extern int lxc_cgroup_destroy(const char *name); extern int lxc_cgroup_path_get(char **path, const char *subsystem, const char *name); extern int lxc_cgroup_nrtasks(const char *name); extern int lxc_cgroup_attach(const char *name, pid_t pid); +extern int lxc_cgroup_prepare_attach(const char *name, void **data); +extern int lxc_cgroup_finish_attach(void *data, pid_t pid); +extern int lxc_cgroup_dispose_attach(void *data); extern int lxc_ns_is_mounted(void); #endif diff --git a/src/lxc/commands.c b/src/lxc/commands.c index cce24db0f..737540d3d 100644 --- a/src/lxc/commands.c +++ b/src/lxc/commands.c @@ -154,11 +154,32 @@ pid_t get_init_pid(const char *name) return command.answer.pid; } +int lxc_get_clone_flags(const char *name) +{ + struct lxc_command command = { + .request = { .type = LXC_COMMAND_CLONE_FLAGS }, + }; + + int ret, stopped = 0; + + ret = lxc_command(name, &command, &stopped); + if (ret < 0 && stopped) + return -1; + + if (ret < 0) { + ERROR("failed to send command"); + return -1; + } + + return command.answer.ret; +} + extern void lxc_console_remove_fd(int, struct lxc_tty_info *); extern int lxc_console_callback(int, struct lxc_request *, struct lxc_handler *); extern int lxc_stop_callback(int, struct lxc_request *, struct lxc_handler *); extern int lxc_state_callback(int, struct lxc_request *, struct lxc_handler *); extern int lxc_pid_callback(int, struct lxc_request *, struct lxc_handler *); +extern int lxc_clone_flags_callback(int, struct lxc_request *, struct lxc_handler *); static int trigger_command(int fd, struct lxc_request *request, struct lxc_handler *handler) @@ -166,10 +187,11 @@ static int trigger_command(int fd, struct lxc_request *request, typedef int (*callback)(int, struct lxc_request *, struct lxc_handler *); callback cb[LXC_COMMAND_MAX] = { - [LXC_COMMAND_TTY] = lxc_console_callback, - [LXC_COMMAND_STOP] = lxc_stop_callback, - [LXC_COMMAND_STATE] = lxc_state_callback, - [LXC_COMMAND_PID] = lxc_pid_callback, + [LXC_COMMAND_TTY] = lxc_console_callback, + [LXC_COMMAND_STOP] = lxc_stop_callback, + [LXC_COMMAND_STATE] = lxc_state_callback, + [LXC_COMMAND_PID] = lxc_pid_callback, + [LXC_COMMAND_CLONE_FLAGS] = lxc_clone_flags_callback, }; if (request->type < 0 || request->type >= LXC_COMMAND_MAX) @@ -305,5 +327,6 @@ extern int lxc_command_mainloop_add(const char *name, close(fd); } + handler->conf->maincmd_fd = fd; return ret; } diff --git a/src/lxc/commands.h b/src/lxc/commands.h index d5c013fc7..3b0ac9a65 100644 --- a/src/lxc/commands.h +++ b/src/lxc/commands.h @@ -28,6 +28,7 @@ enum { LXC_COMMAND_STOP, LXC_COMMAND_STATE, LXC_COMMAND_PID, + LXC_COMMAND_CLONE_FLAGS, LXC_COMMAND_MAX, }; @@ -48,6 +49,7 @@ struct lxc_command { }; extern pid_t get_init_pid(const char *name); +extern int lxc_get_clone_flags(const char *name); extern int lxc_command(const char *name, struct lxc_command *command, int *stopped); diff --git a/src/lxc/conf.c b/src/lxc/conf.c index b437654a3..84e51db1b 100644 --- a/src/lxc/conf.c +++ b/src/lxc/conf.c @@ -66,6 +66,8 @@ #include #endif +#include "lxcseccomp.h" + lxc_log_define(lxc_conf, lxc); #define MAXHWLEN 18 @@ -109,6 +111,9 @@ lxc_log_define(lxc_conf, lxc); #define PR_CAPBSET_DROP 24 #endif +char *lxchook_names[NUM_LXC_HOOKS] = { + "pre-start", "pre-mount", "mount", "start", "post-stop" }; + extern int pivot_root(const char * new_root, const char * put_old); typedef int (*instanciate_cb)(struct lxc_handler *, struct lxc_netdev *); @@ -138,6 +143,20 @@ static instanciate_cb netdev_conf[LXC_NET_MAXCONFTYPE + 1] = { [LXC_NET_EMPTY] = instanciate_empty, }; +static int shutdown_veth(struct lxc_handler *, struct lxc_netdev *); +static int shutdown_macvlan(struct lxc_handler *, struct lxc_netdev *); +static int shutdown_vlan(struct lxc_handler *, struct lxc_netdev *); +static int shutdown_phys(struct lxc_handler *, struct lxc_netdev *); +static int shutdown_empty(struct lxc_handler *, struct lxc_netdev *); + +static instanciate_cb netdev_deconf[LXC_NET_MAXCONFTYPE + 1] = { + [LXC_NET_VETH] = shutdown_veth, + [LXC_NET_MACVLAN] = shutdown_macvlan, + [LXC_NET_VLAN] = shutdown_vlan, + [LXC_NET_PHYS] = shutdown_phys, + [LXC_NET_EMPTY] = shutdown_empty, +}; + static struct mount_opt mount_opt[] = { { "defaults", 0, 0 }, { "ro", 0, MS_RDONLY }, @@ -214,12 +233,41 @@ static struct caps_opt caps_opt[] = { #endif }; +static int run_buffer(char *buffer) +{ + FILE *f; + char *output; + + f = popen(buffer, "r"); + if (!f) { + SYSERROR("popen failed"); + return -1; + } + + output = malloc(LXC_LOG_BUFFER_SIZE); + if (!output) { + ERROR("failed to allocate memory for script output"); + return -1; + } + + while(fgets(output, LXC_LOG_BUFFER_SIZE, f)) + DEBUG("script output: %s", output); + + free(output); + + if (pclose(f) == -1) { + SYSERROR("Script exited on error"); + return -1; + } + + return 0; +} + static int run_script(const char *name, const char *section, const char *script, ...) { int ret; - FILE *f; - char *buffer, *p, *output; + char *buffer, *p; size_t size = 0; va_list ap; @@ -266,29 +314,7 @@ static int run_script(const char *name, const char *section, } va_end(ap); - f = popen(buffer, "r"); - if (!f) { - SYSERROR("popen failed"); - return -1; - } - - output = malloc(LXC_LOG_BUFFER_SIZE); - if (!output) { - ERROR("failed to allocate memory for script output"); - return -1; - } - - while(fgets(output, LXC_LOG_BUFFER_SIZE, f)) - DEBUG("script output: %s", output); - - free(output); - - if (pclose(f) == -1) { - SYSERROR("Script exited on error"); - return -1; - } - - return 0; + return run_buffer(buffer); } static int find_fstype_cb(char* buffer, void *data) @@ -636,6 +662,15 @@ static int setup_tty(const struct lxc_rootfs *rootfs, return -1; } } else { + /* If we populated /dev, then we need to create /dev/ttyN */ + if (access(path, F_OK)) { + ret = creat(path, 0660); + if (ret==-1) { + SYSERROR("error creating %s\n", path); + /* this isn't fatal, continue */ + } else + close(ret); + } if (mount(pty_info->name, path, "none", MS_BIND, 0)) { WARN("failed to mount '%s'->'%s'", pty_info->name, path); @@ -708,7 +743,7 @@ static int umount_oldrootfs(const char *oldrootfs) { char path[MAXPATHLEN]; void *cbparm[2]; - struct lxc_list mountlist, *iterator; + struct lxc_list mountlist, *iterator, *next; int ok, still_mounted, last_still_mounted; int rc; @@ -748,7 +783,7 @@ static int umount_oldrootfs(const char *oldrootfs) last_still_mounted = still_mounted; still_mounted = 0; - lxc_list_for_each(iterator, &mountlist) { + lxc_list_for_each_safe(iterator, &mountlist, next) { /* umount normally */ if (!umount(iterator->elem)) { @@ -843,6 +878,114 @@ static int setup_rootfs_pivot_root(const char *rootfs, const char *pivotdir) return 0; } +/* + * Do we want to add options for max size of /dev and a file to + * specify which devices to create? + */ +static int mount_autodev(char *root) +{ + int ret; + char path[MAXPATHLEN]; + + INFO("Mounting /dev under %s\n", root); + ret = snprintf(path, MAXPATHLEN, "%s/dev", root); + if (ret < 0 || ret > MAXPATHLEN) + return -1; + ret = mount("none", path, "tmpfs", 0, "size=100000"); + if (ret) { + SYSERROR("Failed to mount /dev at %s\n", root); + return -1; + } + ret = snprintf(path, MAXPATHLEN, "%s/dev/pts", root); + if (ret < 0 || ret >= MAXPATHLEN) + return -1; + ret = mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + if (ret) { + SYSERROR("Failed to create /dev/pts in container"); + return -1; + } + + INFO("Mounted /dev under %s\n", root); + return 0; +} + +/* + * Try to run MAKEDEV console in the container. If something fails, + * continue anyway as it should not be detrimental to the container. + * This makes sure that things like /dev/vcs* exist. + * (Pass devpath in to reduce stack usage) + */ +static void run_makedev(char *devpath) +{ + int curd; + int ret; + + curd = open(".", O_RDONLY); + if (curd < 0) + return; + ret = chdir(devpath); + if (ret) { + close(curd); + return; + } + if (run_buffer("/sbin/MAKEDEV console")) + INFO("Error running MAKEDEV console in %s", devpath); + ret = fchdir(curd); + if (ret) + INFO("Error returning to original directory: expect breakage"); + close(curd); +} + +struct lxc_devs { + char *name; + mode_t mode; + int maj; + int min; +}; + +struct lxc_devs lxc_devs[] = { + { "null", S_IFCHR | S_IRWXU | S_IRWXG | S_IRWXO, 1, 3 }, + { "zero", S_IFCHR | S_IRWXU | S_IRWXG | S_IRWXO, 1, 5 }, + { "full", S_IFCHR | S_IRWXU | S_IRWXG | S_IRWXO, 1, 7 }, + { "urandom", S_IFCHR | S_IRWXU | S_IRWXG | S_IRWXO, 1, 9 }, + { "random", S_IFCHR | S_IRWXU | S_IRWXG | S_IRWXO, 1, 8 }, + { "tty", S_IFCHR | S_IRWXU | S_IRWXG | S_IRWXO, 5, 0 }, + { "console", S_IFCHR | S_IRUSR | S_IWUSR, 5, 1 }, +}; + +static int setup_autodev(char *root) +{ + int ret; + struct lxc_devs *d; + char path[MAXPATHLEN]; + int i; + + INFO("Creating initial consoles under %s/dev\n", root); + + ret = snprintf(path, MAXPATHLEN, "%s/dev", root); + if (ret < 0 || ret >= MAXPATHLEN) { + ERROR("Error calculating container /dev location"); + return -1; + } else + run_makedev(path); + + INFO("Populating /dev under %s\n", root); + for (i = 0; i < sizeof(lxc_devs) / sizeof(lxc_devs[0]); i++) { + d = &lxc_devs[i]; + ret = snprintf(path, MAXPATHLEN, "%s/dev/%s", root, d->name); + if (ret < 0 || ret >= MAXPATHLEN) + return -1; + ret = mknod(path, d->mode, makedev(d->maj, d->min)); + if (ret && errno != EEXIST) { + SYSERROR("Error creating %s\n", d->name); + return -1; + } + } + + INFO("Populated /dev under %s\n", root); + return 0; +} + static int setup_rootfs(const struct lxc_rootfs *rootfs) { if (!rootfs->path) @@ -1061,6 +1204,8 @@ static int setup_kmsg(const struct lxc_rootfs *rootfs, char kpath[MAXPATHLEN]; int ret; + if (!rootfs->path) + return 0; ret = snprintf(kpath, sizeof(kpath), "%s/dev/kmsg", rootfs->mount); if (ret < 0 || ret >= sizeof(kpath)) return -1; @@ -1228,8 +1373,8 @@ static int mount_entry_on_absolute_rootfs(struct mntent *mntent, } /* if rootfs->path is a blockdev path, allow container fstab to - * use /var/lib/lxc/CN/rootfs as the target prefix */ - r = snprintf(path, MAXPATHLEN, "/var/lib/lxc/%s/rootfs", lxc_name); + * use $LXCPATH/CN/rootfs as the target prefix */ + r = snprintf(path, MAXPATHLEN, LXCPATH "/%s/rootfs", lxc_name); if (r < 0 || r >= MAXPATHLEN) goto skipvarlib; @@ -1647,7 +1792,7 @@ static int setup_netdev(struct lxc_netdev *netdev) ifname, strerror(-err)); if (netdev->ipv6_gateway_auto) { char buf[INET6_ADDRSTRLEN]; - inet_ntop(AF_INET, netdev->ipv6_gateway, buf, sizeof(buf)); + inet_ntop(AF_INET6, netdev->ipv6_gateway, buf, sizeof(buf)); ERROR("tried to set autodetected ipv6 gateway '%s'", buf); } return -1; @@ -1680,6 +1825,21 @@ static int setup_network(struct lxc_list *network) return 0; } +void lxc_rename_phys_nics_on_shutdown(struct lxc_conf *conf) +{ + int i; + + INFO("running to reset %d nic names", conf->num_savednics); + for (i=0; inum_savednics; i++) { + struct saved_nic *s = &conf->saved_nics[i]; + INFO("resetting nic %d to %s\n", s->ifindex, s->orig_name); + lxc_netdev_rename_by_index(s->ifindex, s->orig_name); + free(s->orig_name); + } + conf->num_savednics = 0; + free(conf->saved_nics); +} + static int setup_private_host_hw_addr(char *veth1) { struct ifreq ifr; @@ -1715,6 +1875,8 @@ static int setup_private_host_hw_addr(char *veth1) return 0; } +static char *default_rootfs_mount = LXCROOTFSMOUNT; + struct lxc_conf *lxc_conf_init(void) { struct lxc_conf *new; @@ -1733,7 +1895,8 @@ struct lxc_conf *lxc_conf_init(void) new->console.master = -1; new->console.slave = -1; new->console.name[0] = '\0'; - new->rootfs.mount = LXCROOTFSMOUNT; + new->rootfs.mount = default_rootfs_mount; + new->loglevel = LXC_LOG_PRIORITY_NOTSET; lxc_list_init(&new->cgroup); lxc_list_init(&new->network); lxc_list_init(&new->mount_list); @@ -1765,6 +1928,8 @@ static int instanciate_veth(struct lxc_handler *handler, struct lxc_netdev *netd return -1; } veth1 = mktemp(veth1buf); + /* store away for deconf */ + memcpy(netdev->priv.veth_attr.veth1, veth1, IFNAMSIZ); } snprintf(veth2buf, sizeof(veth2buf), "vethXXXXXX"); @@ -1841,6 +2006,25 @@ out_delete: return -1; } +static int shutdown_veth(struct lxc_handler *handler, struct lxc_netdev *netdev) +{ + char *veth1; + int err; + + if (netdev->priv.veth_attr.pair) + veth1 = netdev->priv.veth_attr.pair; + else + veth1 = netdev->priv.veth_attr.veth1; + + if (netdev->downscript) { + err = run_script(handler->name, "net", netdev->downscript, + "down", "veth", veth1, (char*) NULL); + if (err) + return -1; + } + return 0; +} + static int instanciate_macvlan(struct lxc_handler *handler, struct lxc_netdev *netdev) { char peerbuf[IFNAMSIZ], *peer; @@ -1889,6 +2073,20 @@ static int instanciate_macvlan(struct lxc_handler *handler, struct lxc_netdev *n return 0; } +static int shutdown_macvlan(struct lxc_handler *handler, struct lxc_netdev *netdev) +{ + int err; + + if (netdev->downscript) { + err = run_script(handler->name, "net", netdev->downscript, + "down", "macvlan", netdev->link, + (char*) NULL); + if (err) + return -1; + } + return 0; +} + /* XXX: merge with instanciate_macvlan */ static int instanciate_vlan(struct lxc_handler *handler, struct lxc_netdev *netdev) { @@ -1926,6 +2124,11 @@ static int instanciate_vlan(struct lxc_handler *handler, struct lxc_netdev *netd return 0; } +static int shutdown_vlan(struct lxc_handler *handler, struct lxc_netdev *netdev) +{ + return 0; +} + static int instanciate_phys(struct lxc_handler *handler, struct lxc_netdev *netdev) { if (!netdev->link) { @@ -1950,6 +2153,19 @@ static int instanciate_phys(struct lxc_handler *handler, struct lxc_netdev *netd return 0; } +static int shutdown_phys(struct lxc_handler *handler, struct lxc_netdev *netdev) +{ + int err; + + if (netdev->downscript) { + err = run_script(handler->name, "net", netdev->downscript, + "down", "phys", netdev->link, (char*) NULL); + if (err) + return -1; + } + return 0; +} + static int instanciate_empty(struct lxc_handler *handler, struct lxc_netdev *netdev) { netdev->ifindex = 0; @@ -1963,6 +2179,19 @@ static int instanciate_empty(struct lxc_handler *handler, struct lxc_netdev *net return 0; } +static int shutdown_empty(struct lxc_handler *handler, struct lxc_netdev *netdev) +{ + int err; + + if (netdev->downscript) { + err = run_script(handler->name, "net", netdev->downscript, + "down", "empty", (char*) NULL); + if (err) + return -1; + } + return 0; +} + int lxc_create_network(struct lxc_handler *handler) { struct lxc_list *network = &handler->conf->network; @@ -1989,28 +2218,32 @@ int lxc_create_network(struct lxc_handler *handler) return 0; } -void lxc_delete_network(struct lxc_list *network) +void lxc_delete_network(struct lxc_handler *handler) { + struct lxc_list *network = &handler->conf->network; struct lxc_list *iterator; struct lxc_netdev *netdev; lxc_list_for_each(iterator, network) { netdev = iterator->elem; - if (netdev->ifindex == 0) - continue; - if (netdev->type == LXC_NET_PHYS) { + if (netdev->ifindex != 0 && netdev->type == LXC_NET_PHYS) { if (lxc_netdev_rename_by_index(netdev->ifindex, netdev->link)) WARN("failed to rename to the initial name the " \ "netdev '%s'", netdev->link); continue; } + if (netdev_deconf[netdev->type](handler, netdev)) { + WARN("failed to destroy netdev"); + } + /* Recent kernel remove the virtual interfaces when the network * namespace is destroyed but in case we did not moved the * interface to the network namespace, we have to destroy it */ - if (lxc_netdev_delete_by_index(netdev->ifindex)) + if (netdev->ifindex != 0 && + lxc_netdev_delete_by_index(netdev->ifindex)) WARN("failed to remove interface '%s'", netdev->name); } } @@ -2166,11 +2399,23 @@ int lxc_setup(const char *name, struct lxc_conf *lxc_conf) return -1; } + if (run_lxc_hooks(name, "pre-mount", lxc_conf)) { + ERROR("failed to run pre-mount hooks for container '%s'.", name); + return -1; + } + if (setup_rootfs(&lxc_conf->rootfs)) { ERROR("failed to setup rootfs for '%s'", name); return -1; } + if (lxc_conf->autodev) { + if (mount_autodev(lxc_conf->rootfs.mount)) { + ERROR("failed to mount /dev in the container"); + return -1; + } + } + if (setup_mount(&lxc_conf->rootfs, lxc_conf->fstab, name)) { ERROR("failed to setup the mounts for '%s'", name); return -1; @@ -2186,6 +2431,13 @@ int lxc_setup(const char *name, struct lxc_conf *lxc_conf) return -1; } + if (lxc_conf->autodev) { + if (setup_autodev(lxc_conf->rootfs.mount)) { + ERROR("failed to populate /dev in the container"); + return -1; + } + } + if (setup_cgroup(name, &lxc_conf->cgroup)) { ERROR("failed to setup the cgroups for '%s'", name); return -1; @@ -2196,10 +2448,8 @@ int lxc_setup(const char *name, struct lxc_conf *lxc_conf) return -1; } - if (setup_kmsg(&lxc_conf->rootfs, &lxc_conf->console)) { + if (setup_kmsg(&lxc_conf->rootfs, &lxc_conf->console)) // don't fail ERROR("failed to setup kmsg for '%s'", name); - return -1; - } if (setup_tty(&lxc_conf->rootfs, &lxc_conf->tty_info, lxc_conf->ttydir)) { ERROR("failed to setup the ttys for '%s'", name); @@ -2253,6 +2503,8 @@ int run_lxc_hooks(const char *name, char *hook, struct lxc_conf *conf) if (strcmp(hook, "pre-start") == 0) which = LXCHOOK_PRESTART; + else if (strcmp(hook, "pre-mount") == 0) + which = LXCHOOK_PREMOUNT; else if (strcmp(hook, "mount") == 0) which = LXCHOOK_MOUNT; else if (strcmp(hook, "start") == 0) @@ -2270,3 +2522,253 @@ int run_lxc_hooks(const char *name, char *hook, struct lxc_conf *conf) } return 0; } + +static void lxc_remove_nic(struct lxc_list *it) +{ + struct lxc_netdev *netdev = it->elem; + struct lxc_list *it2,*next; + + lxc_list_del(it); + + if (netdev->link) + free(netdev->link); + if (netdev->name) + free(netdev->name); + if (netdev->upscript) + free(netdev->upscript); + if (netdev->hwaddr) + free(netdev->hwaddr); + if (netdev->mtu) + free(netdev->mtu); + if (netdev->ipv4_gateway) + free(netdev->ipv4_gateway); + if (netdev->ipv6_gateway) + free(netdev->ipv6_gateway); + lxc_list_for_each_safe(it2, &netdev->ipv4, next) { + lxc_list_del(it2); + free(it2->elem); + free(it2); + } + lxc_list_for_each_safe(it2, &netdev->ipv6, next) { + lxc_list_del(it2); + free(it2->elem); + free(it2); + } + free(netdev); + free(it); +} + +/* we get passed in something like '0', '0.ipv4' or '1.ipv6' */ +int lxc_clear_nic(struct lxc_conf *c, const char *key) +{ + char *p1; + int ret, idx, i; + struct lxc_list *it; + struct lxc_netdev *netdev; + + p1 = index(key, '.'); + if (!p1 || *(p1+1) == '\0') + p1 = NULL; + + ret = sscanf(key, "%d", &idx); + if (ret != 1) return -1; + if (idx < 0) + return -1; + + i = 0; + lxc_list_for_each(it, &c->network) { + if (i == idx) + break; + i++; + } + if (i < idx) // we don't have that many nics defined + return -1; + + if (!it || !it->elem) + return -1; + + netdev = it->elem; + + if (!p1) { + lxc_remove_nic(it); + } else if (strcmp(p1, "ipv4") == 0) { + struct lxc_list *it2,*next; + lxc_list_for_each_safe(it2, &netdev->ipv4, next) { + lxc_list_del(it2); + free(it2->elem); + free(it2); + } + } else if (strcmp(p1, "ipv6") == 0) { + struct lxc_list *it2,*next; + lxc_list_for_each_safe(it2, &netdev->ipv6, next) { + lxc_list_del(it2); + free(it2->elem); + free(it2); + } + } else if (strcmp(p1, "link") == 0) { + if (netdev->link) { + free(netdev->link); + netdev->link = NULL; + } + } else if (strcmp(p1, "name") == 0) { + if (netdev->name) { + free(netdev->name); + netdev->name = NULL; + } + } else if (strcmp(p1, "script.up") == 0) { + if (netdev->upscript) { + free(netdev->upscript); + netdev->upscript = NULL; + } + } else if (strcmp(p1, "hwaddr") == 0) { + if (netdev->hwaddr) { + free(netdev->hwaddr); + netdev->hwaddr = NULL; + } + } else if (strcmp(p1, "mtu") == 0) { + if (netdev->mtu) { + free(netdev->mtu); + netdev->mtu = NULL; + } + } else if (strcmp(p1, "ipv4_gateway") == 0) { + if (netdev->ipv4_gateway) { + free(netdev->ipv4_gateway); + netdev->ipv4_gateway = NULL; + } + } else if (strcmp(p1, "ipv6_gateway") == 0) { + if (netdev->ipv6_gateway) { + free(netdev->ipv6_gateway); + netdev->ipv6_gateway = NULL; + } + } + else return -1; + + return 0; +} + +int lxc_clear_config_network(struct lxc_conf *c) +{ + struct lxc_list *it,*next; + lxc_list_for_each_safe(it, &c->network, next) { + lxc_remove_nic(it); + } + return 0; +} + +int lxc_clear_config_caps(struct lxc_conf *c) +{ + struct lxc_list *it,*next; + + lxc_list_for_each_safe(it, &c->caps, next) { + lxc_list_del(it); + free(it->elem); + free(it); + } + return 0; +} + +int lxc_clear_cgroups(struct lxc_conf *c, const char *key) +{ + struct lxc_list *it,*next; + bool all = false; + const char *k = key + 11; + + if (strcmp(key, "lxc.cgroup") == 0) + all = true; + + lxc_list_for_each_safe(it, &c->cgroup, next) { + struct lxc_cgroup *cg = it->elem; + if (!all && strcmp(cg->subsystem, k) != 0) + continue; + lxc_list_del(it); + free(cg->subsystem); + free(cg->value); + free(cg); + free(it); + } + return 0; +} + +int lxc_clear_mount_entries(struct lxc_conf *c) +{ + struct lxc_list *it,*next; + + lxc_list_for_each_safe(it, &c->mount_list, next) { + lxc_list_del(it); + free(it->elem); + free(it); + } + return 0; +} + +int lxc_clear_hooks(struct lxc_conf *c, const char *key) +{ + struct lxc_list *it,*next; + bool all = false, done = false; + const char *k = key + 9; + int i; + + if (strcmp(key, "lxc.hook") == 0) + all = true; + + for (i=0; ihooks[i], next) { + lxc_list_del(it); + free(it->elem); + free(it); + } + done = true; + } + } + + if (!done) { + ERROR("Invalid hook key: %s", key); + return -1; + } + return 0; +} + +void lxc_clear_saved_nics(struct lxc_conf *conf) +{ + int i; + + if (!conf->num_savednics) + return; + for (i=0; i < conf->num_savednics; i++) + free(conf->saved_nics[i].orig_name); + conf->saved_nics = 0; + free(conf->saved_nics); +} + +void lxc_conf_free(struct lxc_conf *conf) +{ + if (!conf) + return; + if (conf->console.path) + free(conf->console.path); + if (conf->rootfs.mount != default_rootfs_mount) + free(conf->rootfs.mount); + if (conf->rootfs.path) + free(conf->rootfs.path); + if (conf->utsname) + free(conf->utsname); + if (conf->ttydir) + free(conf->ttydir); + if (conf->fstab) + free(conf->fstab); + if (conf->logfile) + free(conf->logfile); + lxc_clear_config_network(conf); +#if HAVE_APPARMOR + if (conf->aa_profile) + free(conf->aa_profile); +#endif + lxc_seccomp_free(conf); + lxc_clear_config_caps(conf); + lxc_clear_cgroups(conf, "lxc.cgroup"); + lxc_clear_hooks(conf, "lxc.hook"); + lxc_clear_mount_entries(conf); + lxc_clear_saved_nics(conf); + free(conf); +} diff --git a/src/lxc/conf.h b/src/lxc/conf.h index eee12b7b5..ca4dbc211 100644 --- a/src/lxc/conf.h +++ b/src/lxc/conf.h @@ -24,6 +24,7 @@ #define _conf_h #include +#include #include #include @@ -31,6 +32,10 @@ #include /* for lxc_handler */ +#if HAVE_SCMP_FILTER_CTX +typedef void * scmp_filter_ctx; +#endif + enum { LXC_NET_EMPTY, LXC_NET_VETH, @@ -76,6 +81,7 @@ struct lxc_route6 { struct ifla_veth { char *pair; /* pair name */ + char veth1[IFNAMSIZ]; /* needed for deconf */ }; struct ifla_vlan { @@ -103,6 +109,7 @@ union netdev_p { * @ipv4 : a list of ipv4 addresses to be set on the network device * @ipv6 : a list of ipv6 addresses to be set on the network device * @upscript : a script filename to be executed during interface configuration + * @downscript : a script filename to be executed during interface destruction */ struct lxc_netdev { int type; @@ -120,6 +127,7 @@ struct lxc_netdev { struct in6_addr *ipv6_gateway; bool ipv6_gateway_auto; char *upscript; + char *downscript; }; /* @@ -203,8 +211,15 @@ struct lxc_rootfs { #endif */ enum lxchooks { - LXCHOOK_PRESTART, LXCHOOK_MOUNT, LXCHOOK_START, + LXCHOOK_PRESTART, LXCHOOK_PREMOUNT, LXCHOOK_MOUNT, LXCHOOK_START, LXCHOOK_POSTSTOP, NUM_LXC_HOOKS}; +extern char *lxchook_names[NUM_LXC_HOOKS]; + +struct saved_nic { + int ifindex; + char *orig_name; +}; + struct lxc_conf { char *fstab; int tty; @@ -215,6 +230,8 @@ struct lxc_conf { struct utsname *utsname; struct lxc_list cgroup; struct lxc_list network; + struct saved_nic *saved_nics; + int num_savednics; struct lxc_list mount_list; struct lxc_list caps; struct lxc_tty_info tty_info; @@ -226,9 +243,18 @@ struct lxc_conf { #if HAVE_APPARMOR char *aa_profile; #endif + char *logfile; + int loglevel; + #if HAVE_APPARMOR /* || HAVE_SELINUX || HAVE_SMACK */ int lsm_umount_proc; #endif + char *seccomp; // filename with the seccomp rules +#if HAVE_SCMP_FILTER_CTX + scmp_filter_ctx *seccomp_ctx; +#endif + int maincmd_fd; + int autodev; // if 1, mount and fill a /dev at start }; int run_lxc_hooks(const char *name, char *hook, struct lxc_conf *conf); @@ -237,20 +263,30 @@ int run_lxc_hooks(const char *name, char *hook, struct lxc_conf *conf); * Initialize the lxc configuration structure */ extern struct lxc_conf *lxc_conf_init(void); +extern void lxc_conf_free(struct lxc_conf *conf); extern int pin_rootfs(const char *rootfs); extern int lxc_create_network(struct lxc_handler *handler); -extern void lxc_delete_network(struct lxc_list *networks); +extern void lxc_delete_network(struct lxc_handler *handler); extern int lxc_assign_network(struct lxc_list *networks, pid_t pid); extern int lxc_find_gateway_addresses(struct lxc_handler *handler); extern int lxc_create_tty(const char *name, struct lxc_conf *conf); extern void lxc_delete_tty(struct lxc_tty_info *tty_info); +extern int lxc_clear_config_network(struct lxc_conf *c); +extern int lxc_clear_nic(struct lxc_conf *c, const char *key); +extern int lxc_clear_config_caps(struct lxc_conf *c); +extern int lxc_clear_cgroups(struct lxc_conf *c, const char *key); +extern int lxc_clear_mount_entries(struct lxc_conf *c); +extern int lxc_clear_hooks(struct lxc_conf *c, const char *key); + /* * Configure the container from inside */ extern int lxc_setup(const char *name, struct lxc_conf *lxc_conf); + +extern void lxc_rename_phys_nics_on_shutdown(struct lxc_conf *conf); #endif diff --git a/src/lxc/confile.c b/src/lxc/confile.c index 4f816f55b..1d87227b8 100644 --- a/src/lxc/confile.c +++ b/src/lxc/confile.c @@ -20,6 +20,7 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#define _GNU_SOURCE #include #include #include @@ -42,48 +43,48 @@ #include #include +#include "network.h" lxc_log_define(lxc_confile, lxc); -static int config_personality(const char *, char *, struct lxc_conf *); -static int config_pts(const char *, char *, struct lxc_conf *); -static int config_tty(const char *, char *, struct lxc_conf *); -static int config_ttydir(const char *, char *, struct lxc_conf *); +static int config_personality(const char *, const char *, struct lxc_conf *); +static int config_pts(const char *, const char *, struct lxc_conf *); +static int config_tty(const char *, const char *, struct lxc_conf *); +static int config_ttydir(const char *, const char *, struct lxc_conf *); #if HAVE_APPARMOR -static int config_aa_profile(const char *, char *, struct lxc_conf *); +static int config_aa_profile(const char *, const char *, struct lxc_conf *); #endif -static int config_cgroup(const char *, char *, struct lxc_conf *); -static int config_mount(const char *, char *, struct lxc_conf *); -static int config_rootfs(const char *, char *, struct lxc_conf *); -static int config_rootfs_mount(const char *, char *, struct lxc_conf *); -static int config_pivotdir(const char *, char *, struct lxc_conf *); -static int config_utsname(const char *, char *, struct lxc_conf *); -static int config_hook(const char *key, char *value, struct lxc_conf *lxc_conf); -static int config_network_type(const char *, char *, struct lxc_conf *); -static int config_network_flags(const char *, char *, struct lxc_conf *); -static int config_network_link(const char *, char *, struct lxc_conf *); -static int config_network_name(const char *, char *, struct lxc_conf *); -static int config_network_veth_pair(const char *, char *, struct lxc_conf *); -static int config_network_macvlan_mode(const char *, char *, struct lxc_conf *); -static int config_network_hwaddr(const char *, char *, struct lxc_conf *); -static int config_network_vlan_id(const char *, char *, struct lxc_conf *); -static int config_network_mtu(const char *, char *, struct lxc_conf *); -static int config_network_ipv4(const char *, char *, struct lxc_conf *); -static int config_network_ipv4_gateway(const char *, char *, struct lxc_conf *); -static int config_network_script(const char *, char *, struct lxc_conf *); -static int config_network_ipv6(const char *, char *, struct lxc_conf *); -static int config_network_ipv6_gateway(const char *, char *, struct lxc_conf *); -static int config_cap_drop(const char *, char *, struct lxc_conf *); -static int config_console(const char *, char *, struct lxc_conf *); +static int config_cgroup(const char *, const char *, struct lxc_conf *); +static int config_loglevel(const char *, const char *, struct lxc_conf *); +static int config_logfile(const char *, const char *, struct lxc_conf *); +static int config_mount(const char *, const char *, struct lxc_conf *); +static int config_rootfs(const char *, const char *, struct lxc_conf *); +static int config_rootfs_mount(const char *, const char *, struct lxc_conf *); +static int config_pivotdir(const char *, const char *, struct lxc_conf *); +static int config_utsname(const char *, const char *, struct lxc_conf *); +static int config_hook(const char *, const char *, struct lxc_conf *lxc_conf); +static int config_network_type(const char *, const char *, struct lxc_conf *); +static int config_network_flags(const char *, const char *, struct lxc_conf *); +static int config_network_link(const char *, const char *, struct lxc_conf *); +static int config_network_name(const char *, const char *, struct lxc_conf *); +static int config_network_veth_pair(const char *, const char *, struct lxc_conf *); +static int config_network_macvlan_mode(const char *, const char *, struct lxc_conf *); +static int config_network_hwaddr(const char *, const char *, struct lxc_conf *); +static int config_network_vlan_id(const char *, const char *, struct lxc_conf *); +static int config_network_mtu(const char *, const char *, struct lxc_conf *); +static int config_network_ipv4(const char *, const char *, struct lxc_conf *); +static int config_network_ipv4_gateway(const char *, const char *, struct lxc_conf *); +static int config_network_script(const char *, const char *, struct lxc_conf *); +static int config_network_ipv6(const char *, const char *, struct lxc_conf *); +static int config_network_ipv6_gateway(const char *, const char *, struct lxc_conf *); +static int config_cap_drop(const char *, const char *, struct lxc_conf *); +static int config_console(const char *, const char *, struct lxc_conf *); +static int config_seccomp(const char *, const char *, struct lxc_conf *); +static int config_includefile(const char *, const char *, struct lxc_conf *); +static int config_network_nic(const char *, const char *, struct lxc_conf *); +static int config_autodev(const char *, const char *, struct lxc_conf *); -typedef int (*config_cb)(const char *, char *, struct lxc_conf *); - -struct config { - char *name; - config_cb cb; -}; - -static struct config config[] = { +static struct lxc_config_t config[] = { { "lxc.arch", config_personality }, { "lxc.pts", config_pts }, @@ -93,12 +94,15 @@ static struct config config[] = { { "lxc.aa_profile", config_aa_profile }, #endif { "lxc.cgroup", config_cgroup }, + { "lxc.loglevel", config_loglevel }, + { "lxc.logfile", config_logfile }, { "lxc.mount", config_mount }, { "lxc.rootfs.mount", config_rootfs_mount }, { "lxc.rootfs", config_rootfs }, { "lxc.pivotdir", config_pivotdir }, { "lxc.utsname", config_utsname }, { "lxc.hook.pre-start", config_hook }, + { "lxc.hook.pre-mount", config_hook }, { "lxc.hook.mount", config_hook }, { "lxc.hook.start", config_hook }, { "lxc.hook.post-stop", config_hook }, @@ -109,6 +113,7 @@ static struct config config[] = { { "lxc.network.macvlan.mode", config_network_macvlan_mode }, { "lxc.network.veth.pair", config_network_veth_pair }, { "lxc.network.script.up", config_network_script }, + { "lxc.network.script.down", config_network_script }, { "lxc.network.hwaddr", config_network_hwaddr }, { "lxc.network.mtu", config_network_mtu }, { "lxc.network.vlan.id", config_network_vlan_id }, @@ -116,13 +121,18 @@ static struct config config[] = { { "lxc.network.ipv4", config_network_ipv4 }, { "lxc.network.ipv6.gateway", config_network_ipv6_gateway }, { "lxc.network.ipv6", config_network_ipv6 }, + /* config_network_nic must come after all other 'lxc.network.*' entries */ + { "lxc.network.", config_network_nic }, { "lxc.cap.drop", config_cap_drop }, { "lxc.console", config_console }, + { "lxc.seccomp", config_seccomp }, + { "lxc.include", config_includefile }, + { "lxc.autodev", config_autodev }, }; -static const size_t config_size = sizeof(config)/sizeof(struct config); +static const size_t config_size = sizeof(config)/sizeof(struct lxc_config_t); -static struct config *getconfig(const char *key) +extern struct lxc_config_t *lxc_getconfig(const char *key) { int i; @@ -133,7 +143,77 @@ static struct config *getconfig(const char *key) return NULL; } -static int config_network_type(const char *key, char *value, +#define strprint(str, inlen, ...) \ + do { \ + len = snprintf(str, inlen, ##__VA_ARGS__); \ + if (len < 0) { SYSERROR("snprintf"); return -1; }; \ + fulllen += len; \ + if (inlen > 0) { \ + if (str) str += len; \ + inlen -= len; \ + if (inlen < 0) inlen = 0; \ + } \ + } while (0); + +int lxc_listconfigs(char *retv, int inlen) +{ + int i, fulllen = 0, len; + + if (!retv) + inlen = 0; + else + memset(retv, 0, inlen); + for (i = 0; i < config_size; i++) { + char *s = config[i].name; + if (s[strlen(s)-1] == '.') + continue; + strprint(retv, inlen, "%s\n", s); + } + return fulllen; +} + +/* + * config entry is something like "lxc.network.0.ipv4" + * the key 'lxc.network.' was found. So we make sure next + * comes an integer, find the right callback (by rewriting + * the key), and call it. + */ +static int config_network_nic(const char *key, const char *value, + struct lxc_conf *lxc_conf) +{ + char *copy = strdup(key), *p; + int ret = -1; + struct lxc_config_t *config; + + if (!copy) { + SYSERROR("failed to allocate memory"); + return -1; + } + /* + * ok we know that to get here we've got "lxc.network." + * and it isn't any of the other network entries. So + * after the second . should come an integer (# of defined + * nic) followed by a valid entry. + */ + if (*(key+12) < '0' || *(key+12) > '9') + goto out; + p = index(key+12, '.'); + if (!p) + goto out; + strcpy(copy+12, p+1); + config = lxc_getconfig(copy); + if (!config) { + ERROR("unknown key %s", key); + goto out; + } + ret = config->cb(key, value, lxc_conf); + +out: + free(copy); + return ret; +} + +static int config_network_type(const char *key, const char *value, struct lxc_conf *lxc_conf) { struct lxc_list *network = &lxc_conf->network; @@ -190,10 +270,90 @@ static int config_ip_prefix(struct in_addr *addr) return 0; } +/* + * if you have p="lxc.network.0.link", pass it p+12 + * to get back '0' (the index of the nic) + */ +static int get_network_netdev_idx(const char *key) +{ + int ret, idx; + + if (*key < '0' || *key > '9') + return -1; + ret = sscanf(key, "%d", &idx); + if (ret != 1) + return -1; + return idx; +} + +/* + * if you have p="lxc.network.0", pass this p+12 and it will return + * the netdev of the first configured nic + */ +static struct lxc_netdev *get_netdev_from_key(const char *key, + struct lxc_list *network) +{ + int i = 0, idx = get_network_netdev_idx(key); + struct lxc_netdev *netdev = NULL; + struct lxc_list *it; + if (idx == -1) + return NULL; + lxc_list_for_each(it, network) { + if (idx == i++) { + netdev = it->elem; + break; + } + } + return netdev; +} + +extern int lxc_list_nicconfigs(struct lxc_conf *c, const char *key, + char *retv, int inlen) +{ + struct lxc_netdev *netdev; + int fulllen = 0, len; + + netdev = get_netdev_from_key(key+12, &c->network); + if (!netdev) + return -1; + + if (!retv) + inlen = 0; + else + memset(retv, 0, inlen); + + strprint(retv, inlen, "script.up\n"); + if (netdev->type != LXC_NET_EMPTY) { + strprint(retv, inlen, "flags\n"); + strprint(retv, inlen, "link\n"); + strprint(retv, inlen, "name\n"); + strprint(retv, inlen, "hwaddr\n"); + strprint(retv, inlen, "mtu\n"); + strprint(retv, inlen, "ipv6\n"); + strprint(retv, inlen, "ipv6_gateway\n"); + strprint(retv, inlen, "ipv4\n"); + strprint(retv, inlen, "ipv4_gateway\n"); + } + switch(netdev->type) { + case LXC_NET_VETH: + strprint(retv, inlen, "veth.pair\n"); + break; + case LXC_NET_MACVLAN: + strprint(retv, inlen, "macvlan.mode\n"); + break; + case LXC_NET_VLAN: + strprint(retv, inlen, "vlan.id\n"); + break; + case LXC_NET_PHYS: + break; + } + return fulllen; +} + static struct lxc_netdev *network_netdev(const char *key, const char *value, struct lxc_list *network) { - struct lxc_netdev *netdev; + struct lxc_netdev *netdev = NULL; if (lxc_list_empty(network)) { ERROR("network is not created for '%s' = '%s' option", @@ -201,7 +361,11 @@ static struct lxc_netdev *network_netdev(const char *key, const char *value, return NULL; } - netdev = lxc_list_last_elem(network); + if (get_network_netdev_idx(key+12) == -1) + netdev = lxc_list_last_elem(network); + else + netdev = get_netdev_from_key(key+12, network); + if (!netdev) { ERROR("no network device defined for '%s' = '%s' option", key, value); @@ -211,7 +375,7 @@ static struct lxc_netdev *network_netdev(const char *key, const char *value, return netdev; } -static int network_ifname(char **valuep, char *value) +static int network_ifname(char **valuep, const char *value) { if (strlen(value) >= IFNAMSIZ) { ERROR("interface name '%s' too long (>%d)\n", @@ -240,7 +404,7 @@ static int network_ifname(char **valuep, char *value) # define MACVLAN_MODE_BRIDGE 4 #endif -static int macvlan_mode(int *valuep, char *value) +static int macvlan_mode(int *valuep, const char *value) { struct mc_mode { char *name; @@ -264,7 +428,7 @@ static int macvlan_mode(int *valuep, char *value) return -1; } -static int config_network_flags(const char *key, char *value, +static int config_network_flags(const char *key, const char *value, struct lxc_conf *lxc_conf) { struct lxc_netdev *netdev; @@ -278,7 +442,7 @@ static int config_network_flags(const char *key, char *value, return 0; } -static int config_network_link(const char *key, char *value, +static int config_network_link(const char *key, const char *value, struct lxc_conf *lxc_conf) { struct lxc_netdev *netdev; @@ -290,7 +454,7 @@ static int config_network_link(const char *key, char *value, return network_ifname(&netdev->link, value); } -static int config_network_name(const char *key, char *value, +static int config_network_name(const char *key, const char *value, struct lxc_conf *lxc_conf) { struct lxc_netdev *netdev; @@ -302,7 +466,7 @@ static int config_network_name(const char *key, char *value, return network_ifname(&netdev->name, value); } -static int config_network_veth_pair(const char *key, char *value, +static int config_network_veth_pair(const char *key, const char *value, struct lxc_conf *lxc_conf) { struct lxc_netdev *netdev; @@ -314,7 +478,7 @@ static int config_network_veth_pair(const char *key, char *value, return network_ifname(&netdev->priv.veth_attr.pair, value); } -static int config_network_macvlan_mode(const char *key, char *value, +static int config_network_macvlan_mode(const char *key, const char *value, struct lxc_conf *lxc_conf) { struct lxc_netdev *netdev; @@ -326,25 +490,29 @@ static int config_network_macvlan_mode(const char *key, char *value, return macvlan_mode(&netdev->priv.macvlan_attr.mode, value); } -static int config_network_hwaddr(const char *key, char *value, +static int config_network_hwaddr(const char *key, const char *value, struct lxc_conf *lxc_conf) { struct lxc_netdev *netdev; + char *hwaddr; netdev = network_netdev(key, value, &lxc_conf->network); if (!netdev) return -1; - netdev->hwaddr = strdup(value); - if (!netdev->hwaddr) { + hwaddr = strdup(value); + if (!hwaddr) { SYSERROR("failed to dup string '%s'", value); return -1; } + if (netdev->hwaddr) + free(netdev->hwaddr); + netdev->hwaddr = hwaddr; return 0; } -static int config_network_vlan_id(const char *key, char *value, +static int config_network_vlan_id(const char *key, const char *value, struct lxc_conf *lxc_conf) { struct lxc_netdev *netdev; @@ -359,25 +527,29 @@ static int config_network_vlan_id(const char *key, char *value, return 0; } -static int config_network_mtu(const char *key, char *value, +static int config_network_mtu(const char *key, const char *value, struct lxc_conf *lxc_conf) { struct lxc_netdev *netdev; + char *mtu; netdev = network_netdev(key, value, &lxc_conf->network); if (!netdev) return -1; - netdev->mtu = strdup(value); - if (!netdev->mtu) { + mtu = strdup(value); + if (!mtu) { SYSERROR("failed to dup string '%s'", value); return -1; } + if (netdev->mtu) + free(netdev->mtu); + netdev->mtu = mtu; return 0; } -static int config_network_ipv4(const char *key, char *value, +static int config_network_ipv4(const char *key, const char *value, struct lxc_conf *lxc_conf) { struct lxc_netdev *netdev; @@ -405,7 +577,7 @@ static int config_network_ipv4(const char *key, char *value, lxc_list_init(list); list->elem = inetdev; - addr = value; + addr = strdupa(value); cursor = strstr(addr, " "); if (cursor) { @@ -452,7 +624,7 @@ static int config_network_ipv4(const char *key, char *value, return 0; } -static int config_network_ipv4_gateway(const char *key, char *value, +static int config_network_ipv4_gateway(const char *key, const char *value, struct lxc_conf *lxc_conf) { struct lxc_netdev *netdev; @@ -489,13 +661,13 @@ static int config_network_ipv4_gateway(const char *key, char *value, return 0; } -static int config_network_ipv6(const char *key, char *value, +static int config_network_ipv6(const char *key, const char *value, struct lxc_conf *lxc_conf) { struct lxc_netdev *netdev; struct lxc_inet6dev *inet6dev; struct lxc_list *list; - char *slash; + char *slash,*valdup; char *netmask; netdev = network_netdev(key, value, &lxc_conf->network); @@ -518,8 +690,9 @@ static int config_network_ipv6(const char *key, char *value, lxc_list_init(list); list->elem = inet6dev; + valdup = strdupa(value); inet6dev->prefix = 64; - slash = strstr(value, "/"); + slash = strstr(valdup, "/"); if (slash) { *slash = '\0'; netmask = slash + 1; @@ -536,7 +709,7 @@ static int config_network_ipv6(const char *key, char *value, return 0; } -static int config_network_ipv6_gateway(const char *key, char *value, +static int config_network_ipv6_gateway(const char *key, const char *value, struct lxc_conf *lxc_conf) { struct lxc_netdev *netdev; @@ -573,7 +746,7 @@ static int config_network_ipv6_gateway(const char *key, char *value, return 0; } -static int config_network_script(const char *key, char *value, +static int config_network_script(const char *key, const char *value, struct lxc_conf *lxc_conf) { struct lxc_netdev *netdev; @@ -587,10 +760,14 @@ static int config_network_script(const char *key, char *value, SYSERROR("failed to dup string '%s'", value); return -1; } - if (strcmp(key, "lxc.network.script.up") == 0) { + if (strstr(key, "script.up") != NULL) { netdev->upscript = copy; return 0; } + if (strcmp(key, "lxc.network.script.down") == 0) { + netdev->downscript = copy; + return 0; + } SYSERROR("Unknown key: %s", key); free(copy); return -1; @@ -610,7 +787,29 @@ static int add_hook(struct lxc_conf *lxc_conf, int which, char *hook) return 0; } -static int config_hook(const char *key, char *value, +static int config_seccomp(const char *key, const char *value, + struct lxc_conf *lxc_conf) +{ + char *path; + + if (lxc_conf->seccomp) { + ERROR("seccomp already defined"); + return -1; + } + path = strdup(value); + if (!path) { + SYSERROR("failed to strdup '%s': %m", value); + return -1; + } + + if (lxc_conf->seccomp) + free(lxc_conf->seccomp); + lxc_conf->seccomp = path; + + return 0; +} + +static int config_hook(const char *key, const char *value, struct lxc_conf *lxc_conf) { char *copy = strdup(value); @@ -620,6 +819,8 @@ static int config_hook(const char *key, char *value, } if (strcmp(key, "lxc.hook.pre-start") == 0) return add_hook(lxc_conf, LXCHOOK_PRESTART, copy); + else if (strcmp(key, "lxc.hook.pre-mount") == 0) + return add_hook(lxc_conf, LXCHOOK_PREMOUNT, copy); else if (strcmp(key, "lxc.hook.mount") == 0) return add_hook(lxc_conf, LXCHOOK_MOUNT, copy); else if (strcmp(key, "lxc.hook.start") == 0) @@ -631,7 +832,7 @@ static int config_hook(const char *key, char *value, return -1; } -static int config_personality(const char *key, char *value, +static int config_personality(const char *key, const const char *value, struct lxc_conf *lxc_conf) { signed long personality = lxc_config_parse_arch(value); @@ -644,7 +845,8 @@ static int config_personality(const char *key, char *value, return 0; } -static int config_pts(const char *key, char *value, struct lxc_conf *lxc_conf) +static int config_pts(const char *key, const char *value, + struct lxc_conf *lxc_conf) { int maxpts = atoi(value); @@ -653,7 +855,8 @@ static int config_pts(const char *key, char *value, struct lxc_conf *lxc_conf) return 0; } -static int config_tty(const char *key, char *value, struct lxc_conf *lxc_conf) +static int config_tty(const char *key, const char *value, + struct lxc_conf *lxc_conf) { int nbtty = atoi(value); @@ -662,7 +865,7 @@ static int config_tty(const char *key, char *value, struct lxc_conf *lxc_conf) return 0; } -static int config_ttydir(const char *key, char *value, +static int config_ttydir(const char *key, const char *value, struct lxc_conf *lxc_conf) { char *path; @@ -675,13 +878,16 @@ static int config_ttydir(const char *key, char *value, return -1; } + if (lxc_conf->ttydir) + free(lxc_conf->ttydir); lxc_conf->ttydir = path; return 0; } #if HAVE_APPARMOR -static int config_aa_profile(const char *key, char *value, struct lxc_conf *lxc_conf) +static int config_aa_profile(const char *key, const char *value, + struct lxc_conf *lxc_conf) { char *path; @@ -693,13 +899,71 @@ static int config_aa_profile(const char *key, char *value, struct lxc_conf *lxc_ return -1; } + if (lxc_conf->aa_profile) + free(lxc_conf->aa_profile); lxc_conf->aa_profile = path; return 0; } #endif -static int config_cgroup(const char *key, char *value, struct lxc_conf *lxc_conf) +static int config_logfile(const char *key, const char *value, + struct lxc_conf *lxc_conf) +{ + char *path; + + // if given a blank entry, null out any previous entries. + if (!value || strlen(value) == 0) { + if (lxc_conf->logfile) { + free(lxc_conf->logfile); + lxc_conf->logfile = NULL; + } + return 0; + } + + path = strdup(value); + if (!path) { + SYSERROR("failed to strdup '%s': %m", value); + return -1; + } + + if (lxc_log_set_file(path)) { + free(path); + return -1; + } + + if (lxc_conf->logfile) + free(lxc_conf->logfile); + lxc_conf->logfile = path; + + return 0; +} + +static int config_loglevel(const char *key, const char *value, + struct lxc_conf *lxc_conf) +{ + if (!value || strlen(value) == 0) + return 0; + + if (value[0] >= '0' && value[0] <= '9') + lxc_conf->loglevel = atoi(value); + else + lxc_conf->loglevel = lxc_log_priority_to_int(value); + return lxc_log_set_level(lxc_conf->loglevel); +} + +static int config_autodev(const char *key, const char *value, + struct lxc_conf *lxc_conf) +{ + int v = atoi(value); + + lxc_conf->autodev = v; + + return 0; +} + +static int config_cgroup(const char *key, const char *value, + struct lxc_conf *lxc_conf) { char *token = "lxc.cgroup."; char *subkey; @@ -757,23 +1021,35 @@ out: return -1; } -static int config_fstab(const char *key, char *value, struct lxc_conf *lxc_conf) +static int config_path_item(const char *key, const char *value, + struct lxc_conf *lxc_conf, char **conf_item) { + char *valdup; if (strlen(value) >= MAXPATHLEN) { ERROR("%s path is too long", value); return -1; } - lxc_conf->fstab = strdup(value); - if (!lxc_conf->fstab) { + valdup = strdup(value); + if (!valdup) { SYSERROR("failed to duplicate string %s", value); return -1; } + if (*conf_item) + free(*conf_item); + *conf_item = valdup; return 0; } -static int config_mount(const char *key, char *value, struct lxc_conf *lxc_conf) +static int config_fstab(const char *key, const char *value, + struct lxc_conf *lxc_conf) +{ + return config_path_item(key, value, lxc_conf, &lxc_conf->fstab); +} + +static int config_mount(const char *key, const char *value, + struct lxc_conf *lxc_conf) { char *fstab_token = "lxc.mount"; char *token = "lxc.mount.entry"; @@ -809,10 +1085,10 @@ static int config_mount(const char *key, char *value, struct lxc_conf *lxc_conf) return 0; } -static int config_cap_drop(const char *key, char *value, +static int config_cap_drop(const char *key, const char *value, struct lxc_conf *lxc_conf) { - char *dropcaps, *sptr, *token; + char *dropcaps, *dropptr, *sptr, *token; struct lxc_list *droplist; int ret = -1; @@ -827,13 +1103,12 @@ static int config_cap_drop(const char *key, char *value, /* in case several capability drop is specified in a single line * split these caps in a single element for the list */ - for (;;) { - token = strtok_r(dropcaps, " \t", &sptr); + for (dropptr = dropcaps;;dropptr = NULL) { + token = strtok_r(dropptr, " \t", &sptr); if (!token) { ret = 0; break; } - dropcaps = NULL; droplist = malloc(sizeof(*droplist)); if (!droplist) { @@ -856,7 +1131,7 @@ static int config_cap_drop(const char *key, char *value, return ret; } -static int config_console(const char *key, char *value, +static int config_console(const char *key, const char *value, struct lxc_conf *lxc_conf) { char *path; @@ -867,60 +1142,39 @@ static int config_console(const char *key, char *value, return -1; } + if (lxc_conf->console.path) + free(lxc_conf->console.path); lxc_conf->console.path = path; return 0; } -static int config_rootfs(const char *key, char *value, struct lxc_conf *lxc_conf) +static int config_includefile(const char *key, const char *value, + struct lxc_conf *lxc_conf) { - if (strlen(value) >= MAXPATHLEN) { - ERROR("%s path is too long", value); - return -1; - } - - lxc_conf->rootfs.path = strdup(value); - if (!lxc_conf->rootfs.path) { - SYSERROR("failed to duplicate string %s", value); - return -1; - } - - return 0; + return lxc_config_read(value, lxc_conf); } -static int config_rootfs_mount(const char *key, char *value, struct lxc_conf *lxc_conf) +static int config_rootfs(const char *key, const char *value, + struct lxc_conf *lxc_conf) { - if (strlen(value) >= MAXPATHLEN) { - ERROR("%s path is too long", value); - return -1; - } - - lxc_conf->rootfs.mount = strdup(value); - if (!lxc_conf->rootfs.mount) { - SYSERROR("failed to duplicate string '%s'", value); - return -1; - } - - return 0; + return config_path_item(key, value, lxc_conf, &lxc_conf->rootfs.path); } -static int config_pivotdir(const char *key, char *value, struct lxc_conf *lxc_conf) +static int config_rootfs_mount(const char *key, const char *value, + struct lxc_conf *lxc_conf) { - if (strlen(value) >= MAXPATHLEN) { - ERROR("%s path is too long", value); - return -1; - } - - lxc_conf->rootfs.pivot = strdup(value); - if (!lxc_conf->rootfs.pivot) { - SYSERROR("failed to duplicate string %s", value); - return -1; - } - - return 0; + return config_path_item(key, value, lxc_conf, &lxc_conf->rootfs.mount); } -static int config_utsname(const char *key, char *value, struct lxc_conf *lxc_conf) +static int config_pivotdir(const char *key, const char *value, + struct lxc_conf *lxc_conf) +{ + return config_path_item(key, value, lxc_conf, &lxc_conf->rootfs.pivot); +} + +static int config_utsname(const char *key, const char *value, + struct lxc_conf *lxc_conf) { struct utsname *utsname; @@ -937,6 +1191,8 @@ static int config_utsname(const char *key, char *value, struct lxc_conf *lxc_con } strcpy(utsname->nodename, value); + if (lxc_conf->utsname) + free(lxc_conf->utsname); lxc_conf->utsname = utsname; return 0; @@ -944,7 +1200,7 @@ static int config_utsname(const char *key, char *value, struct lxc_conf *lxc_con static int parse_line(char *buffer, void *data) { - struct config *config; + struct lxc_config_t *config; char *line, *linep; char *dot; char *key; @@ -989,7 +1245,7 @@ static int parse_line(char *buffer, void *data) value += lxc_char_left_gc(value, strlen(value)); value[lxc_char_right_gc(value, strlen(value))] = '\0'; - config = getconfig(key); + config = lxc_getconfig(key); if (!config) { ERROR("unknown key %s", key); goto out; @@ -1027,7 +1283,7 @@ int lxc_config_define_add(struct lxc_list *defines, char* arg) int lxc_config_define_load(struct lxc_list *defines, struct lxc_conf *conf) { - struct lxc_list *it; + struct lxc_list *it,*next; int ret = 0; lxc_list_for_each(it, defines) { @@ -1036,7 +1292,7 @@ int lxc_config_define_load(struct lxc_list *defines, struct lxc_conf *conf) break; } - lxc_list_for_each(it, defines) { + lxc_list_for_each_safe(it, defines, next) { lxc_list_del(it); free(it); } @@ -1066,3 +1322,441 @@ signed long lxc_config_parse_arch(const char *arch) return -1; } + +static int lxc_get_conf_int(struct lxc_conf *c, char *retv, int inlen, int v) +{ + if (!retv) + inlen = 0; + else + memset(retv, 0, inlen); + return snprintf(retv, inlen, "%d", v); +} + +static int lxc_get_arch_entry(struct lxc_conf *c, char *retv, int inlen) +{ + int len, fulllen = 0; + + if (!retv) + inlen = 0; + else + memset(retv, 0, inlen); + + switch(c->personality) { + case PER_LINUX32: strprint(retv, inlen, "x86"); break; + case PER_LINUX: strprint(retv, inlen, "x86_64"); break; + default: break; + } + + return fulllen; +} + +/* + * If you ask for a specific cgroup value, i.e. lxc.cgroup.devices.list, + * then just the value(s) will be printed. Since there still could be + * more than one, it is newline-separated. + * (Maybe that's ambigous, since some values, i.e. devices.list, will + * already have newlines?) + * If you ask for 'lxc.cgroup", then all cgroup entries will be printed, + * in 'lxc.cgroup.subsystem.key = value' format. + */ +static int lxc_get_cgroup_entry(struct lxc_conf *c, char *retv, int inlen, + const char *key) +{ + int fulllen = 0, len; + int all = 0; + struct lxc_list *it; + + if (!retv) + inlen = 0; + else + memset(retv, 0, inlen); + + if (strcmp(key, "all") == 0) + all = 1; + + lxc_list_for_each(it, &c->cgroup) { + struct lxc_cgroup *cg = it->elem; + if (all) { + strprint(retv, inlen, "lxc.cgroup.%s = %s\n", cg->subsystem, cg->value); + } else if (strcmp(cg->subsystem, key) == 0) { + strprint(retv, inlen, "%s\n", cg->value); + } + } + return fulllen; +} + +static int lxc_get_item_hooks(struct lxc_conf *c, char *retv, int inlen, + const char *key) +{ + char *subkey; + int len, fulllen = 0, found = -1; + struct lxc_list *it; + int i; + + /* "lxc.hook.mount" */ + subkey = index(key, '.'); + if (subkey) subkey = index(subkey+1, '.'); + if (!subkey) + return -1; + subkey++; + if (!*subkey) + return -1; + for (i=0; ihooks[found]) { + strprint(retv, inlen, "%s\n", (char *)it->elem); + } + return fulllen; +} + +static int lxc_get_item_cap_drop(struct lxc_conf *c, char *retv, int inlen) +{ + int len, fulllen = 0; + struct lxc_list *it; + + if (!retv) + inlen = 0; + else + memset(retv, 0, inlen); + + lxc_list_for_each(it, &c->caps) { + strprint(retv, inlen, "%s\n", (char *)it->elem); + } + return fulllen; +} + +static int lxc_get_mount_entries(struct lxc_conf *c, char *retv, int inlen) +{ + int len, fulllen = 0; + struct lxc_list *it; + + if (!retv) + inlen = 0; + else + memset(retv, 0, inlen); + + lxc_list_for_each(it, &c->mount_list) { + strprint(retv, inlen, "%s\n", (char *)it->elem); + } + return fulllen; +} + +/* + * lxc.network.0.XXX, where XXX can be: name, type, link, flags, type, + * macvlan.mode, veth.pair, vlan, ipv4, ipv6, upscript, hwaddr, mtu, + * ipv4_gateway, ipv6_gateway. ipvX_gateway can return 'auto' instead + * of an address. ipv4 and ipv6 return lists (newline-separated). + * things like veth.pair return '' if invalid (i.e. if called for vlan + * type). + */ +static int lxc_get_item_nic(struct lxc_conf *c, char *retv, int inlen, + const char *key) +{ + char *p1; + int len, fulllen = 0; + struct lxc_netdev *netdev; + + if (!retv) + inlen = 0; + else + memset(retv, 0, inlen); + + p1 = index(key, '.'); + if (!p1 || *(p1+1) == '\0') return -1; + p1++; + + netdev = get_netdev_from_key(key, &c->network); + if (!netdev) + return -1; + if (strcmp(p1, "name") == 0) { + if (netdev->name) + strprint(retv, inlen, "%s", netdev->name); + } else if (strcmp(p1, "type") == 0) { + strprint(retv, inlen, "%s", lxc_net_type_to_str(netdev->type)); + } else if (strcmp(p1, "link") == 0) { + if (netdev->link) + strprint(retv, inlen, "%s", netdev->link); + } else if (strcmp(p1, "flags") == 0) { + if (netdev->flags & IFF_UP) + strprint(retv, inlen, "up"); + } else if (strcmp(p1, "upscript") == 0) { + if (netdev->upscript) + strprint(retv, inlen, "%s", netdev->upscript); + } else if (strcmp(p1, "hwaddr") == 0) { + if (netdev->hwaddr) + strprint(retv, inlen, "%s", netdev->hwaddr); + } else if (strcmp(p1, "mtu") == 0) { + if (netdev->mtu) + strprint(retv, inlen, "%s", netdev->mtu); + } else if (strcmp(p1, "macvlan.mode") == 0) { + if (netdev->type == LXC_NET_MACVLAN) { + const char *mode; + switch (netdev->priv.macvlan_attr.mode) { + case MACVLAN_MODE_PRIVATE: mode = "private"; break; + case MACVLAN_MODE_VEPA: mode = "vepa"; break; + case MACVLAN_MODE_BRIDGE: mode = "bridge"; break; + default: mode = "(invalid)"; break; + } + strprint(retv, inlen, "%s", mode); + } + } else if (strcmp(p1, "veth.pair") == 0) { + if (netdev->type == LXC_NET_VETH && netdev->priv.veth_attr.pair) + strprint(retv, inlen, "%s", netdev->priv.veth_attr.pair); + } else if (strcmp(p1, "vlan") == 0) { + if (netdev->type == LXC_NET_VLAN) { + strprint(retv, inlen, "%d", netdev->priv.vlan_attr.vid); + } + } else if (strcmp(p1, "ipv4_gateway") == 0) { + if (netdev->ipv4_gateway_auto) { + strprint(retv, inlen, "auto"); + } else if (netdev->ipv4_gateway) { + char buf[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, netdev->ipv4_gateway, buf, sizeof(buf)); + strprint(retv, inlen, "%s", buf); + } + } else if (strcmp(p1, "ipv4") == 0) { + struct lxc_list *it2; + lxc_list_for_each(it2, &netdev->ipv4) { + struct lxc_inetdev *i = it2->elem; + char buf[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &i->addr, buf, sizeof(buf)); + strprint(retv, inlen, "%s\n", buf); + } + } else if (strcmp(p1, "ipv6_gateway") == 0) { + if (netdev->ipv6_gateway_auto) { + strprint(retv, inlen, "auto"); + } else if (netdev->ipv6_gateway) { + char buf[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, netdev->ipv6_gateway, buf, sizeof(buf)); + strprint(retv, inlen, "%s", buf); + } + } else if (strcmp(p1, "ipv6") == 0) { + struct lxc_list *it2; + lxc_list_for_each(it2, &netdev->ipv6) { + struct lxc_inetdev *i = it2->elem; + char buf[INET_ADDRSTRLEN]; + inet_ntop(AF_INET6, &i->addr, buf, sizeof(buf)); + strprint(retv, inlen, "%s\n", buf); + } + } + return fulllen; +} + +static int lxc_get_item_network(struct lxc_conf *c, char *retv, int inlen) +{ + int len, fulllen = 0; + struct lxc_list *it; + + if (!retv) + inlen = 0; + else + memset(retv, 0, inlen); + + lxc_list_for_each(it, &c->network) { + struct lxc_netdev *n = it->elem; + const char *t = lxc_net_type_to_str(n->type); + strprint(retv, inlen, "%s\n", t ? t : "(invalid)"); + } + return fulllen; +} + +int lxc_get_config_item(struct lxc_conf *c, const char *key, char *retv, + int inlen) +{ + const char *v = NULL; + + if (strcmp(key, "lxc.mount.entry") == 0) + return lxc_get_mount_entries(c, retv, inlen); + else if (strcmp(key, "lxc.mount") == 0) + v = c->fstab; + else if (strcmp(key, "lxc.tty") == 0) + return lxc_get_conf_int(c, retv, inlen, c->tty); + else if (strcmp(key, "lxc.pts") == 0) + return lxc_get_conf_int(c, retv, inlen, c->pts); + else if (strcmp(key, "lxc.devttydir") == 0) + v = c->ttydir; + else if (strcmp(key, "lxc.arch") == 0) + return lxc_get_arch_entry(c, retv, inlen); +#if HAVE_APPARMOR + else if (strcmp(key, "lxc.aa_profile") == 0) + v = c->aa_profile; +#endif + else if (strcmp(key, "lxc.logfile") == 0) + v = c->logfile; + else if (strcmp(key, "lxc.loglevel") == 0) + v = lxc_log_priority_to_string(c->loglevel); + else if (strcmp(key, "lxc.cgroup") == 0) // all cgroup info + return lxc_get_cgroup_entry(c, retv, inlen, "all"); + else if (strncmp(key, "lxc.cgroup.", 11) == 0) // specific cgroup info + return lxc_get_cgroup_entry(c, retv, inlen, key + 11); + else if (strcmp(key, "lxc.utsname") == 0) + v = c->utsname ? c->utsname->nodename : NULL; + else if (strcmp(key, "lxc.console") == 0) + v = c->console.path; + else if (strcmp(key, "lxc.rootfs.mount") == 0) + v = c->rootfs.mount; + else if (strcmp(key, "lxc.rootfs") == 0) + v = c->rootfs.path; + else if (strcmp(key, "lxc.pivotdir") == 0) + v = c->rootfs.pivot; + else if (strcmp(key, "lxc.cap.drop") == 0) + return lxc_get_item_cap_drop(c, retv, inlen); + else if (strncmp(key, "lxc.hook", 8) == 0) + return lxc_get_item_hooks(c, retv, inlen, key); + else if (strcmp(key, "lxc.network") == 0) + return lxc_get_item_network(c, retv, inlen); + else if (strncmp(key, "lxc.network.", 12) == 0) + return lxc_get_item_nic(c, retv, inlen, key + 12); + else return -1; + + if (!v) + return 0; + if (retv && inlen >= strlen(v) + 1) + strncpy(retv, v, strlen(v)+1); + return strlen(v); +} + +int lxc_clear_config_item(struct lxc_conf *c, const char *key) +{ + if (strcmp(key, "lxc.network") == 0) + return lxc_clear_config_network(c); + else if (strncmp(key, "lxc.network.", 12) == 0) + return lxc_clear_nic(c, key + 12); + else if (strcmp(key, "lxc.cap.drop") == 0) + return lxc_clear_config_caps(c); + else if (strncmp(key, "lxc.cgroup", 10) == 0) + return lxc_clear_cgroups(c, key); + else if (strcmp(key, "lxc.mount.entries") == 0) + return lxc_clear_mount_entries(c); + else if (strncmp(key, "lxc.hook", 8) == 0) + return lxc_clear_hooks(c, key); + + return -1; +} + +/* + * writing out a confile. + */ +void write_config(FILE *fout, struct lxc_conf *c) +{ + struct lxc_list *it; + int i; + + if (c->fstab) + fprintf(fout, "lxc.mount = %s\n", c->fstab); + lxc_list_for_each(it, &c->mount_list) { + fprintf(fout, "lxc.mount.entry = %s\n", (char *)it->elem); + } + if (c->tty) + fprintf(fout, "lxc.tty = %d\n", c->tty); + if (c->pts) + fprintf(fout, "lxc.pts = %d\n", c->pts); + if (c->ttydir) + fprintf(fout, "lxc.devttydir = %s\n", c->ttydir); + switch(c->personality) { + case PER_LINUX32: fprintf(fout, "lxc.arch = x86\n"); break; + case PER_LINUX: fprintf(fout, "lxc.arch = x86_64\n"); break; + default: break; + } +#if HAVE_APPARMOR + if (c->aa_profile) + fprintf(fout, "lxc.aa_profile = %s\n", c->aa_profile); +#endif + if (c->loglevel != LXC_LOG_PRIORITY_NOTSET) + fprintf(fout, "lxc.loglevel = %s\n", lxc_log_priority_to_string(c->loglevel)); + if (c->logfile) + fprintf(fout, "lxc.logfile = %s\n", c->logfile); + lxc_list_for_each(it, &c->cgroup) { + struct lxc_cgroup *cg = it->elem; + fprintf(fout, "lxc.cgroup.%s = %s\n", cg->subsystem, cg->value); + } + if (c->utsname) + fprintf(fout, "lxc.utsname = %s\n", c->utsname->nodename); + lxc_list_for_each(it, &c->network) { + struct lxc_netdev *n = it->elem; + const char *t = lxc_net_type_to_str(n->type); + struct lxc_list *it2; + fprintf(fout, "lxc.network.type = %s\n", t ? t : "(invalid)"); + if (n->flags & IFF_UP) + fprintf(fout, "lxc.network.flags = up\n"); + if (n->link) + fprintf(fout, "lxc.network.link = %s\n", n->link); + if (n->name) + fprintf(fout, "lxc.network.name = %s\n", n->name); + if (n->type == LXC_NET_MACVLAN) { + const char *mode; + switch (n->priv.macvlan_attr.mode) { + case MACVLAN_MODE_PRIVATE: mode = "private"; break; + case MACVLAN_MODE_VEPA: mode = "vepa"; break; + case MACVLAN_MODE_BRIDGE: mode = "bridge"; break; + default: mode = "(invalid)"; break; + } + fprintf(fout, "lxc.network.macvlan.mode = %s\n", mode); + } else if (n->type == LXC_NET_VETH) { + if (n->priv.veth_attr.pair) + fprintf(fout, "lxc.network.veth.pair = %s\n", + n->priv.veth_attr.pair); + } else if (n->type == LXC_NET_VLAN) { + fprintf(fout, "lxc.network.vlan.id = %d\n", n->priv.vlan_attr.vid); + } + if (n->upscript) + fprintf(fout, "lxc.network.script.up = %s\n", n->upscript); + if (n->hwaddr) + fprintf(fout, "lxc.network.hwaddr = %s\n", n->hwaddr); + if (n->mtu) + fprintf(fout, "lxc.network.mtu = %s\n", n->mtu); + if (n->ipv4_gateway_auto) + fprintf(fout, "lxc.network.ipv4.gateway = auto\n"); + else if (n->ipv4_gateway) { + char buf[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, n->ipv4_gateway, buf, sizeof(buf)); + fprintf(fout, "lxc.network.ipv4.gateway = %s\n", buf); + } + lxc_list_for_each(it2, &n->ipv4) { + struct lxc_inetdev *i = it2->elem; + char buf[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &i->addr, buf, sizeof(buf)); + fprintf(fout, "lxc.network.ipv4 = %s\n", buf); + } + if (n->ipv6_gateway_auto) + fprintf(fout, "lxc.network.ipv6.gateway = auto\n"); + else if (n->ipv6_gateway) { + char buf[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, n->ipv6_gateway, buf, sizeof(buf)); + fprintf(fout, "lxc.network.ipv6.gateway = %s\n", buf); + } + lxc_list_for_each(it2, &n->ipv6) { + struct lxc_inet6dev *i = it2->elem; + char buf[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET, &i->addr, buf, sizeof(buf)); + fprintf(fout, "lxc.network.ipv6 = %s\n", buf); + } + } + lxc_list_for_each(it, &c->caps) + fprintf(fout, "lxc.cap.drop = %s\n", (char *)it->elem); + for (i=0; ihooks[i]) + fprintf(fout, "lxc.hook.%s = %s\n", + lxchook_names[i], (char *)it->elem); + } + if (c->console.path) + fprintf(fout, "lxc.console = %s\n", c->console.path); + if (c->rootfs.path) + fprintf(fout, "lxc.rootfs = %s\n", c->rootfs.path); + if (c->rootfs.mount && strcmp(c->rootfs.mount, LXCROOTFSMOUNT) != 0) + fprintf(fout, "lxc.rootfs.mount = %s\n", c->rootfs.mount); + if (c->rootfs.pivot) + fprintf(fout, "lxc.pivotdir = %s\n", c->rootfs.pivot); +} diff --git a/src/lxc/confile.h b/src/lxc/confile.h index d2faa7502..a96efcecb 100644 --- a/src/lxc/confile.h +++ b/src/lxc/confile.h @@ -27,6 +27,15 @@ struct lxc_conf; struct lxc_list; +typedef int (*config_cb)(const char *, const char *, struct lxc_conf *); +struct lxc_config_t { + char *name; + config_cb cb; +}; + +extern struct lxc_config_t *lxc_getconfig(const char *key); +extern int lxc_list_nicconfigs(struct lxc_conf *c, const char *key, char *retv, int inlen); +extern int lxc_listconfigs(char *retv, int inlen); extern int lxc_config_read(const char *file, struct lxc_conf *conf); extern int lxc_config_readline(char *buffer, struct lxc_conf *conf); @@ -37,4 +46,7 @@ extern int lxc_config_define_load(struct lxc_list *defines, /* needed for lxc-attach */ extern signed long lxc_config_parse_arch(const char *arch); +extern int lxc_get_config_item(struct lxc_conf *c, const char *key, char *retv, int inlen); +extern int lxc_clear_config_item(struct lxc_conf *c, const char *key); +extern void write_config(FILE *fout, struct lxc_conf *c); #endif diff --git a/src/lxc/execute.c b/src/lxc/execute.c index 487765ff9..99800d084 100644 --- a/src/lxc/execute.c +++ b/src/lxc/execute.c @@ -27,7 +27,6 @@ #include #include - #include "log.h" #include "start.h" diff --git a/src/lxc/genl.c b/src/lxc/genl.c index 43959ce9a..a0d76ebf4 100644 --- a/src/lxc/genl.c +++ b/src/lxc/genl.c @@ -62,7 +62,7 @@ static int genetlink_resolve_family(const char *family) if (ret) return ret; - ret = nla_put_string((struct nlmsg *)&request->nlmsghdr, + ret = nla_put_string((struct nlmsg *)&request->nlmsghdr, CTRL_ATTR_FAMILY_NAME, family); if (ret) goto out; @@ -130,7 +130,7 @@ extern int genetlink_send(struct genl_handler *handler, struct genlmsg *genlmsg) return netlink_send(&handler->nlh, (struct nlmsg *)&genlmsg->nlmsghdr); } -extern int genetlink_transaction(struct genl_handler *handler, +extern int genetlink_transaction(struct genl_handler *handler, struct genlmsg *request, struct genlmsg *answer) { return netlink_transaction(&handler->nlh, (struct nlmsg *)&request->nlmsghdr, diff --git a/src/lxc/genl.h b/src/lxc/genl.h index 4800aacc2..31b471ae3 100644 --- a/src/lxc/genl.h +++ b/src/lxc/genl.h @@ -30,7 +30,7 @@ #define GENLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN)) /* - * struct genl_handler : the structure which store the netlink handler + * struct genl_handler : the structure which store the netlink handler * and the family number resulting of the auto-generating id family * for the generic netlink protocol * @@ -116,6 +116,6 @@ void genlmsg_free(struct genlmsg *genlmsg); * * Returns 0 on success, < 0 otherwise */ -int genetlink_transaction(struct genl_handler *handler, +int genetlink_transaction(struct genl_handler *handler, struct genlmsg *request, struct genlmsg *answer); #endif diff --git a/src/lxc/lxc-ls.in b/src/lxc/legacy/lxc-ls.in similarity index 96% rename from src/lxc/lxc-ls.in rename to src/lxc/legacy/lxc-ls.in index 9293323c9..f26572da2 100644 --- a/src/lxc/lxc-ls.in +++ b/src/lxc/legacy/lxc-ls.in @@ -75,7 +75,7 @@ directory=$(readlink -f "$lxc_path") for i in "$@"; do case $i in --help) - help; exit 1;; + help; exit;; --active) get_parent_cgroup; directory="$parent_cgroup"; shift;; --) @@ -90,10 +90,5 @@ if [ ! -z "$directory" ]; then containers=$(find $directory -mindepth 1 -maxdepth 1 -type d -printf "%f\n" 2>/dev/null) fi -if [ -z "$containers" ]; then - echo "$(basename $0): no containers found" >&2 - exit 1 -fi - cd "$directory" ls -d $@ -- $containers diff --git a/src/lxc/list.h b/src/lxc/list.h index 5213e8085..24dffa2d1 100644 --- a/src/lxc/list.h +++ b/src/lxc/list.h @@ -14,6 +14,11 @@ struct lxc_list { __iterator != __list; \ __iterator = __iterator->next) +#define lxc_list_for_each_safe(__iterator, __list, __next) \ + for (__iterator = (__list)->next, __next = __iterator->next; \ + __iterator != __list; \ + __iterator = __next, __next = __next->next) + static inline void lxc_list_init(struct lxc_list *list) { list->elem = NULL; diff --git a/src/lxc/log.c b/src/lxc/log.c index 7f3b6b25b..0354d8dfc 100644 --- a/src/lxc/log.c +++ b/src/lxc/log.c @@ -41,6 +41,7 @@ int lxc_log_fd = -1; static char log_prefix[LXC_LOG_PREFIX_SIZE] = "lxc"; +int lxc_loglevel_specified = 0; lxc_log_define(lxc_log, lxc); @@ -153,7 +154,11 @@ extern int lxc_log_init(const char *file, const char *priority, { int lxc_priority = LXC_LOG_PRIORITY_ERROR; + if (lxc_log_fd != -1) + return 0; + if (priority) { + lxc_loglevel_specified = 1; lxc_priority = lxc_log_priority_to_int(priority); if (lxc_priority == LXC_LOG_PRIORITY_NOTSET) { @@ -185,3 +190,39 @@ extern int lxc_log_init(const char *file, const char *priority, return 0; } + +/* + * This is called when we read a lxc.loglevel entry in a lxc.conf file. This + * happens after processing command line arguments, which override the .conf + * settings. So only set the level if previously unset. + */ +extern int lxc_log_set_level(int level) +{ + if (lxc_loglevel_specified) + return 0; + if (level < 0 || level >= LXC_LOG_PRIORITY_NOTSET) { + ERROR("invalid log priority %d", level); + return -1; + } + lxc_log_category_lxc.priority = level; + return 0; +} + +/* + * This is called when we read a lxc.logfile entry in a lxc.conf file. This + * happens after processing command line arguments, which override the .conf + * settings. So only set the logfile if previously unset. + */ +extern int lxc_log_set_file(char *fname) +{ + if (lxc_log_fd != -1) { + INFO("Configuration file was specified on command line, configuration file entry being ignored"); + return 0; + } + lxc_log_fd = log_open(fname); + if (lxc_log_fd == -1) { + ERROR("failed to open log file %s\n", fname); + return -1; + } + return 0; +} diff --git a/src/lxc/log.h b/src/lxc/log.h index b11093d1e..340a3abd5 100644 --- a/src/lxc/log.h +++ b/src/lxc/log.h @@ -41,7 +41,7 @@ #define LXC_LOG_BUFFER_SIZE 512 /* predefined priorities. */ -enum { +enum lxc_loglevel { LXC_LOG_PRIORITY_TRACE, LXC_LOG_PRIORITY_DEBUG, LXC_LOG_PRIORITY_INFO, @@ -291,4 +291,6 @@ extern int lxc_log_init(const char *file, const char *priority, const char *prefix, int quiet); extern void lxc_log_setprefix(const char *a_prefix); +extern int lxc_log_set_level(int level); +extern int lxc_log_set_file(char *fname); #endif diff --git a/src/lxc/lxc-checkconfig.in b/src/lxc/lxc-checkconfig.in index 8c2b5e517..13dbf3bf6 100644 --- a/src/lxc/lxc-checkconfig.in +++ b/src/lxc/lxc-checkconfig.in @@ -1,13 +1,13 @@ -#!/bin/bash +#!/bin/sh # Allow environment variables to override grep and config : ${CONFIG:=/proc/config.gz} : ${GREP:=zgrep} -SETCOLOR_SUCCESS="echo -en \\033[1;32m" -SETCOLOR_FAILURE="echo -en \\033[1;31m" -SETCOLOR_WARNING="echo -en \\033[1;33m" -SETCOLOR_NORMAL="echo -en \\033[0;39m" +SETCOLOR_SUCCESS="printf \\e[1;32m" +SETCOLOR_FAILURE="printf \\e[1;31m" +SETCOLOR_WARNING="printf \\e[1;33m" +SETCOLOR_NORMAL="printf \\e[0;39m" is_set() { $GREP -q "$1=[y|m]" $CONFIG @@ -21,13 +21,13 @@ is_enabled() { RES=$? if [ $RES -eq 0 ]; then - $SETCOLOR_SUCCESS && echo -e "enabled" && $SETCOLOR_NORMAL + $SETCOLOR_SUCCESS && echo "enabled" && $SETCOLOR_NORMAL else - if [ ! -z "$mandatory" -a "$mandatory" = yes ]; then - $SETCOLOR_FAILURE && echo -e "required" && $SETCOLOR_NORMAL - else - $SETCOLOR_WARNING && echo -e "missing" && $SETCOLOR_NORMAL - fi + if [ ! -z "$mandatory" -a "$mandatory" = yes ]; then + $SETCOLOR_FAILURE && echo "required" && $SETCOLOR_NORMAL + else + $SETCOLOR_WARNING && echo "missing" && $SETCOLOR_NORMAL + fi fi } @@ -68,40 +68,45 @@ print_cgroups() { } CGROUP_MNT_PATH=`print_cgroups cgroup /proc/self/mounts | head -1` - -echo -n "Cgroup: " && is_enabled CONFIG_CGROUPS yes - -if [ -f $CGROUP_MNT_PATH/cgroup.clone_children ]; then - echo -n "Cgroup clone_children flag: " && - $SETCOLOR_SUCCESS && echo -e "enabled" && $SETCOLOR_NORMAL -else - echo -n "Cgroup namespace: " && is_enabled CONFIG_CGROUP_NS yes -fi -echo -n "Cgroup device: " && is_enabled CONFIG_CGROUP_DEVICE -echo -n "Cgroup sched: " && is_enabled CONFIG_CGROUP_SCHED -echo -n "Cgroup cpu account: " && is_enabled CONFIG_CGROUP_CPUACCT -echo -n "Cgroup memory controller: " && is_enabled CONFIG_CGROUP_MEM_RES_CTLR -is_set CONFIG_SMP && echo -n "Cgroup cpuset: " && is_enabled CONFIG_CPUSETS -echo -echo "--- Misc ---" -echo -n "Veth pair device: " && is_enabled CONFIG_VETH -echo -n "Macvlan: " && is_enabled CONFIG_MACVLAN -echo -n "Vlan: " && is_enabled CONFIG_VLAN_8021Q KVER_MAJOR=$($GREP '^# Linux' $CONFIG | \ sed -r 's/.* ([0-9])\.[0-9]{1,2}\.[0-9]{1,3}.*/\1/') -if [[ $KVER_MAJOR == 2 ]]; then +if [ "$KVER_MAJOR" = "2" ]; then KVER_MINOR=$($GREP '^# Linux' $CONFIG | \ sed -r 's/.* 2.6.([0-9]{2}).*/\1/') else KVER_MINOR=$($GREP '^# Linux' $CONFIG | \ sed -r 's/.* [0-9]\.([0-9]{1,3})\.[0-9]{1,3}.*/\1/') fi -echo -n "File capabilities: " && - ( [[ ${KVER_MAJOR} == 2 && ${KVER_MINOR} < 33 ]] && - is_enabled CONFIG_SECURITY_FILE_CAPABILITIES ) || - ( [[ ( ${KVER_MAJOR} == 2 && ${KVER_MINOR} > 32 ) || - ${KVER_MAJOR} > 2 ]] && $SETCOLOR_SUCCESS && - echo -e "enabled" && $SETCOLOR_NORMAL ) + +echo -n "Cgroup: " && is_enabled CONFIG_CGROUPS yes + +if [ -f $CGROUP_MNT_PATH/cgroup.clone_children ]; then + echo -n "Cgroup clone_children flag: " && + $SETCOLOR_SUCCESS && echo "enabled" && $SETCOLOR_NORMAL +else + echo -n "Cgroup namespace: " && is_enabled CONFIG_CGROUP_NS yes +fi +echo -n "Cgroup device: " && is_enabled CONFIG_CGROUP_DEVICE +echo -n "Cgroup sched: " && is_enabled CONFIG_CGROUP_SCHED +echo -n "Cgroup cpu account: " && is_enabled CONFIG_CGROUP_CPUACCT +echo -n "Cgroup memory controller: " +if [ $KVER_MAJOR -ge 3 -a $KVER_MINOR -ge 6 ]; then + is_enabled CONFIG_MEMCG +else + is_enabled CONFIG_CGROUP_MEM_RES_CTLR +fi +is_set CONFIG_SMP && echo -n "Cgroup cpuset: " && is_enabled CONFIG_CPUSETS +echo +echo "--- Misc ---" +echo -n "Veth pair device: " && is_enabled CONFIG_VETH +echo -n "Macvlan: " && is_enabled CONFIG_MACVLAN +echo -n "Vlan: " && is_enabled CONFIG_VLAN_8021Q +echo -n "File capabilities: " && \ + ( [ "${KVER_MAJOR}" = 2 ] && [ ${KVER_MINOR} -lt 33 ] && \ + is_enabled CONFIG_SECURITY_FILE_CAPABILITIES ) || \ + ( ( [ "${KVER_MAJOR}" = "2" ] && [ ${KVER_MINOR} -gt 32 ] ) || \ + [ ${KVER_MAJOR} -gt 2 ] && $SETCOLOR_SUCCESS && \ + echo "enabled" && $SETCOLOR_NORMAL ) echo echo "Note : Before booting a new kernel, you can check its configuration" diff --git a/src/lxc/lxc-clone.in b/src/lxc/lxc-clone.in index 04ef20bb2..c9cc5c788 100644 --- a/src/lxc/lxc-clone.in +++ b/src/lxc/lxc-clone.in @@ -225,7 +225,7 @@ if [ -b $oldroot ]; then mkfs -t $fstype /dev/$lxc_vg/${lxc_lv_prefix}$lxc_new mount /dev/$lxc_vg/${lxc_lv_prefix}$lxc_new $rootfs || { echo "$(basename $0): failed to mount new rootfs" >&2; false; } mounted=1 - rsync -ax ${rootfs}_snapshot/ ${rootfs}/ || { echo "$(basename $0): copying data to new lv failed" >&2; false; } + rsync -Hax ${rootfs}_snapshot/ ${rootfs}/ || { echo "$(basename $0): copying data to new lv failed" >&2; false; } umount ${rootfs}_snapshot rmdir ${rootfs}_snapshot lvremove -f $lxc_vg/${lxc_lv_prefix}${lxc_new}_snapshot @@ -252,7 +252,7 @@ else frozen=1 fi mkdir -p $rootfs/ - rsync -ax $oldroot/ $rootfs/ + rsync -Hax $oldroot/ $rootfs/ echo "lxc.rootfs = $rootfs" >> $lxc_path/$lxc_new/config if [ $container_running = "True" ]; then lxc-unfreeze -n $lxc_orig @@ -272,11 +272,11 @@ c=$lxc_path/$lxc_new/config mv ${c} ${c}.old ( while read line; do - if [ "${line:0:18}" = "lxc.network.hwaddr" ]; then - echo "lxc.network.hwaddr= 00:16:3e:$(openssl rand -hex 3| sed 's/\(..\)/\1:/g; s/.$//')" - else - echo "$line" - fi + if [ "${line:0:18}" = "lxc.network.hwaddr" ]; then + echo "lxc.network.hwaddr= 00:16:3e:$(openssl rand -hex 3| sed 's/\(..\)/\1:/g; s/.$//')" + else + echo "$line" + fi done ) < ${c}.old > ${c} rm -f ${c}.old diff --git a/src/lxc/lxc-create.in b/src/lxc/lxc-create.in index b21cdc301..5cd9fdba5 100644 --- a/src/lxc/lxc-create.in +++ b/src/lxc/lxc-create.in @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # # lxc: linux Container library @@ -26,6 +26,7 @@ usage() { echo >&2 echo "where FS_OPTIONS is one of:" >&2 echo " -B none" >&2 + echo " -B dir [--dir rootfs_dir]" >&2 echo " -B lvm [--lvname LV_NAME] [--vgname VG_NAME] [--fstype FS_TYPE]" >&2 echo " [--fssize FS_SIZE]" >&2 echo " -B btrfs" >&2 @@ -43,25 +44,35 @@ help() { echo " -B BACKING_STORE alter the container backing store (default: none)" >&2 echo " --lvname LV_NAME specify the LVM logical volume name" >&2 echo " (default: container name)" >&2 + echo " --dir ROOTFS_DIR specify path for custom rootfs directory location" >&2 echo " --vgname VG_NAME specify the LVM volume group name (default: lxc)" >&2 echo " --fstype FS_TYPE specify the filesystem type (default: ext4)" >&2 echo " --fssize FS_SIZE specify the filesystem size (default: 500M)" >&2 echo >&2 - if [ -z $lxc_template ]; then + if [ -z "$lxc_template" ]; then echo "To see template-specific options, specify a template. For example:" >&2 echo " $(basename $0) -t ubuntu -h" >&2 exit 0 fi - type ${templatedir}/lxc-$lxc_template 2>/dev/null - if [ $? -eq 0 ]; then + if [ -x ${templatedir}/lxc-$lxc_template ]; then echo >&2 echo "Template-specific options (TEMPLATE_OPTIONS):" >&2 ${templatedir}/lxc-$lxc_template -h fi } -shortoptions='hn:f:t:B:' -longoptions='help,name:,config:,template:,backingstore:,fstype:,lvname:,vgname:,fssize:' +usage_err() { + [ -n "$1" ] && echo "$1" >&2 + usage + exit 1 +} + +optarg_check() { + if [ -z "$2" ]; then + usage_err "option '$1' requires an argument" + fi +} + lxc_path=@LXCPATH@ bindir=@BINDIR@ templatedir=@LXCTEMPLATEDIR@ @@ -69,80 +80,85 @@ backingstore=_unset fstype=ext4 fssize=500M vgname=lxc +custom_rootfs="" -getopt=$(getopt -o $shortoptions --longoptions $longoptions -- "$@") -if [ $? != 0 ]; then - usage - exit 1; -fi - -eval set -- "$getopt" - -while true; do - case "$1" in - -h|--help) - help - exit 1 - ;; - -n|--name) - shift - lxc_name=$1 - shift - ;; - -f|--config) - shift - lxc_config=$1 - shift - ;; - -t|--template) - shift - lxc_template=$1 - shift - ;; - -B|--backingstore) - shift - backingstore=$1 - shift - ;; - --lvname) - shift - lvname=$1 - shift - ;; - --vgname) - shift - vgname=$1 - shift - ;; - --fstype) - shift - fstype=$1 - shift - ;; - --fssize) - shift - fssize=$1 - shift - ;; - --) - shift - break;; - *) - usage - exit 1 - ;; - esac +while [ $# -gt 0 ]; do + opt="$1" + shift + case "$opt" in + -h|--help) + help + exit 1 + ;; + -n|--name) + optarg_check $opt "$1" + lxc_name=$1 + shift + ;; + -f|--config) + optarg_check $opt "$1" + lxc_config=$1 + shift + ;; + -t|--template) + optarg_check $opt "$1" + lxc_template=$1 + shift + ;; + -B|--backingstore) + optarg_check $opt "$1" + backingstore=$1 + shift + ;; + --dir) + optarg_check $opt "$1" + custom_rootfs=$1 + shift + ;; + --lvname) + optarg_check $opt "$1" + lvname=$1 + shift + ;; + --vgname) + optarg_check $opt "$1" + vgname=$1 + shift + ;; + --fstype) + optarg_check $opt "$1" + fstype=$1 + shift + ;; + --fssize) + optarg_check $opt "$1" + fssize=$1 + shift + ;; + --) + break;; + -?) + usage_err "unknown option '$opt'" + ;; + -*) + # split opts -abc into -a -b -c + set -- $(echo "${opt#-}" | sed 's/\(.\)/ -\1/g') "$@" + ;; + *) + usage + exit 1 + ;; + esac done # If -h or --help was passed into the container, we'll want to cleanup # afterward wantedhelp=0 -for var in "$@" -do -if [ "$var" = "-h" -o "$var" = "--help" ]; then - help - exit 1 -fi +for var in "$@"; do + if [ "$var" = "-h" ] || [ "$var" = "--help" ]; then + help + exit 1 + fi done @@ -171,9 +187,14 @@ if [ "$(id -u)" != "0" ]; then exit 1 fi +if [ -n "$custom_rootfs" ] && [ "$backingstore" != "dir" ]; then + echo "--dir is only valid with -B dir" +fi + case "$backingstore" in - lvm|none|btrfs|_unset) :;; - *) echo "$(basename $0): '$backingstore' is not known (try 'none', 'lvm', 'btrfs')" >&2 + dir|lvm|none|btrfs|_unset) :;; + *) + echo "$(basename $0): '$backingstore' is not known (try 'none', 'dir', 'lvm', 'btrfs')" >&2 usage exit 1 ;; @@ -186,9 +207,9 @@ fi rootfs="$lxc_path/$lxc_name/rootfs" -if [ "$backingstore" = "_unset" -o "$backingstore" = "btrfs" ]; then +if [ "$backingstore" = "_unset" ] || [ "$backingstore" = "btrfs" ]; then # if no backing store was given, then see if btrfs would work - if which btrfs >/dev/null 2>&1 && + if which btrfs >/dev/null 2>&1 && \ btrfs filesystem df "$lxc_path/" >/dev/null 2>&1; then backingstore="btrfs" else @@ -200,12 +221,13 @@ if [ "$backingstore" = "_unset" -o "$backingstore" = "btrfs" ]; then fi fi -if [ $backingstore = "lvm" ]; then +if [ "$backingstore" = "lvm" ]; then which vgscan > /dev/null if [ $? -ne 0 ]; then echo "$(basename $0): vgscan not found (is lvm2 installed?)" >&2 exit 1 fi + grep -q "\<$fstype\>" /proc/filesystems if [ $? -ne 0 ]; then echo "$(basename $0): $fstype is not listed in /proc/filesystems" >&2 @@ -225,6 +247,7 @@ if [ $backingstore = "lvm" ]; then echo "please delete it (using \"lvremove $rootdev\") and try again" >&2 exit 1 fi + elif [ "$backingstore" = "btrfs" ]; then mkdir "$lxc_path/$lxc_name" if ! out=$(btrfs subvolume create "$rootfs" 2>&1); then @@ -234,10 +257,13 @@ elif [ "$backingstore" = "btrfs" ]; then fi cleanup() { - if [ $backingstore = "lvm" ]; then + if [ "$backingstore" = "lvm" ]; then umount $rootfs lvremove -f $rootdev + elif [ "$backingstore" = "btrfs" ]; then + btrfs subvolume delete "$rootfs" fi + ${bindir}/lxc-destroy -n $lxc_name echo "$(basename $0): aborted" >&2 exit 1 @@ -248,18 +274,54 @@ trap cleanup HUP INT TERM mkdir -p $lxc_path/$lxc_name if [ -z "$lxc_config" ]; then - touch $lxc_path/$lxc_name/config -else - if [ ! -r "$lxc_config" ]; then - echo "$(basename $0): '$lxc_config' configuration file not found" >&2 - exit 1 + lxc_config="@SYSCONFDIR@/lxc/lxc.conf" + echo + echo "$(basename $0): No config file specified, using the default config $lxc_config" +fi + +if [ ! -r "$lxc_config" ]; then + echo "$(basename $0): '$lxc_config' configuration file not found" >&2 + exit 1 +fi + +if [ ! -z "$lxc_template" ]; then + # Allow for a path to be provided as the template name + if [ -x "$lxc_template" ]; then + template_path=$lxc_template + else + template_path=${templatedir}/lxc-$lxc_template fi - cp $lxc_config $lxc_path/$lxc_name/config + if ! [ -x "$template_path" ]; then + echo "$(basename $0): unknown template '$lxc_template'" >&2 + cleanup + fi + + sum=$(sha1sum $template_path | cut -d ' ' -f1) + echo "# Template used to create this container: $lxc_template" >> $lxc_path/$lxc_name/config + if [ -n "$*" ]; then + echo "# Parameters passed to the template: $*" >> $lxc_path/$lxc_name/config + fi + echo "# Checksum of the template script (SHA-1): $sum" >> $lxc_path/$lxc_name/config + echo "" >> $lxc_path/$lxc_name/config +fi + +cat $lxc_config >> $lxc_path/$lxc_name/config + +if [ -n "$custom_rootfs" ]; then + if grep -q "lxc.rootfs" $lxc_path/$lxc_name/config ; then + echo "configuration file already specifies a lxc.rootfs" + exit 1 + fi + if [ -d "$custom_rootfs" ]; then + echo "specified rootfs ($custom_rootfs) already exists. Bailing." + exit 1 + fi + echo "lxc.rootfs = $custom_rootfs" >> $lxc_path/$lxc_name/config fi # Create the fs as needed -if [ $backingstore = "lvm" ]; then +if [ "$backingstore" = "lvm" ]; then [ -d "$rootfs" ] || mkdir $rootfs lvcreate -L $fssize -n $lvname $vgname || exit 1 udevadm settle @@ -267,22 +329,8 @@ if [ $backingstore = "lvm" ]; then mount -t $fstype $rootdev $rootfs fi -if [ ! -z $lxc_template ]; then - - type ${templatedir}/lxc-$lxc_template 2>/dev/null - if [ $? -ne 0 ]; then - echo "$(basename $0): unknown template '$lxc_template'" >&2 - cleanup - fi - - if [ -z "$lxc_config" ]; then - echo "Note: Usually the template option is called with a configuration" - echo "file option too, mostly to configure the network." - echo "For more information look at lxc.conf (5)" - echo - fi - - ${templatedir}/lxc-$lxc_template --path=$lxc_path/$lxc_name --name=$lxc_name $* +if [ ! -z "$lxc_template" ]; then + $template_path --path=$lxc_path/$lxc_name --name=$lxc_name $* if [ $? -ne 0 ]; then echo "$(basename $0): failed to execute template '$lxc_template'" >&2 cleanup @@ -291,7 +339,7 @@ if [ ! -z $lxc_template ]; then echo "'$lxc_template' template installed" fi -if [ $backingstore = "lvm" ]; then +if [ "$backingstore" = "lvm" ]; then echo "Unmounting LVM" umount $rootfs diff --git a/src/lxc/lxc-destroy.in b/src/lxc/lxc-destroy.in index 846266cad..c72f18a33 100644 --- a/src/lxc/lxc-destroy.in +++ b/src/lxc/lxc-destroy.in @@ -54,26 +54,27 @@ eval set -- "$getopt" while true; do case "$1" in - -h|--help) - help - exit 1 - ;; - -n|--name) - shift - lxc_name=$1 - shift - ;; - -f) - force=1 - shift - ;; + -h|--help) + help + exit 1 + ;; + -n|--name) + shift + lxc_name=$1 + shift + ;; + -f) + force=1 + shift + ;; --) - shift - break;; + shift + break + ;; *) - usage - exit 1 - ;; + usage + exit 1 + ;; esac done @@ -96,13 +97,13 @@ fi # make sure the container isn't running lxc-info -n $lxc_name 2>/dev/null | grep -q RUNNING if [ $? -eq 0 ]; then - if [ $force -eq 1 ]; then - lxc-stop -n $lxc_name - lxc-wait -n $lxc_name -s STOPPED - else - echo "$(basename $0): '$lxc_name' is running; aborted" >&2 - exit 1 - fi + if [ $force -eq 1 ]; then + lxc-stop -n $lxc_name + lxc-wait -n $lxc_name -s STOPPED + else + echo "$(basename $0): '$lxc_name' is running; aborted" >&2 + exit 1 + fi fi # Deduce the type of rootfs @@ -110,21 +111,22 @@ fi # else, ignore it. We'll support deletion of others later. rootdev=`grep lxc.rootfs $lxc_path/$lxc_name/config 2>/dev/null | sed -e 's/^[^/]*/\//'` if [ -n "$rootdev" ]; then - if [ -b "$rootdev" ]; then - lvdisplay $rootdev > /dev/null 2>&1 - if [ $? -eq 0 ]; then - echo "removing backing store: $rootdev" - lvremove -f $rootdev - fi - elif [ -h "$rootdev" -o -d "$rootdev" ]; then - if which btrfs >/dev/null 2>&1 && - btrfs subvolume list "$rootdev" >/dev/null 2>&1; then - btrfs subvolume delete "$rootdev" - else - # In case rootfs is not under $lxc_path/$lxc_name, remove it - rm -rf --one-file-system --preserve-root $rootdev - fi - fi + if [ -b "$rootdev" -o -h "$rootdev" ]; then + lvdisplay $rootdev > /dev/null 2>&1 + if [ $? -eq 0 ]; then + echo "removing backing store: $rootdev" + lvremove -f $rootdev + fi + elif [ -h "$rootdev" -o -d "$rootdev" ]; then + if which btrfs >/dev/null 2>&1 && + btrfs subvolume list "$rootdev" >/dev/null 2>&1; then + btrfs subvolume delete "$rootdev" + else + # In case rootfs is not under $lxc_path/$lxc_name, remove it + rm -rf --one-file-system --preserve-root $rootdev + fi + fi fi + # recursively remove the container to remove old container configuration rm -rf --one-file-system --preserve-root $lxc_path/$lxc_name diff --git a/src/lxc/lxc-device b/src/lxc/lxc-device new file mode 100644 index 000000000..db9399d2a --- /dev/null +++ b/src/lxc/lxc-device @@ -0,0 +1,95 @@ +#!/usr/bin/python3 +# +# lxc-device: Add devices to a running container +# +# This python implementation is based on the work done in the original +# shell implementation done by Serge Hallyn in Ubuntu (and other contributors) +# +# (C) Copyright Canonical Ltd. 2012 +# +# Authors: +# Stéphane Graber +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# NOTE: To remove once the API is stabilized +import warnings +warnings.filterwarnings("ignore", "The python-lxc API isn't yet stable") + +import argparse +import gettext +import lxc +import os +import sys + +_ = gettext.gettext +gettext.textdomain("lxc-device") + +# Begin parsing the command line +parser = argparse.ArgumentParser(description=_("LXC: Manage devices"), + formatter_class=argparse.RawTextHelpFormatter) + +# Global arguments +parser.add_argument("-n", dest="container", metavar="CONTAINER", + help=_("Name of the container to add the device to"), + required=True) + +# Commands +subparsers = parser.add_subparsers() +subparser_add = subparsers.add_parser('add', help=_('Add a device')) +subparser_add.set_defaults(action="add") + +subparser_add.add_argument(dest="device", metavar="DEVICE", + help=_("Add a device " + "(path to a node or interface name)")) + +subparser_add.add_argument(dest="name", metavar="NAME", nargs="?", + help=_("Use an alternative path or name " + "in the container")) + +args = parser.parse_args() + +# Some basic checks +if not hasattr(args, "action"): + parser.error(_("You must specify an action.")) + +## The user needs to be uid 0 +if not os.geteuid() == 0: + parser.error(_("You must be root to run this script. Try running: sudo %s" + % (sys.argv[0]))) + +## Don't rename if no alternative name +if not args.name: + args.name = args.device + +## Check that the container is ready +container = lxc.Container(args.container) +if not container.running: + parser.error("The container must be running.") + +# Do the work +if args.action == "add": + if os.path.exists("/sys/class/net/%s/" % args.device): + ret = container.add_device_net(args.device, args.name) + else: + ret = container.add_device_node(args.device, args.name) + + if ret: + print("Added '%s' to '%s' as '%s'." % + (args.device, container.name, args.name)) + else: + print("Failed to add '%s' to '%s' as '%s'." % + (args.device, container.name, args.name)) diff --git a/src/lxc/lxc-ls b/src/lxc/lxc-ls new file mode 100644 index 000000000..98b786167 --- /dev/null +++ b/src/lxc/lxc-ls @@ -0,0 +1,251 @@ +#!/usr/bin/python3 +# +# lxc-ls: List containers +# +# This python implementation is based on the work done in the original +# shell implementation done by Serge Hallyn in Ubuntu (and other contributors) +# +# (C) Copyright Canonical Ltd. 2012 +# +# Authors: +# Stéphane Graber +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# NOTE: To remove once the API is stabilized +import warnings +warnings.filterwarnings("ignore", "The python-lxc API isn't yet stable") + +import argparse +import gettext +import lxc +import os +import re +import sys + +_ = gettext.gettext +gettext.textdomain("lxc-ls") + + +# Functions used later on +def batch(iterable, cols=1): + import math + + length = len(iterable) + lines = math.ceil(length / cols) + + for line in range(lines): + fields = [] + for col in range(cols): + index = line + (col * lines) + if index < length: + fields.append(iterable[index]) + yield fields + + +def getTerminalSize(): + import os + env = os.environ + + def ioctl_GWINSZ(fd): + try: + import fcntl + import termios + import struct + cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, + '1234')) + return cr + except: + return + + cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) + if not cr: + try: + fd = os.open(os.ctermid(), os.O_RDONLY) + cr = ioctl_GWINSZ(fd) + os.close(fd) + except: + pass + + if not cr: + cr = (env.get('LINES', 25), env.get('COLUMNS', 80)) + + return int(cr[1]), int(cr[0]) + +# Begin parsing the command line +parser = argparse.ArgumentParser(description=_("LXC: List containers"), + formatter_class=argparse.RawTextHelpFormatter) + +parser.add_argument("-1", dest="one", action="store_true", + help=_("list one container per line (default when piped)")) + +parser.add_argument("--active", action="store_true", + help=_("list only active containers " + "(same as --running --frozen)")) + +parser.add_argument("--frozen", dest="state", action="append_const", + const="FROZEN", help=_("list only frozen containers")) + +parser.add_argument("--running", dest="state", action="append_const", + const="RUNNING", help=_("list only running containers")) + +parser.add_argument("--stopped", dest="state", action="append_const", + const="STOPPED", help=_("list only stopped containers")) + +parser.add_argument("--fancy", action="store_true", + help=_("use fancy output")) + +parser.add_argument("--fancy-format", type=str, default="name,state,ipv4,ipv6", + help=_("comma separated list of fields to show")) + +parser.add_argument("filter", metavar='FILTER', type=str, nargs="?", + help=_("regexp to be applied on the container list")) + +args = parser.parse_args() + +# --active is the same as --running --frozen +if args.active: + if not args.state: + args.state = [] + args.state += ["RUNNING", "FROZEN"] + +# If the output is piped, default to --one +if not sys.stdout.isatty(): + args.one = True + +# Turn args.fancy_format into a list +args.fancy_format = args.fancy_format.strip().split(",") + +# Basic checks +## The user needs to be uid 0 +if not os.geteuid() == 0 and (args.fancy or args.state): + parser.error(_("You must be root to access advanced container properties. " + "Try running: sudo %s" + % (sys.argv[0]))) + +# List of containers, stored as dictionaries +containers = [] +for container_name in lxc.list_containers(): + entry = {} + entry['name'] = container_name + + # Apply filter + if args.filter and not re.match(args.filter, container_name): + continue + + # Return before grabbing the object (non-root) + if not args.state and not args.fancy: + containers.append(entry) + continue + + container = lxc.Container(container_name) + + # Filter by status + if args.state and container.state not in args.state: + continue + + # Nothing more is needed if we're not printing some fancy output + if not args.fancy: + containers.append(entry) + continue + + # Some extra field we may want + if 'state' in args.fancy_format: + entry['state'] = container.state + if 'pid' in args.fancy_format: + entry['pid'] = "-" + if container.init_pid != -1: + entry['pid'] = str(container.init_pid) + + # Get the IPs + for protocol in ('ipv4', 'ipv6'): + if protocol in args.fancy_format: + entry[protocol] = "-" + ips = container.get_ips(protocol=protocol, timeout=1) + if ips: + entry[protocol] = ", ".join(ips) + + containers.append(entry) + + +# Print the list +## Standard list with one entry per line +if not args.fancy and args.one: + for container in sorted(containers, + key=lambda container: container['name']): + print(container['name']) + sys.exit(0) + +## Standard list with multiple entries per line +if not args.fancy and not args.one: + # Get the longest name and extra simple list + field_maxlength = 0 + container_names = [] + for container in containers: + if len(container['name']) > field_maxlength: + field_maxlength = len(container['name']) + container_names.append(container['name']) + + # Figure out how many we can put per line + width = getTerminalSize()[0] + + entries = int(width / (field_maxlength + 2)) + if entries == 0: + entries = 1 + + for line in batch(sorted(container_names), entries): + line_format = "" + for index in range(len(line)): + line_format += "{line[%s]:%s}" % (index, field_maxlength + 2) + + print(line_format.format(line=line)) + +## Fancy listing +if args.fancy: + field_maxlength = {} + + # Get the maximum length per field + for field in args.fancy_format: + field_maxlength[field] = len(field) + + for container in containers: + for field in args.fancy_format: + if len(container[field]) > field_maxlength[field]: + field_maxlength[field] = len(container[field]) + + # Generate the line format string based on the maximum length and + # a 2 character padding + line_format = "" + index = 0 + for field in args.fancy_format: + line_format += "{fields[%s]:%s}" % (index, field_maxlength[field] + 2) + index += 1 + + # Get the line length minus the padding of the last field + line_length = -2 + for field in field_maxlength: + line_length += field_maxlength[field] + 2 + + # Print header + print(line_format.format(fields=[header.upper() + for header in args.fancy_format])) + print("-" * line_length) + + # Print the entries + for container in sorted(containers, + key=lambda container: container['name']): + fields = [container[field] for field in args.fancy_format] + print(line_format.format(fields=fields)) diff --git a/src/lxc/lxc-netstat.in b/src/lxc/lxc-netstat.in index 4abe25f82..df18620a7 100644 --- a/src/lxc/lxc-netstat.in +++ b/src/lxc/lxc-netstat.in @@ -18,110 +18,110 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA usage() { - echo "usage: $(basename $0) -n|--name -- [netstat_options]" >&2 + echo "usage: $(basename $0) -n|--name -- [netstat_options]" >&2 } help() { - usage - echo >&2 - echo "Execute 'netstat' for the specified container." >&2 - echo >&2 - echo " --name NAME specify the container name" >&2 - echo " NETSTAT_OPTIONS netstat command options (see \`netstat --help')" >&2 + usage + echo >&2 + echo "Execute 'netstat' for the specified container." >&2 + echo >&2 + echo " --name NAME specify the container name" >&2 + echo " NETSTAT_OPTIONS netstat command options (see \`netstat --help')" >&2 } get_parent_cgroup() { - local hierarchies hierarchy fields subsystems init_cgroup mountpoint + local hierarchies hierarchy fields subsystems init_cgroup mountpoint - parent_cgroup="" + parent_cgroup="" - # Obtain a list of hierarchies that contain one or more subsystems - hierarchies=$(tail -n +2 /proc/cgroups | cut -f 2) + # Obtain a list of hierarchies that contain one or more subsystems + hierarchies=$(tail -n +2 /proc/cgroups | cut -f 2) - # Iterate through the list until a suitable hierarchy is found - for hierarchy in $hierarchies; do - # Obtain information about the init process in the hierarchy - fields=$(grep -E "^$hierarchy:" /proc/1/cgroup | head -n 1) - if [ -z "$fields" ]; then continue; fi - fields=${fields#*:} + # Iterate through the list until a suitable hierarchy is found + for hierarchy in $hierarchies; do + # Obtain information about the init process in the hierarchy + fields=$(grep -E "^$hierarchy:" /proc/1/cgroup | head -n 1) + if [ -z "$fields" ]; then continue; fi + fields=${fields#*:} - # Get a comma-separated list of the hierarchy's subsystems - subsystems=${fields%:*} + # Get a comma-separated list of the hierarchy's subsystems + subsystems=${fields%:*} - # Get the cgroup of the init process in the hierarchy - init_cgroup=${fields#*:} + # Get the cgroup of the init process in the hierarchy + init_cgroup=${fields#*:} - # Get the filesystem mountpoint of the hierarchy - mountpoint=$(grep -E "^cgroup [^ ]+ [^ ]+ ([^ ]+,)?$subsystems(,[^ ]+)? " /proc/self/mounts | cut -d ' ' -f 2) - if [ -z "$mountpoint" ]; then continue; fi + # Get the filesystem mountpoint of the hierarchy + mountpoint=$(grep -E "^cgroup [^ ]+ [^ ]+ ([^ ]+,)?$subsystems(,[^ ]+)? " /proc/self/mounts | cut -d ' ' -f 2) + if [ -z "$mountpoint" ]; then continue; fi - # Return the absolute path to the containers' parent cgroup - # (do not append '/lxc' if the hierarchy contains the 'ns' subsystem) - if [[ ",$subsystems," == *,ns,* ]]; then - parent_cgroup="${mountpoint}${init_cgroup%/}" - else - parent_cgroup="${mountpoint}${init_cgroup%/}/lxc" - fi - break - done + # Return the absolute path to the containers' parent cgroup + # (do not append '/lxc' if the hierarchy contains the 'ns' subsystem) + if [[ ",$subsystems," == *,ns,* ]]; then + parent_cgroup="${mountpoint}${init_cgroup%/}" + else + parent_cgroup="${mountpoint}${init_cgroup%/}/lxc" + fi + break + done } exec="" while true; do - case $1 in - -h|--help) - help; exit 1;; - -n|--name) - name=$2; shift 2;; - --exec) - exec="exec"; shift;; - --) - shift; break;; - *) - break;; - esac + case $1 in + -h|--help) + help; exit 1;; + -n|--name) + name=$2; shift 2;; + --exec) + exec="exec"; shift;; + --) + shift; break;; + *) + break;; + esac done if [ "$(id -u)" != "0" ]; then - echo "$(basename $0): must be run as root" >&2 - exit 1 + echo "$(basename $0): must be run as root" >&2 + exit 1 fi if [ -z "$name" ]; then - usage - exit 1 + usage + exit 1 fi if [ -z "$exec" ]; then - exec @BINDIR@/lxc-unshare -s MOUNT -- $0 -n $name --exec "$@" + exec @BINDIR@/lxc-unshare -s MOUNT -- $0 -n $name --exec "$@" fi lxc-info -n $name 2>&1 | grep -q 'STOPPED' if [ $? -eq 0 ]; then - echo "$(basename $0): container '$name' is not running" >&2 - exit 1 + echo "$(basename $0): container '$name' is not running" >&2 + exit 1 fi get_parent_cgroup if [ ! -d "$parent_cgroup" ]; then - echo "$(basename $0): no cgroup mount point found" >&2 - exit 1 + echo "$(basename $0): no cgroup mount point found" >&2 + exit 1 fi pid=$(head -1 $parent_cgroup/$name/tasks) if [ -z "$pid" ]; then - echo "$(basename $0): no process found for '$name'" >&2 - exit 1 + echo "$(basename $0): no process found for '$name'" >&2 + exit 1 fi tmpdir=$(mktemp -d) if [ -z "$tmpdir" -o ! -d "$tmpdir" ]; then - echo "$(basename $0): unable to create temporary directory" >&2 - exit 1 + echo "$(basename $0): unable to create temporary directory" >&2 + exit 1 fi # Bind mount /proc/$pid/net onto /proc/net before calling 'netstat'. diff --git a/src/lxc/lxc-ps.in b/src/lxc/lxc-ps.in index cf3f1e933..1f45044b4 100644 --- a/src/lxc/lxc-ps.in +++ b/src/lxc/lxc-ps.in @@ -19,125 +19,124 @@ usage() { - echo "usage: $(basename $0) [--lxc | --name NAME] [--] [PS_OPTIONS...]" >&2 + echo "usage: $(basename $0) [--lxc | --name NAME] [--] [PS_OPTIONS...]" >&2 } help() { - usage - echo >&2 - echo "List current processes with container names." >&2 - echo >&2 - echo " --lxc show processes in all containers" >&2 - echo " --name NAME show processes in the specified container" >&2 - echo " (multiple containers can be separated by commas)" >&2 - echo " PS_OPTIONS ps command options (see \`ps --help')" >&2 + usage + echo >&2 + echo "List current processes with container names." >&2 + echo >&2 + echo " --lxc show processes in all containers" >&2 + echo " --name NAME show processes in the specified container" >&2 + echo " (multiple containers can be separated by commas)" >&2 + echo " PS_OPTIONS ps command options (see \`ps --help')" >&2 } get_parent_cgroup() { - local hierarchies hierarchy fields subsystems init_cgroup mountpoint + local hierarchies hierarchy fields subsystems init_cgroup mountpoint - parent_cgroup="" + parent_cgroup="" - # Obtain a list of hierarchies that contain one or more subsystems - hierarchies=$(tail -n +2 /proc/cgroups | cut -f 2) + # Obtain a list of hierarchies that contain one or more subsystems + hierarchies=$(tail -n +2 /proc/cgroups | cut -f 2) - # Iterate through the list until a suitable hierarchy is found - for hierarchy in $hierarchies; do - # Obtain information about the init process in the hierarchy - fields=$(grep -E "^$hierarchy:" /proc/1/cgroup | head -n 1) - if [ -z "$fields" ]; then continue; fi - fields=${fields#*:} + # Iterate through the list until a suitable hierarchy is found + for hierarchy in $hierarchies; do + # Obtain information about the init process in the hierarchy + fields=$(grep -E "^$hierarchy:" /proc/1/cgroup | head -n 1) + if [ -z "$fields" ]; then continue; fi + fields=${fields#*:} - # Get a comma-separated list of the hierarchy's subsystems - subsystems=${fields%:*} + # Get a comma-separated list of the hierarchy's subsystems + subsystems=${fields%:*} - # Get the cgroup of the init process in the hierarchy - init_cgroup=${fields#*:} + # Get the cgroup of the init process in the hierarchy + init_cgroup=${fields#*:} - # Get the filesystem mountpoint of the hierarchy - mountpoint=$(grep -E "^cgroup [^ ]+ [^ ]+ ([^ ]+,)?$subsystems(,[^ ]+)? " /proc/self/mounts | cut -d ' ' -f 2) - if [ -z "$mountpoint" ]; then continue; fi + # Get the filesystem mountpoint of the hierarchy + mountpoint=$(grep -E "^cgroup [^ ]+ [^ ]+ ([^ ]+,)?$subsystems(,[^ ]+)? " /proc/self/mounts | cut -d ' ' -f 2) + if [ -z "$mountpoint" ]; then continue; fi - # Return the absolute path to the containers' parent cgroup - # (do not append '/lxc' if the hierarchy contains the 'ns' subsystem) - if [[ ",$subsystems," == *,ns,* ]]; then - parent_cgroup="${mountpoint}${init_cgroup%/}" - else - parent_cgroup="${mountpoint}${init_cgroup%/}/lxc" - fi - break - done + # Return the absolute path to the containers' parent cgroup + # (do not append '/lxc' if the hierarchy contains the 'ns' subsystem) + if [[ ",$subsystems," == *,ns,* ]]; then + parent_cgroup="${mountpoint}${init_cgroup%/}" + else + parent_cgroup="${mountpoint}${init_cgroup%/}/lxc" + fi + break + done } containers="" list_container_processes=0 while true; do - case $1 in - -h|--help) - help; exit 1;; - -n|--name) - containers=$2; list_container_processes=1; shift 2;; - --lxc) - list_container_processes=1; shift;; - --) - shift; break;; - *) - break;; + case $1 in + -h|--help) + help; exit 1;; + -n|--name) + containers=$2; list_container_processes=1; shift 2;; + --lxc) + list_container_processes=1; shift;; + --) + shift; break;; + *) + break;; esac done if [ "$list_container_processes" -eq "1" ]; then - set -- -e $@ + set -- -e $@ fi get_parent_cgroup if [ ! -d "$parent_cgroup" ]; then - echo "$(basename $0): no cgroup mount point found" >&2 - exit 1 + echo "$(basename $0): no cgroup mount point found" >&2 + exit 1 fi declare -a container_of_pid container_field_width=9 IFS="," if [ -z "$containers" ]; then - containers=( $(find $parent_cgroup -mindepth 1 -maxdepth 1 -type d -printf "%f," 2>/dev/null) ) + containers=( $(find $parent_cgroup -mindepth 1 -maxdepth 1 -type d -printf "%f," 2>/dev/null) ) else - containers=( $containers ) + containers=( $containers ) fi declare -i pid IFS=$'\n' for container in ${containers[@]}; do - if [ "${#container}" -gt "$container_field_width" ]; then - container_field_width=${#container} - fi + if [ "${#container}" -gt "$container_field_width" ]; then + container_field_width=${#container} + fi - if [ -f "$parent_cgroup/$container/tasks" ]; then - while read pid; do - container_of_pid[$pid]=$container - done < "$parent_cgroup/$container/tasks" - fi + if [ -f "$parent_cgroup/$container/tasks" ]; then + while read pid; do + container_of_pid[$pid]=$container + done < "$parent_cgroup/$container/tasks" + fi done declare -i line_pid_end_position while read line; do - if [ -z "$line_pid_end_position" ]; then - if [[ "$line" != *" PID"* ]]; then - echo "$(basename $0): no PID column found in \`ps' output" >&2 - exit 1 - fi + if [ -z "$line_pid_end_position" ]; then + if [[ "$line" != *" PID"* ]]; then + echo "$(basename $0): no PID column found in \`ps' output" >&2 + exit 1 + fi - buffer=${line%" PID"*} - let line_pid_end_position=${#buffer}+4 - printf "%-${container_field_width}s %s\n" "CONTAINER" "$line" - continue - fi + buffer=${line%" PID"*} + let line_pid_end_position=${#buffer}+4 + printf "%-${container_field_width}s %s\n" "CONTAINER" "$line" + continue + fi - buffer=${line:0:$line_pid_end_position} - pid=${buffer##* } - if [ "$list_container_processes" -eq "0" -o ! -z "${container_of_pid[pid]}" ]; then - printf "%-${container_field_width}s %s\n" "${container_of_pid[pid]}" "$line" - fi + buffer=${line:0:$line_pid_end_position} + pid=${buffer##* } + if [ "$list_container_processes" -eq "0" -o ! -z "${container_of_pid[pid]}" ]; then + printf "%-${container_field_width}s %s\n" "${container_of_pid[pid]}" "$line" + fi done < <(ps "$@") - diff --git a/src/lxc/lxc-setcap.in b/src/lxc/lxc-setcap.in index 7fd390c00..fcbe2be31 100644 --- a/src/lxc/lxc-setcap.in +++ b/src/lxc/lxc-setcap.in @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # # lxc: linux Container library @@ -81,35 +81,43 @@ lxc_dropcaps() chmod 0755 @LXCPATH@ } -shortoptions='hd' -longoptions='help' - -getopt=$(getopt -o $shortoptions --longoptions $longoptions -- "$@") -if [ $? != 0 ]; then +usage_err() { + [ -n "$1" ] && echo "$1" >&2 usage exit 1 -fi +} -eval set -- "$getopt" +optarg_check() { + if [ -z "$2" ]; then + usage_err "option '$1' requires an argument" + fi +} -while true; do - case "$1" in - -d) - LXC_DROP_CAPS="yes" - shift - ;; - -h|--help) - help - exit 0 - ;; - --) - shift - break - ;; - *) - usage - exit 1 - ;; +while [ $# -gt 0 ]; do + opt="$1" + shift + case "$opt" in + -d) + LXC_DROP_CAPS="yes" + ;; + -h|--help) + help + exit 0 + ;; + --) + break + ;; + -?) + usage_err "unknown option '$opt'" + ;; + -*) + # split opts -abc into -a -b -c + set -- $(echo "${opt#-}" | sed 's/\(.\)/ -\1/g') "$@" + ;; + *) + usage + exit 1 + ;; esac done; diff --git a/src/lxc/lxc-setuid.in b/src/lxc/lxc-setuid.in index 84f18af59..4e92bb0b6 100644 --- a/src/lxc/lxc-setuid.in +++ b/src/lxc/lxc-setuid.in @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # # lxc: linux Container library @@ -41,9 +41,9 @@ help() { setuid() { if [ "$1" = "-r" ]; then - chmod -s $2 + chmod -s $2 else - chmod +s $1 + chmod +s $1 fi } @@ -78,35 +78,43 @@ lxc_dropuid() chmod 0755 @LXCPATH@ } -shortoptions='hd' -longoptions='help' - -getopt=$(getopt -o $shortoptions --longoptions $longoptions -- "$@") -if [ $? != 0 ]; then +usage_err() { + [ -n "$1" ] && echo "$1" >&2 usage exit 1 -fi +} -eval set -- "$getopt" +optarg_check() { + if [ -z "$2" ]; then + usage_err "option '$1' requires an argument" + fi +} -while true; do - case "$1" in - -d) - LXC_DROP_CAPS="yes" - shift - ;; - -h|--help) - help - exit 0 - ;; - --) - shift - break - ;; - *) - usage - exit 1 - ;; +while [ $# -gt 0 ]; do + opt="$1" + shift + case "$opt" in + -d) + LXC_DROP_CAPS="yes" + ;; + -h|--help) + help + exit 0 + ;; + --) + break + ;; + -?) + usage_err "unknown option '$opt'" + ;; + -*) + # split opts -abc into -a -b -c + set -- $(echo "${opt#-}" | sed 's/\(.\)/ -\1/g') "$@" + ;; + *) + usage + exit 1 + ;; esac done; diff --git a/src/lxc/lxc-start-ephemeral.in b/src/lxc/lxc-start-ephemeral.in new file mode 100644 index 000000000..e11919fb4 --- /dev/null +++ b/src/lxc/lxc-start-ephemeral.in @@ -0,0 +1,289 @@ +#!/usr/bin/python3 +# +# lxc-start-ephemeral: Start a copy of a container using an overlay +# +# This python implementation is based on the work done in the original +# shell implementation done by Serge Hallyn in Ubuntu (and other contributors) +# +# (C) Copyright Canonical Ltd. 2012 +# +# Authors: +# Stéphane Graber +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# NOTE: To remove once the API is stabilized +import warnings +warnings.filterwarnings("ignore", "The python-lxc API isn't yet stable") + +import argparse +import gettext +import lxc +import os +import sys +import subprocess +import tempfile + +_ = gettext.gettext +gettext.textdomain("lxc-start-ephemeral") + + +# Other functions +def randomMAC(): + import random + + mac = [0x00, 0x16, 0x3e, + random.randint(0x00, 0x7f), + random.randint(0x00, 0xff), + random.randint(0x00, 0xff)] + return ':'.join(map(lambda x: "%02x" % x, mac)) + +# Begin parsing the command line +parser = argparse.ArgumentParser(description=_( + "LXC: Start an ephemeral container"), + formatter_class=argparse.RawTextHelpFormatter, + epilog=_("If a COMMAND is given, then the " + """container will run only as long +as the command runs. +If no COMMAND is given, this command will attach to tty1 and stop the +container when exiting (with ctrl-a-q). + +If no COMMAND is given and -d is used, the name and IP addresses of the +container will be printed to the console.""")) + +parser.add_argument("--orig", "-o", type=str, required=True, + help=_("name of the original container")) + +parser.add_argument("--bdir", "-b", type=str, + help=_("directory to bind mount into container")) + +parser.add_argument("--user", "-u", type=str, + help=_("the user to connect to the container as")) + +parser.add_argument("--key", "-S", type=str, + help=_("the path to the SSH key to use to connect")) + +parser.add_argument("--daemon", "-d", action="store_true", + help=_("run in the background")) + +parser.add_argument("--union-type", "-U", type=str, default="overlayfs", + choices=("overlayfs", "aufs"), + help=_("type of union (overlayfs or aufs), " + "defaults to overlayfs.")) + +parser.add_argument("--keep-data", "-k", action="store_true", + help=_("Use a persistent backend instead of tmpfs.")) + +parser.add_argument("command", metavar='CMD', type=str, nargs="*", + help=_("Run specific command in container " + "(command as argument)")) + +args = parser.parse_args() + +# Basic requirements check +## Check that -d and CMD aren't used at the same time +if args.command and args.daemon: + parser.error(_("You can't use -d and a command at the same time.")) + +## The user needs to be uid 0 +if not os.geteuid() == 0: + parser.error(_("You must be root to run this script. Try running: sudo %s" + % (sys.argv[0]))) + +# Load the orig container +orig = lxc.Container(args.orig) +if not orig.defined: + parser.error(_("Source container '%s' doesn't exist." % args.orig)) + +# Create the new container paths +dest_path = tempfile.mkdtemp(prefix="%s-" % args.orig, dir="@LXCPATH@") +os.mkdir(os.path.join(dest_path, "rootfs")) + +# Setup the new container's configuration +dest = lxc.Container(os.path.basename(dest_path)) +dest.load_config(orig.config_file_name) +dest.set_config_item("lxc.utsname", dest.name) +dest.set_config_item("lxc.rootfs", os.path.join(dest_path, "rootfs")) +dest.set_config_item("lxc.network.hwaddr", randomMAC()) + +overlay_dirs = [(orig.get_config_item("lxc.rootfs"), "%s/rootfs/" % dest_path)] + +# Generate a new fstab +if orig.get_config_item("lxc.mount"): + dest.set_config_item("lxc.mount", os.path.join(dest_path, "fstab")) + with open(orig.get_config_item("lxc.mount"), "r") as orig_fd: + with open(dest.get_config_item("lxc.mount"), "w+") as dest_fd: + for line in orig_fd.read().split("\n"): + # Start by replacing any reference to the container rootfs + line.replace(orig.get_config_item("lxc.rootfs"), + dest.get_config_item("lxc.rootfs")) + + # Skip any line that's not a bind mount + fields = line.split() + if len(fields) < 4: + dest_fd.write("%s\n" % line) + continue + + if fields[2] != "bind" and "bind" not in fields[3]: + dest_fd.write("%s\n" % line) + continue + + # Process any remaining line + dest_mount = os.path.abspath(os.path.join("%s/rootfs/" % ( + dest_path), fields[1])) + + if dest_mount == os.path.abspath("%s/rootfs/%s" % ( + dest_path, args.bdir)): + + dest_fd.write("%s\n" % line) + continue + + if "%s/rootfs/" % dest_path not in dest_mount: + print(_("Skipping mount entry '%s' as it's outside " + "of the container rootfs.") % line) + + overlay_dirs += [(fields[0], dest_mount)] + +# Generate pre-mount script +with open(os.path.join(dest_path, "pre-mount"), "w+") as fd: + os.fchmod(fd.fileno(), 0o755) + fd.write("""#!/bin/sh +LXC_DIR="%s" +LXC_BASE="%s" +LXC_NAME="%s" +""" % (dest_path, orig.name, dest.name)) + + count = 0 + for entry in overlay_dirs: + target = "%s/delta%s" % (dest_path, count) + fd.write("mkdir -p %s %s\n" % (target, entry[1])) + + if not args.keep_data: + fd.write("mount -n -t tmpfs none %s\n" % (target)) + + if args.union_type == "overlayfs": + fd.write("mount -n -t overlayfs" + " -oupperdir=%s,lowerdir=%s none %s\n" % ( + target, + entry[0], + entry[1])) + elif args.union_type == "aufs": + fd.write("mount -n -t aufs " + "-o br=${upper}=rw:${lower}=ro,noplink none %s\n" % ( + target, + entry[0], + entry[1])) + count += 1 + + if args.bdir: + if not os.path.exists(args.bdir): + print(_("Path '%s' doesn't exist, won't be bind-mounted.") % + args.bdir) + else: + src_path = os.path.abspath(args.bdir) + dst_path = "%s/rootfs/%s" % (dest_path, os.path.abspath(args.bdir)) + fd.write("mkdir -p %s\nmount -n --bind %s %s\n" % ( + dst_path, src_path, dst_path)) + + fd.write(""" +[ -e $LXC_DIR/configured ] && exit 0 +for file in $LXC_DIR/rootfs/etc/hostname \\ + $LXC_DIR/rootfs/etc/hosts \\ + $LXC_DIR/rootfs/etc/sysconfig/network \\ + $LXC_DIR/rootfs/etc/sysconfig/network-scripts/ifcfg-eth0; do + [ -f "$file" ] && sed -i -e "s/$LXC_BASE/$LXC_NAME/" $file +done +touch $LXC_DIR/configured +""") + +dest.set_config_item("lxc.hook.pre-mount", + os.path.join(dest_path, "pre-mount")) + +# Generate post-stop script +if not args.keep_data: + with open(os.path.join(dest_path, "post-stop"), "w+") as fd: + os.fchmod(fd.fileno(), 0o755) + fd.write("""#!/bin/sh +[ -d "%s" ] && rm -Rf "%s" +""" % (dest_path, dest_path)) + + dest.set_config_item("lxc.hook.post-stop", + os.path.join(dest_path, "post-stop")) + +dest.save_config() + +# Start the container +if not dest.start() or not dest.wait("RUNNING", timeout=5): + print(_("The container '%s' failed to start.") % dest.name) + dest.stop() + if dest.defined: + dest.destroy() + sys.exit(1) + +# Deal with the case where we just attach to the container's console +if not args.command and not args.daemon: + dest.console(tty=1) + dest.shutdown(timeout=5) + sys.exit(0) + +# Try to get the IP addresses +ips = dest.get_ips(timeout=10) + +# Deal with the case where we just print info about the container +if args.daemon: + print(_("""The ephemeral container is now started. + +You can enter it from the command line with: lxc-console -n %s +The following IP addresses have be found in the container: +%s""") % (dest.name, + "\n".join([" - %s" % entry for entry in ips] + or [" - %s" % _("No address could be found")]))) + sys.exit(0) + +# Now deal with the case where we want to run a command in the container +if not ips: + print(_("Failed to get an IP for container '%s'.") % dest.name) + dest.stop() + if dest.defined: + dest.destroy() + sys.exit(1) + +# NOTE: To replace by .attach() once the kernel supports it +cmd = ["ssh", + "-o", "StrictHostKeyChecking=no", + "-o", "UserKnownHostsFile=/dev/null"] + +if args.user: + cmd += ["-l", args.user] + +if args.key: + cmd += ["-k", args.key] + +for ip in ips: + ssh_cmd = cmd + [ip] + args.command + retval = subprocess.call(ssh_cmd, universal_newlines=True) + if retval == 255: + print(_("SSH failed to connect, trying next IP address.")) + continue + + if retval != 0: + print(_("Command returned with non-zero return code: %s") % retval) + break + +# Shutdown the container +dest.shutdown(timeout=5) + +sys.exit(retval) diff --git a/src/lxc/lxc-version.in b/src/lxc/lxc-version.in index 1bd055a71..b6875da26 100644 --- a/src/lxc/lxc-version.in +++ b/src/lxc/lxc-version.in @@ -1,3 +1,3 @@ -#!/bin/bash +#!/bin/sh echo "lxc version: @PACKAGE_VERSION@" diff --git a/src/lxc/lxc.h b/src/lxc/lxc.h index ae8a3f725..349bbbc0d 100644 --- a/src/lxc/lxc.h +++ b/src/lxc/lxc.h @@ -84,6 +84,7 @@ extern int lxc_monitor_open(void); * data was readen, < 0 otherwise */ extern int lxc_monitor_read(int fd, struct lxc_msg *msg); +extern int lxc_monitor_read_timeout(int fd, struct lxc_msg *msg, int timeout); /* * Close the fd associated with the monitoring @@ -178,6 +179,30 @@ extern int lxc_restart(const char *, int, struct lxc_conf *, int); */ extern const char const *lxc_version(void); +/* + * Create and return a new lxccontainer struct. + */ +extern struct lxc_container *lxc_container_new(const char *name); + +/* + * Returns 1 on success, 0 on failure. + */ +extern int lxc_container_get(struct lxc_container *c); + +/* + * Put a lxccontainer struct reference. + * Return -1 on error. + * Return 0 if this was not the last reference. + * If it is the last reference, free the lxccontainer and return 1. + */ +extern int lxc_container_put(struct lxc_container *c); + +/* + * Get a list of valid wait states. + * If states is NULL, simply return the number of states + */ +extern int lxc_get_wait_states(const char **states); + #ifdef __cplusplus } #endif diff --git a/src/lxc/lxc_attach.c b/src/lxc/lxc_attach.c index 955e9f445..e292bc4be 100644 --- a/src/lxc/lxc_attach.c +++ b/src/lxc/lxc_attach.c @@ -40,22 +40,30 @@ #include "start.h" #include "sync.h" #include "log.h" +#include "namespace.h" lxc_log_define(lxc_attach_ui, lxc); static const struct option my_longopts[] = { {"elevated-privileges", no_argument, 0, 'e'}, {"arch", required_argument, 0, 'a'}, + {"namespaces", required_argument, 0, 's'}, + {"remount-sys-proc", no_argument, 0, 'R'}, LXC_COMMON_OPTIONS }; static int elevated_privileges = 0; static signed long new_personality = -1; +static int namespace_flags = -1; +static int remount_sys_proc = 0; static int my_parser(struct lxc_arguments* args, int c, char* arg) { + int ret; + switch (c) { case 'e': elevated_privileges = 1; break; + case 'R': remount_sys_proc = 1; break; case 'a': new_personality = lxc_config_parse_arch(arg); if (new_personality < 0) { @@ -63,6 +71,14 @@ static int my_parser(struct lxc_arguments* args, int c, char* arg) return -1; } break; + case 's': + namespace_flags = 0; + ret = lxc_fill_namespace_flags(arg, &namespace_flags); + if (ret) + return -1; + /* -s implies -e */ + elevated_privileges = 1; + break; } return 0; @@ -83,7 +99,18 @@ Options :\n\ WARNING: This may leak privleges into the container.\n\ Use with care.\n\ -a, --arch=ARCH Use ARCH for program instead of container's own\n\ - architecture.\n", + architecture.\n\ + -s, --namespaces=FLAGS\n\ + Don't attach to all the namespaces of the container\n\ + but just to the following OR'd list of flags:\n\ + MOUNT, PID, UTSNAME, IPC, USER or NETWORK\n\ + WARNING: Using -s implies -e, it may therefore\n\ + leak privileges into the container. Use with care.\n\ + -R, --remount-sys-proc\n\ + Remount /sys and /proc if not attaching to the\n\ + mount namespace when using -s in order to properly\n\ + reflect the correct namespace context. See the\n\ + lxc-attach(1) manual page for details.\n", .options = my_longopts, .parser = my_parser, .checker = NULL, @@ -96,6 +123,7 @@ int main(int argc, char *argv[]) struct passwd *passwd; struct lxc_proc_context_info *init_ctx; struct lxc_handler *handler; + void *cgroup_data = NULL; uid_t uid; char *curdir; @@ -124,6 +152,48 @@ int main(int argc, char *argv[]) return -1; } + if (!elevated_privileges) { + /* we have to do this now since /sys/fs/cgroup may not + * be available inside the container or we may not have + * the required permissions anymore + */ + ret = lxc_cgroup_prepare_attach(my_args.name, &cgroup_data); + if (ret < 0) { + ERROR("failed to prepare attaching to cgroup"); + return -1; + } + } + + curdir = get_current_dir_name(); + + /* determine which namespaces the container was created with + * by asking lxc-start + */ + if (namespace_flags == -1) { + namespace_flags = lxc_get_clone_flags(my_args.name); + /* call failed */ + if (namespace_flags == -1) { + ERROR("failed to automatically determine the " + "namespaces which the container unshared"); + return -1; + } + } + + /* we need to attach before we fork since certain namespaces + * (such as pid namespaces) only really affect children of the + * current process and not the process itself + */ + ret = lxc_attach_to_ns(init_pid, namespace_flags); + if (ret < 0) { + ERROR("failed to enter the namespace"); + return -1; + } + + if (curdir && chdir(curdir)) + WARN("could not change directory to '%s'", curdir); + + free(curdir); + /* hack: we need sync.h infrastructure - and that needs a handler */ handler = calloc(1, sizeof(*handler)); @@ -150,8 +220,22 @@ int main(int argc, char *argv[]) if (lxc_sync_wait_child(handler, LXC_SYNC_CONFIGURE)) return -1; - if (!elevated_privileges && lxc_cgroup_attach(my_args.name, pid)) - return -1; + /* now that we are done with all privileged operations, + * we can add ourselves to the cgroup. Since we smuggled in + * the fds earlier, we still have write permission + */ + if (!elevated_privileges) { + /* since setns() for pid namespaces only really + * affects child processes, the pid we have is + * still valid outside the container, so this is + * fine + */ + ret = lxc_cgroup_finish_attach(cgroup_data, pid); + if (ret < 0) { + ERROR("failed to attach process to cgroup"); + return -1; + } + } /* tell the child we are done initializing */ if (lxc_sync_wake_child(handler, LXC_SYNC_POST_CONFIGURE)) @@ -175,20 +259,20 @@ int main(int argc, char *argv[]) if (!pid) { lxc_sync_fini_parent(handler); + lxc_cgroup_dispose_attach(cgroup_data); - curdir = get_current_dir_name(); - - ret = lxc_attach_to_ns(init_pid); - if (ret < 0) { - ERROR("failed to enter the namespace"); - return -1; + /* A description of the purpose of this functionality is + * provided in the lxc-attach(1) manual page. We have to + * remount here and not in the parent process, otherwise + * /proc may not properly reflect the new pid namespace. + */ + if (!(namespace_flags & CLONE_NEWNS) && remount_sys_proc) { + ret = lxc_attach_remount_sys_proc(); + if (ret < 0) { + return -1; + } } - if (curdir && chdir(curdir)) - WARN("could not change directory to '%s'", curdir); - - free(curdir); - if (new_personality < 0) new_personality = init_ctx->personality; diff --git a/src/lxc/lxc_info.c b/src/lxc/lxc_info.c index 809769e4d..1a1cc22df 100644 --- a/src/lxc/lxc_info.c +++ b/src/lxc/lxc_info.c @@ -34,12 +34,14 @@ static bool state; static bool pid; +static char *test_state = NULL; static int my_parser(struct lxc_arguments* args, int c, char* arg) { switch (c) { case 's': state = true; break; case 'p': pid = true; break; + case 't': test_state = arg; break; } return 0; } @@ -47,6 +49,7 @@ static int my_parser(struct lxc_arguments* args, int c, char* arg) static const struct option my_longopts[] = { {"state", no_argument, 0, 's'}, {"pid", no_argument, 0, 'p'}, + {"state-is", required_argument, 0, 't'}, LXC_COMMON_OPTIONS, }; @@ -58,9 +61,11 @@ static struct lxc_arguments my_args = { lxc-info display some information about a container with the identifier NAME\n\ \n\ Options :\n\ - -n, --name=NAME NAME for name of the container\n\ - -s, --state shows the state of the container\n\ - -p, --pid shows the process id of the init container\n", + -n, --name=NAME NAME for name of the container\n\ + -s, --state shows the state of the container\n\ + -p, --pid shows the process id of the init container\n\ + -t, --state-is=STATE test if current state is STATE\n\ + returns success if it matches, false otherwise\n", .options = my_longopts, .parser = my_parser, .checker = NULL, @@ -81,10 +86,12 @@ int main(int argc, char *argv[]) if (!state && !pid) state = pid = true; - if (state) { + if (state || test_state) { ret = lxc_getstate(my_args.name); if (ret < 0) return 1; + if (test_state) + return strcmp(lxc_state2str(ret), test_state) != 0; printf("state:%10s\n", lxc_state2str(ret)); } diff --git a/src/lxc/lxc_monitor.c b/src/lxc/lxc_monitor.c index 10168fb01..4bd6ab015 100644 --- a/src/lxc/lxc_monitor.c +++ b/src/lxc/lxc_monitor.c @@ -102,7 +102,7 @@ int main(int argc, char *argv[]) switch (msg.type) { case lxc_msg_state: - printf("'%s' changed state to [%s]\n", + printf("'%s' changed state to [%s]\n", msg.name, lxc_state2str(msg.value)); break; default: diff --git a/src/lxc/lxc_start.c b/src/lxc/lxc_start.c index 81a5774c4..cedd908c0 100644 --- a/src/lxc/lxc_start.c +++ b/src/lxc/lxc_start.c @@ -62,6 +62,7 @@ static int my_parser(struct lxc_arguments* args, int c, char* arg) case 'f': args->rcfile = arg; break; case 'C': args->close_all_fds = 1; break; case 's': return lxc_config_define_add(&defines, arg); + case 'p': args->pidfile = arg; break; } return 0; } @@ -72,6 +73,7 @@ static const struct option my_longopts[] = { {"define", required_argument, 0, 's'}, {"console", required_argument, 0, 'c'}, {"close-all-fds", no_argument, 0, 'C'}, + {"pidfile", required_argument, 0, 'p'}, LXC_COMMON_OPTIONS }; @@ -85,6 +87,7 @@ lxc-start start COMMAND in specified container NAME\n\ Options :\n\ -n, --name=NAME NAME for name of the container\n\ -d, --daemon daemonize the container\n\ + -p, --pidfile=FILE Create a file with the process id\n\ -f, --rcfile=FILE Load configuration file FILE\n\ -c, --console=FILE Set the file output for the container console\n\ -C, --close-all-fds If any fds are inherited, close them\n\ @@ -95,6 +98,7 @@ Options :\n\ .parser = my_parser, .checker = NULL, .daemonize = 0, + .pidfile = NULL, }; int main(int argc, char *argv[]) @@ -107,6 +111,7 @@ int main(int argc, char *argv[]) "/sbin/init", '\0', }; + FILE *pid_fp = NULL; lxc_list_init(&defines); @@ -117,7 +122,7 @@ int main(int argc, char *argv[]) return err; if (!my_args.argc) - args = default_args; + args = default_args; else args = my_args.argv; @@ -199,6 +204,15 @@ int main(int argc, char *argv[]) free(console); } + if (my_args.pidfile != NULL) { + pid_fp = fopen(my_args.pidfile, "w"); + if (pid_fp == NULL) { + SYSERROR("failed to create pidfile '%s' for '%s'", + my_args.pidfile, my_args.name); + return err; + } + } + if (my_args.daemonize) { /* do an early check for needed privs, since otherwise the * user won't see the error */ @@ -214,6 +228,14 @@ int main(int argc, char *argv[]) } } + if (pid_fp != NULL) { + if (fprintf(pid_fp, "%d\n", getpid()) < 0) { + SYSERROR("failed to write '%s'", my_args.pidfile); + return err; + } + fclose(pid_fp); + } + if (my_args.close_all_fds) conf->close_all_fds = 1; @@ -230,6 +252,9 @@ int main(int argc, char *argv[]) err = -1; } + if (my_args.pidfile) + unlink(my_args.pidfile); + return err; } diff --git a/src/lxc/lxc_unshare.c b/src/lxc/lxc_unshare.c index 498d6e0e6..3a848b23d 100644 --- a/src/lxc/lxc_unshare.c +++ b/src/lxc/lxc_unshare.c @@ -84,51 +84,6 @@ static uid_t lookup_user(const char *optarg) return uid; } -static char *namespaces_list[] = { - "MOUNT", "PID", "UTSNAME", "IPC", - "USER", "NETWORK" -}; -static int cloneflags_list[] = { - CLONE_NEWNS, CLONE_NEWPID, CLONE_NEWUTS, CLONE_NEWIPC, - CLONE_NEWUSER, CLONE_NEWNET -}; - -static int lxc_namespace_2_cloneflag(char *namespace) -{ - int i, len; - len = sizeof(namespaces_list)/sizeof(namespaces_list[0]); - for (i = 0; i < len; i++) - if (!strcmp(namespaces_list[i], namespace)) - return cloneflags_list[i]; - - ERROR("invalid namespace name %s", namespace); - return -1; -} - -static int lxc_fill_namespace_flags(char *flaglist, int *flags) -{ - char *token, *saveptr = NULL; - int aflag; - - if (!flaglist) { - ERROR("need at least one namespace to unshare"); - return -1; - } - - token = strtok_r(flaglist, "|", &saveptr); - while (token) { - - aflag = lxc_namespace_2_cloneflag(token); - if (aflag < 0) - return -1; - - *flags |= aflag; - - token = strtok_r(NULL, "|", &saveptr); - } - return 0; -} - struct start_arg { char ***args; diff --git a/src/lxc/lxc_wait.c b/src/lxc/lxc_wait.c index a58e0c87c..de1163e37 100644 --- a/src/lxc/lxc_wait.c +++ b/src/lxc/lxc_wait.c @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include #include @@ -46,12 +48,14 @@ static int my_parser(struct lxc_arguments* args, int c, char* arg) { switch (c) { case 's': args->states = optarg; break; + case 't': args->timeout = atol(optarg); break; } return 0; } static const struct option my_longopts[] = { {"state", required_argument, 0, 's'}, + {"timeout", required_argument, 0, 't'}, LXC_COMMON_OPTIONS }; @@ -66,37 +70,16 @@ Options :\n\ -n, --name=NAME NAME for name of the container\n\ -s, --state=STATE ORed states to wait for\n\ STOPPED, STARTING, RUNNING, STOPPING,\n\ - ABORTING, FREEZING, FROZEN\n", + ABORTING, FREEZING, FROZEN\n\ + -t, --timeout=TMO Seconds to wait for state changes\n", .options = my_longopts, .parser = my_parser, .checker = my_checker, + .timeout = -1, }; -static int fillwaitedstates(char *strstates, int *states) -{ - char *token, *saveptr = NULL; - int state; - - token = strtok_r(strstates, "|", &saveptr); - while (token) { - - state = lxc_str2state(token); - if (state < 0) - return -1; - - states[state] = 1; - - token = strtok_r(NULL, "|", &saveptr); - } - return 0; -} - int main(int argc, char *argv[]) { - struct lxc_msg msg; - int s[MAX_STATE] = { }, fd; - int state, ret; - if (lxc_arguments_parse(&my_args, argc, argv)) return -1; @@ -104,53 +87,5 @@ int main(int argc, char *argv[]) my_args.progname, my_args.quiet)) return -1; - if (fillwaitedstates(my_args.states, s)) - return -1; - - fd = lxc_monitor_open(); - if (fd < 0) - return -1; - - /* - * if container present, - * then check if already in requested state - */ - ret = -1; - state = lxc_getstate(my_args.name); - if (state < 0) { - goto out_close; - } else if ((state >= 0) && (s[state])) { - ret = 0; - goto out_close; - } - - for (;;) { - if (lxc_monitor_read(fd, &msg) < 0) - goto out_close; - - if (strcmp(my_args.name, msg.name)) - continue; - - switch (msg.type) { - case lxc_msg_state: - if (msg.value < 0 || msg.value >= MAX_STATE) { - ERROR("Receive an invalid state number '%d'", - msg.value); - goto out_close; - } - - if (s[msg.value]) { - ret = 0; - goto out_close; - } - break; - default: - /* just ignore garbage */ - break; - } - } - -out_close: - lxc_monitor_close(fd); - return ret; + return lxc_wait(strdup(my_args.name), my_args.states, my_args.timeout); } diff --git a/src/lxc/lxccontainer.c b/src/lxc/lxccontainer.c new file mode 100644 index 000000000..1345ab55b --- /dev/null +++ b/src/lxc/lxccontainer.c @@ -0,0 +1,1001 @@ +/* liblxcapi + * + * Copyright © 2012 Serge Hallyn . + * Copyright © 2012 Canonical Ltd. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "lxc.h" +#include "state.h" +#include "lxccontainer.h" +#include "conf.h" +#include "config.h" +#include "confile.h" +#include "cgroup.h" +#include "commands.h" +#include "log.h" +#include +#include +#include +#include + +lxc_log_define(lxc_container, lxc); + +/* LOCKING + * c->privlock protects the struct lxc_container from multiple threads. + * c->slock protects the on-disk container data + * NOTHING mutexes two independent programs with their own struct + * lxc_container for the same c->name, between API calls. For instance, + * c->config_read(); c->start(); Between those calls, data on disk + * could change (which shouldn't bother the caller unless for instance + * the rootfs get moved). c->config_read(); update; c->config_write(); + * Two such updaters could race. The callers should therefore check their + * results. Trying to prevent that would necessarily expose us to deadlocks + * due to hung callers. So I prefer to keep the locks only within our own + * functions, not across functions. + * + * If you're going to fork while holding a lxccontainer, increment + * c->numthreads (under privlock) before forking. When deleting, + * decrement numthreads under privlock, then if it hits 0 you can delete. + * Do not ever use a lxccontainer whose numthreads you did not bump. + */ + +static void lxc_container_free(struct lxc_container *c) +{ + if (!c) + return; + + if (c->configfile) { + free(c->configfile); + c->configfile = NULL; + } + if (c->error_string) { + free(c->error_string); + c->error_string = NULL; + } + if (c->slock) { + sem_close(c->slock); + c->slock = NULL; + } + if (c->privlock) { + sem_destroy(c->privlock); + free(c->privlock); + c->privlock = NULL; + } + if (c->name) { + free(c->name); + c->name = NULL; + } + if (c->lxc_conf) { + lxc_conf_free(c->lxc_conf); + c->lxc_conf = NULL; + } + free(c); +} + +int lxc_container_get(struct lxc_container *c) +{ + if (!c) + return 0; + + if (lxclock(c->privlock, 0)) + return 0; + if (c->numthreads < 1) { + // bail without trying to unlock, bc the privlock is now probably + // in freed memory + return 0; + } + c->numthreads++; + lxcunlock(c->privlock); + return 1; +} + +int lxc_container_put(struct lxc_container *c) +{ + if (!c) + return -1; + if (lxclock(c->privlock, 0)) + return -1; + if (--c->numthreads < 1) { + lxcunlock(c->privlock); + lxc_container_free(c); + return 1; + } + lxcunlock(c->privlock); + return 0; +} + +static bool file_exists(char *f) +{ + struct stat statbuf; + + return stat(f, &statbuf) == 0; +} + +static bool lxcapi_is_defined(struct lxc_container *c) +{ + struct stat statbuf; + bool ret = false; + int statret; + + if (!c) + return false; + + if (lxclock(c->privlock, 0)) + return false; + if (!c->configfile) + goto out; + statret = stat(c->configfile, &statbuf); + if (statret != 0) + goto out; + ret = true; + +out: + lxcunlock(c->privlock); + return ret; +} + +static const char *lxcapi_state(struct lxc_container *c) +{ + const char *ret; + lxc_state_t s; + + if (!c) + return NULL; + if (lxclock(c->slock, 0)) + return NULL; + s = lxc_getstate(c->name); + ret = lxc_state2str(s); + lxcunlock(c->slock); + + return ret; +} + +static bool is_stopped_nolock(struct lxc_container *c) +{ + lxc_state_t s; + s = lxc_getstate(c->name); + return (s == STOPPED); +} + +static bool lxcapi_is_running(struct lxc_container *c) +{ + const char *s; + + if (!c) + return false; + s = lxcapi_state(c); + if (!s || strcmp(s, "STOPPED") == 0) + return false; + return true; +} + +static bool lxcapi_freeze(struct lxc_container *c) +{ + int ret; + if (!c) + return false; + + if (lxclock(c->slock, 0)) + return false; + ret = lxc_freeze(c->name); + lxcunlock(c->slock); + if (ret) + return false; + return true; +} + +static bool lxcapi_unfreeze(struct lxc_container *c) +{ + int ret; + if (!c) + return false; + + if (lxclock(c->slock, 0)) + return false; + ret = lxc_unfreeze(c->name); + lxcunlock(c->slock); + if (ret) + return false; + return true; +} + +static pid_t lxcapi_init_pid(struct lxc_container *c) +{ + pid_t ret; + if (!c) + return -1; + + if (lxclock(c->slock, 0)) + return -1; + ret = get_init_pid(c->name); + lxcunlock(c->slock); + return ret; +} + +static bool load_config_locked(struct lxc_container *c, const char *fname) +{ + if (!c->lxc_conf) + c->lxc_conf = lxc_conf_init(); + if (c->lxc_conf && !lxc_config_read(fname, c->lxc_conf)) + return true; + return false; +} + +static bool lxcapi_load_config(struct lxc_container *c, const char *alt_file) +{ + bool ret = false; + const char *fname; + if (!c) + return false; + + fname = c->configfile; + if (alt_file) + fname = alt_file; + if (!fname) + return false; + if (lxclock(c->slock, 0)) + return false; + ret = load_config_locked(c, fname); + lxcunlock(c->slock); + return ret; +} + +static void lxcapi_want_daemonize(struct lxc_container *c) +{ + if (!c) + return; + c->daemonize = 1; +} + +static bool lxcapi_wait(struct lxc_container *c, const char *state, int timeout) +{ + int ret; + + if (!c) + return false; + + ret = lxc_wait(c->name, state, timeout); + return ret == 0; +} + + +static bool wait_on_daemonized_start(struct lxc_container *c) +{ + /* we'll probably want to make this timeout configurable? */ + int timeout = 5, ret, status; + + /* + * our child is going to fork again, then exit. reap the + * child + */ + ret = wait(&status); + if (ret == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) + DEBUG("failed waiting for first dual-fork child"); + return lxcapi_wait(c, "RUNNING", timeout); +} + +/* + * I can't decide if it'd be more convenient for callers if we accept '...', + * or a null-terminated array (i.e. execl vs execv) + */ +static bool lxcapi_start(struct lxc_container *c, int useinit, char * const argv[]) +{ + int ret; + struct lxc_conf *conf; + int daemonize = 0; + char *default_args[] = { + "/sbin/init", + '\0', + }; + + /* container exists */ + if (!c) + return false; + /* container has been setup */ + if (!c->lxc_conf) + return false; + + /* is this app meant to be run through lxcinit, as in lxc-execute? */ + if (useinit && !argv) + return false; + + if (lxclock(c->privlock, 0)) + return false; + conf = c->lxc_conf; + daemonize = c->daemonize; + lxcunlock(c->privlock); + + if (useinit) { + ret = lxc_execute(c->name, argv, 1, conf); + return ret == 0 ? true : false; + } + + if (!argv) + argv = default_args; + + /* + * say, I'm not sure - what locks do we want here? Any? + * Is liblxc's locking enough here to protect the on disk + * container? We don't want to exclude things like lxc_info + * while container is running... + */ + if (daemonize) { + if (!lxc_container_get(c)) + return false; + pid_t pid = fork(); + if (pid < 0) { + lxc_container_put(c); + return false; + } + if (pid != 0) + return wait_on_daemonized_start(c); + /* second fork to be reparented by init */ + pid = fork(); + if (pid < 0) { + SYSERROR("Error doing dual-fork"); + return false; + } + if (pid != 0) + exit(0); + /* like daemon(), chdir to / and redirect 0,1,2 to /dev/null */ + if (chdir("/")) { + SYSERROR("Error chdir()ing to /."); + return false; + } + close(0); + close(1); + close(2); + open("/dev/null", O_RDONLY); + open("/dev/null", O_RDWR); + open("/dev/null", O_RDWR); + setsid(); + } + + if (putenv("container=lxc")) { + fprintf(stderr, "failed to set environment variable"); + if (daemonize) { + lxc_container_put(c); + exit(1); + } else { + return false; + } + } + +reboot: + conf->reboot = 0; + ret = lxc_start(c->name, argv, conf); + + if (conf->reboot) { + INFO("container requested reboot"); + conf->reboot = 0; + if (conf->maincmd_fd) + close(conf->maincmd_fd); + conf->maincmd_fd = 0; + goto reboot; + } + + if (daemonize) { + lxc_container_put(c); + exit (ret == 0 ? true : false); + } else { + return (ret == 0 ? true : false); + } +} + +/* + * note there MUST be an ending NULL + */ +static bool lxcapi_startl(struct lxc_container *c, int useinit, ...) +{ + va_list ap; + char **inargs = NULL, **temp; + int n_inargs = 0; + bool bret = false; + + /* container exists */ + if (!c) + return false; + + /* build array of arguments if any */ + va_start(ap, useinit); + while (1) { + char *arg; + arg = va_arg(ap, char *); + if (!arg) + break; + n_inargs++; + temp = realloc(inargs, n_inargs * sizeof(*inargs)); + if (!temp) + goto out; + inargs = temp; + inargs[n_inargs - 1] = strdup(arg); // not sure if it's safe not to copy + } + va_end(ap); + + /* add trailing NULL */ + if (n_inargs) { + n_inargs++; + temp = realloc(inargs, n_inargs * sizeof(*inargs)); + if (!temp) + goto out; + inargs = temp; + inargs[n_inargs - 1] = NULL; + } + + bret = lxcapi_start(c, useinit, inargs); + +out: + if (inargs) { + int i; + for (i = 0; i < n_inargs; i++) { + if (inargs[i]) + free(inargs[i]); + } + free(inargs); + } + + return bret; +} + +static bool lxcapi_stop(struct lxc_container *c) +{ + int ret; + + if (!c) + return false; + + ret = lxc_stop(c->name); + + return ret == 0; +} + +static bool valid_template(char *t) +{ + struct stat statbuf; + int statret; + + statret = stat(t, &statbuf); + if (statret == 0) + return true; + return false; +} + +/* + * create the standard expected container dir + */ +static bool create_container_dir(struct lxc_container *c) +{ + char *s; + int len, ret; + + len = strlen(LXCPATH) + strlen(c->name) + 2; + s = malloc(len); + if (!s) + return false; + ret = snprintf(s, len, "%s/%s", LXCPATH, c->name); + if (ret < 0 || ret >= len) { + free(s); + return false; + } + ret = mkdir(s, 0755); + if (ret) { + if (errno == EEXIST) + ret = 0; + else + SYSERROR("failed to create container path for %s\n", c->name); + } + free(s); + return ret == 0; +} + +/* + * backing stores not (yet) supported + * for ->create, argv contains the arguments to pass to the template, + * terminated by NULL. If no arguments, you can just pass NULL. + */ +static bool lxcapi_create(struct lxc_container *c, char *t, char *const argv[]) +{ + bool bret = false; + pid_t pid; + int ret, status; + char *tpath = NULL; + int len, nargs = 0; + char **newargv; + + if (!c) + return false; + + len = strlen(LXCTEMPLATEDIR) + strlen(t) + strlen("/lxc-") + 1; + tpath = malloc(len); + if (!tpath) + return false; + ret = snprintf(tpath, len, "%s/lxc-%s", LXCTEMPLATEDIR, t); + if (ret < 0 || ret >= len) + goto out; + if (!valid_template(tpath)) { + ERROR("bad template: %s\n", t); + goto out; + } + + if (!create_container_dir(c)) + goto out; + + if (!c->save_config(c, NULL)) { + ERROR("failed to save starting configuration for %s\n", c->name); + goto out; + } + + /* we're going to fork. but since we'll wait for our child, we + don't need to lxc_container_get */ + + if (lxclock(c->slock, 0)) { + ERROR("failed to grab global container lock for %s\n", c->name); + goto out; + } + + pid = fork(); + if (pid < 0) { + SYSERROR("failed to fork task for container creation template\n"); + goto out_unlock; + } + + if (pid == 0) { // child + char *patharg, *namearg; + int i; + + close(0); + close(1); + close(2); + open("/dev/null", O_RDONLY); + open("/dev/null", O_RDWR); + open("/dev/null", O_RDWR); + + /* + * create our new array, pre-pend the template name and + * base args + */ + if (argv) + for (; argv[nargs]; nargs++) ; + nargs += 3; // template, path and name args + newargv = malloc(nargs * sizeof(*newargv)); + if (!newargv) + exit(1); + newargv[0] = t; + + len = strlen(LXCPATH) + strlen(c->name) + strlen("--path=") + 2; + patharg = malloc(len); + if (!patharg) + exit(1); + ret = snprintf(patharg, len, "--path=%s/%s", LXCPATH, c->name); + if (ret < 0 || ret >= len) + exit(1); + newargv[1] = patharg; + len = strlen("--name=") + strlen(c->name) + 1; + namearg = malloc(len); + if (!namearg) + exit(1); + ret = snprintf(namearg, len, "--name=%s", c->name); + if (ret < 0 || ret >= len) + exit(1); + newargv[2] = namearg; + + /* add passed-in args */ + if (argv) + for (i = 3; i < nargs; i++) + newargv[i] = argv[i-3]; + + /* add trailing NULL */ + nargs++; + newargv = realloc(newargv, nargs * sizeof(*newargv)); + if (!newargv) + exit(1); + newargv[nargs - 1] = NULL; + + /* execute */ + ret = execv(tpath, newargv); + SYSERROR("failed to execute template %s", tpath); + exit(1); + } + +again: + ret = waitpid(pid, &status, 0); + if (ret == -1) { + if (errno == -EINTR) + goto again; + SYSERROR("waitpid failed"); + goto out_unlock; + } + if (ret != pid) + goto again; + if (!WIFEXITED(status)) { // did not exit normally + // we could set an error code and string inside the + // container_struct here if we like + ERROR("container creation template exited abnormally\n"); + goto out_unlock; + } + + if (WEXITSTATUS(status) != 0) { + ERROR("container creation template for %s exited with %d\n", + c->name, WEXITSTATUS(status)); + goto out_unlock; + } + + // now clear out the lxc_conf we have, reload from the created + // container + if (c->lxc_conf) + lxc_conf_free(c->lxc_conf); + c->lxc_conf = NULL; + bret = load_config_locked(c, c->configfile); + +out_unlock: + lxcunlock(c->slock); +out: + if (tpath) + free(tpath); + return bret; +} + +static bool lxcapi_shutdown(struct lxc_container *c, int timeout) +{ + bool retv; + pid_t pid; + + if (!c) + return false; + + if (!timeout) + timeout = -1; + if (!c->is_running(c)) + return true; + pid = c->init_pid(c); + if (pid <= 0) + return true; + kill(pid, SIGPWR); + retv = c->wait(c, "STOPPED", timeout); + if (!retv && timeout > 0) { + c->stop(c); + retv = c->wait(c, "STOPPED", 0); // 0 means don't wait + } + return retv; +} + +static bool lxcapi_createl(struct lxc_container *c, char *t, ...) +{ + bool bret = false; + char **args = NULL, **temp; + va_list ap; + int nargs = 0; + + if (!c) + return false; + + /* + * since we're going to wait for create to finish, I don't think we + * need to get a copy of the arguments. + */ + va_start(ap, t); + while (1) { + char *arg; + arg = va_arg(ap, char *); + if (!arg) + break; + nargs++; + temp = realloc(args, (nargs+1) * sizeof(*args)); + if (!temp) + goto out; + args = temp; + args[nargs - 1] = arg; + } + va_end(ap); + args[nargs] = NULL; + + bret = c->create(c, t, args); + +out: + if (args) + free(args); + return bret; +} + +static bool lxcapi_clear_config_item(struct lxc_container *c, const char *key) +{ + int ret; + + if (!c || !c->lxc_conf) + return false; + if (lxclock(c->privlock, 0)) { + return false; + } + ret = lxc_clear_config_item(c->lxc_conf, key); + lxcunlock(c->privlock); + return ret == 0; +} + +static int lxcapi_get_config_item(struct lxc_container *c, const char *key, char *retv, int inlen) +{ + int ret; + + if (!c || !c->lxc_conf) + return -1; + if (lxclock(c->privlock, 0)) { + return -1; + } + ret = lxc_get_config_item(c->lxc_conf, key, retv, inlen); + lxcunlock(c->privlock); + return ret; +} + +static int lxcapi_get_keys(struct lxc_container *c, const char *key, char *retv, int inlen) +{ + if (!key) + return lxc_listconfigs(retv, inlen); + /* + * Support 'lxc.network.', i.e. 'lxc.network.0' + * This is an intelligent result to show which keys are valid given + * the type of nic it is + */ + if (!c || !c->lxc_conf) + return -1; + if (lxclock(c->privlock, 0)) + return -1; + int ret = -1; + if (strncmp(key, "lxc.network.", 12) == 0) + ret = lxc_list_nicconfigs(c->lxc_conf, key, retv, inlen); + lxcunlock(c->privlock); + return ret; +} + + +/* default config file - should probably come through autoconf */ +#define LXC_DEFAULT_CONFIG "/etc/lxc/lxc.conf" +static bool lxcapi_save_config(struct lxc_container *c, const char *alt_file) +{ + if (!alt_file) + alt_file = c->configfile; + if (!alt_file) + return false; // should we write to stdout if no file is specified? + if (!c->lxc_conf) + if (!c->load_config(c, LXC_DEFAULT_CONFIG)) { + ERROR("Error loading default configuration file %s while saving %s\n", LXC_DEFAULT_CONFIG, c->name); + return false; + } + + FILE *fout = fopen(alt_file, "w"); + if (!fout) + return false; + if (lxclock(c->privlock, 0)) { + fclose(fout); + return false; + } + write_config(fout, c->lxc_conf); + fclose(fout); + lxcunlock(c->privlock); + return true; +} + +static bool lxcapi_destroy(struct lxc_container *c) +{ + pid_t pid; + int ret, status; + + if (!c) + return false; + + pid = fork(); + if (pid < 0) + return false; + if (pid == 0) { // child + ret = execlp("lxc-destroy", "lxc-destroy", "-n", c->name, NULL); + perror("execl"); + exit(1); + } + +again: + ret = waitpid(pid, &status, 0); + if (ret == -1) { + if (errno == -EINTR) + goto again; + perror("waitpid"); + return false; + } + if (ret != pid) + goto again; + if (!WIFEXITED(status)) { // did not exit normally + // we could set an error code and string inside the + // container_struct here if we like + return false; + } + + return WEXITSTATUS(status) == 0; +} + +static bool lxcapi_set_config_item(struct lxc_container *c, const char *key, const char *v) +{ + int ret; + bool b = false; + struct lxc_config_t *config; + + if (!c) + return false; + + if (lxclock(c->privlock, 0)) + return false; + + if (!c->lxc_conf) + c->lxc_conf = lxc_conf_init(); + if (!c->lxc_conf) + goto err; + config = lxc_getconfig(key); + if (!config) + goto err; + ret = config->cb(key, v, c->lxc_conf); + if (!ret) + b = true; + +err: + lxcunlock(c->privlock); + return b; +} + +static char *lxcapi_config_file_name(struct lxc_container *c) +{ + if (!c || !c->configfile) + return NULL; + return strdup(c->configfile); +} + +static bool lxcapi_set_cgroup_item(struct lxc_container *c, const char *subsys, const char *value) +{ + int ret; + bool b = false; + + if (!c) + return false; + + if (lxclock(c->privlock, 0)) + return false; + + if (is_stopped_nolock(c)) + goto err; + + ret = lxc_cgroup_set(c->name, subsys, value); + if (!ret) + b = true; +err: + lxcunlock(c->privlock); + return b; +} + +static int lxcapi_get_cgroup_item(struct lxc_container *c, const char *subsys, char *retv, int inlen) +{ + int ret = -1; + + if (!c || !c->lxc_conf) + return -1; + + if (lxclock(c->privlock, 0)) + return -1; + + if (is_stopped_nolock(c)) + goto out; + + ret = lxc_cgroup_get(c->name, subsys, retv, inlen); + +out: + lxcunlock(c->privlock); + return ret; +} + + +struct lxc_container *lxc_container_new(const char *name) +{ + struct lxc_container *c; + int ret, len; + + c = malloc(sizeof(*c)); + if (!c) { + fprintf(stderr, "failed to malloc lxc_container\n"); + return NULL; + } + memset(c, 0, sizeof(*c)); + + c->name = malloc(strlen(name)+1); + if (!c->name) { + fprintf(stderr, "Error allocating lxc_container name\n"); + goto err; + } + strcpy(c->name, name); + + c->numthreads = 1; + c->slock = lxc_newlock(name); + if (!c->slock) { + fprintf(stderr, "failed to create lock\n"); + goto err; + } + + c->privlock = lxc_newlock(NULL); + if (!c->privlock) { + fprintf(stderr, "failed to alloc privlock\n"); + goto err; + } + + len = strlen(LXCPATH)+strlen(c->name)+strlen("/config")+2; + c->configfile = malloc(len); + if (!c->configfile) { + fprintf(stderr, "Error allocating config file pathname\n"); + goto err; + } + ret = snprintf(c->configfile, len, "%s/%s/config", LXCPATH, c->name); + if (ret < 0 || ret >= len) { + fprintf(stderr, "Error printing out config file name\n"); + goto err; + } + + if (file_exists(c->configfile)) + lxcapi_load_config(c, NULL); + + // assign the member functions + c->is_defined = lxcapi_is_defined; + c->state = lxcapi_state; + c->is_running = lxcapi_is_running; + c->freeze = lxcapi_freeze; + c->unfreeze = lxcapi_unfreeze; + c->init_pid = lxcapi_init_pid; + c->load_config = lxcapi_load_config; + c->want_daemonize = lxcapi_want_daemonize; + c->start = lxcapi_start; + c->startl = lxcapi_startl; + c->stop = lxcapi_stop; + c->config_file_name = lxcapi_config_file_name; + c->wait = lxcapi_wait; + c->set_config_item = lxcapi_set_config_item; + c->destroy = lxcapi_destroy; + c->save_config = lxcapi_save_config; + c->get_keys = lxcapi_get_keys; + c->create = lxcapi_create; + c->createl = lxcapi_createl; + c->shutdown = lxcapi_shutdown; + c->clear_config_item = lxcapi_clear_config_item; + c->get_config_item = lxcapi_get_config_item; + c->get_cgroup_item = lxcapi_get_cgroup_item; + c->set_cgroup_item = lxcapi_set_cgroup_item; + + /* we'll allow the caller to update these later */ + if (lxc_log_init(NULL, NULL, "lxc_container", 0)) { + fprintf(stderr, "failed to open log\n"); + goto err; + } + + /* + * default configuration file is $LXCPATH/$NAME/config + */ + + return c; + +err: + lxc_container_free(c); + return NULL; +} + +int lxc_get_wait_states(const char **states) +{ + int i; + + if (states) + for (i=0; i +#include + +#include + +struct lxc_container { + // private fields + char *name; + char *configfile; + sem_t *slock; + sem_t *privlock; + int numthreads; /* protected by privlock. */ + struct lxc_conf *lxc_conf; // maybe we'll just want the whole lxc_handler? + + // public fields + char *error_string; + int error_num; + int daemonize; + + bool (*is_defined)(struct lxc_container *c); // did /var/lib/lxc/$name/config exist + const char *(*state)(struct lxc_container *c); + bool (*is_running)(struct lxc_container *c); // true so long as defined and not stopped + bool (*freeze)(struct lxc_container *c); + bool (*unfreeze)(struct lxc_container *c); + pid_t (*init_pid)(struct lxc_container *c); + bool (*load_config)(struct lxc_container *c, const char *alt_file); + /* The '...' is the command line. If provided, it must be ended with a NULL */ + bool (*start)(struct lxc_container *c, int useinit, char * const argv[]); + bool (*startl)(struct lxc_container *c, int useinit, ...); + bool (*stop)(struct lxc_container *c); + void (*want_daemonize)(struct lxc_container *c); + // Return current config file name. The result is strdup()d, so free the result. + char *(*config_file_name)(struct lxc_container *c); + // for wait, timeout == -1 means wait forever, timeout == 0 means don't wait. + // otherwise timeout is seconds to wait. + bool (*wait)(struct lxc_container *c, const char *state, int timeout); + bool (*set_config_item)(struct lxc_container *c, const char *key, const char *value); + bool (*destroy)(struct lxc_container *c); + bool (*save_config)(struct lxc_container *c, const char *alt_file); + bool (*create)(struct lxc_container *c, char *t, char *const argv[]); + bool (*createl)(struct lxc_container *c, char *t, ...); + /* send SIGPWR. if timeout is not 0 or -1, do a hard stop after timeout seconds */ + bool (*shutdown)(struct lxc_container *c, int timeout); + /* clear all network or capability items in the in-memory configuration */ + bool (*clear_config_item)(struct lxc_container *c, const char *key); + /* print a config item to a in-memory string allocated by the caller. Return + * the length which was our would be printed. */ + int (*get_config_item)(struct lxc_container *c, const char *key, char *retv, int inlen); + int (*get_keys)(struct lxc_container *c, const char *key, char *retv, int inlen); + /* + * get_cgroup_item returns the number of bytes read, or an error (<0). + * If retv NULL or inlen 0 is passed in, then the length of the cgroup + * file will be returned. * Otherwise it will return the # of bytes read. + * If inlen is less than the number of bytes available, then the returned + * value will be inlen, not the full available size of the file. + */ + int (*get_cgroup_item)(struct lxc_container *c, const char *subsys, char *retv, int inlen); + bool (*set_cgroup_item)(struct lxc_container *c, const char *subsys, const char *value); + +#if 0 + bool (*commit_cgroups)(struct lxc_container *c); + bool (*reread_cgroups)(struct lxc_container *c); + // question with clone: how do we handle non-standard config file in orig? + struct lxc_container (*clone)(struct container *c); + int (*ns_attach)(struct lxc_container *c, int ns_mask); + // we'll need some plumbing to support lxc-console +#endif +}; + +struct lxc_container *lxc_container_new(const char *name); +int lxc_container_get(struct lxc_container *c); +int lxc_container_put(struct lxc_container *c); +int lxc_get_wait_states(const char **states); + +#if 0 +char ** lxc_get_valid_keys(); +char ** lxc_get_valid_values(char *key); +#endif diff --git a/src/lxc/lxclock.c b/src/lxc/lxclock.c new file mode 100644 index 000000000..2d10d7744 --- /dev/null +++ b/src/lxc/lxclock.c @@ -0,0 +1,105 @@ +/* liblxcapi + * + * Copyright © 2012 Serge Hallyn . + * Copyright © 2012 Canonical Ltd. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "lxclock.h" +#include + +#define OFLAG (O_CREAT | O_RDWR) +#define SEMMODE 0660 +#define SEMVALUE 1 +#define SEMVALUE_LOCKED 0 +#define LXCLOCK_PREFIX "/lxcapi." + + +static char *lxclock_name(const char *container) +{ + int ret; + int len = strlen(container) + strlen(LXCLOCK_PREFIX) + 1; + char *dest = malloc(len); + if (!dest) + return NULL; + ret = snprintf(dest, len, "%s%s", LXCLOCK_PREFIX, container); + if (ret < 0 || ret >= len) { + free(dest); + return NULL; + } + return dest; +} + +static void lxcfree_name(char *name) +{ + if (name) + free(name); +} + +static sem_t *lxc_new_unnamed_sem(void) +{ + sem_t *s; + int ret; + + s = malloc(sizeof(*s)); + if (!s) + return NULL; + ret = sem_init(s, 0, 1); + if (ret) + return NULL; + return s; +} + +sem_t *lxc_newlock(const char *name) +{ + char *lname; + sem_t *lock; + + if (!name) + return lxc_new_unnamed_sem(); + + lname = lxclock_name(name); + if (!lname) + return NULL; + lock = sem_open(lname, OFLAG, SEMMODE, SEMVALUE); + lxcfree_name(lname); + if (lock == SEM_FAILED) + return NULL; + return lock; +} + +int lxclock(sem_t *sem, int timeout) +{ + int ret; + + if (!timeout) { + ret = sem_wait(sem); + } else { + struct timespec ts; + if (clock_gettime(CLOCK_REALTIME, &ts) == -1) + return -2; + ts.tv_sec += timeout; + ret = sem_timedwait(sem, &ts); + } + + return ret; +} + +int lxcunlock(sem_t *sem) +{ + if (!sem) + return -2; + return sem_post(sem); +} diff --git a/src/lxc/lxclock.h b/src/lxc/lxclock.h new file mode 100644 index 000000000..1226c22f0 --- /dev/null +++ b/src/lxc/lxclock.h @@ -0,0 +1,61 @@ +/* liblxcapi + * + * Copyright © 2012 Serge Hallyn . + * Copyright © 2012 Canonical Ltd. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include /* For O_* constants */ +#include /* For mode constants */ +#include +#include +#include + +/* + * lxc_newlock: + * if name is not given, create an unnamed semaphore. We use these + * to protect against racing threads. + * Note that an unnamed sem was malloced by us and needs to be freed. + * + * If name is given, it is prepended with '/lxcapi.', and used as the + * name for a system-wide (well, ipcns-wide) semaphore. We use that + * to protect the containers as represented on disk. + * A named sem should not be freed. + * + * XXX TODO + * We should probably introduce a lxclock_close() which detecs the type + * of lock and calls sem_close() or sem_destroy()+free() not as appropriate. + * For now, it is up to the caller to do so. + * + * sem is initialized to value of 1 + * + * return NULL on failure, else a sem_t * which can be passed to + * lxclock() and lxcunlock(). + */ +extern sem_t *lxc_newlock(const char *name); + +/* + * lxclock: take an existing lock. If timeout is 0, wait + * indefinately. Otherwise use given timeout. + * return 0 if we got the lock, -2 on failure to set timeout, or -1 + * otherwise in which case errno will be set by sem_wait()). + */ +extern int lxclock(sem_t *sem, int timeout); + +/* + * lxcunlock: unlock given sem. Return 0 on success. Otherwise returns + * -1 and sem_post will leave errno set. + */ +extern int lxcunlock(sem_t *lock); diff --git a/src/lxc/lxcseccomp.h b/src/lxc/lxcseccomp.h new file mode 100644 index 000000000..4f146dd2b --- /dev/null +++ b/src/lxc/lxcseccomp.h @@ -0,0 +1,49 @@ +/* + * lxc: linux Container library + * + * (C) Copyright Canonical, Inc. 2012 + * + * Authors: + * Serge Hallyn + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _lxc_seccomp_h + +#include "conf.h" + +#ifdef HAVE_SECCOMP +int lxc_seccomp_load(struct lxc_conf *conf); +int lxc_read_seccomp_config(struct lxc_conf *conf); +void lxc_seccomp_free(struct lxc_conf *conf); +#else +static inline int lxc_seccomp_load(struct lxc_conf *conf) { + return 0; +} + +static inline int lxc_read_seccomp_config(struct lxc_conf *conf) { + return 0; +} + +static inline void lxc_seccomp_free(struct lxc_conf *conf) { + if (conf->seccomp) { + free(conf->seccomp); + conf->seccomp = NULL; + } +} +#endif + +#endif diff --git a/src/lxc/mainloop.c b/src/lxc/mainloop.c index 986762cae..76ca58c88 100644 --- a/src/lxc/mainloop.c +++ b/src/lxc/mainloop.c @@ -59,7 +59,7 @@ int lxc_mainloop(struct lxc_epoll_descr *descr) /* If the handler returns a positive value, exit the mainloop */ - if (handler->callback(handler->fd, handler->data, + if (handler->callback(handler->fd, handler->data, descr) > 0) return 0; } @@ -69,7 +69,7 @@ int lxc_mainloop(struct lxc_epoll_descr *descr) } } -int lxc_mainloop_add_handler(struct lxc_epoll_descr *descr, int fd, +int lxc_mainloop_add_handler(struct lxc_epoll_descr *descr, int fd, lxc_mainloop_callback_t callback, void *data) { struct epoll_event ev; diff --git a/src/lxc/mainloop.h b/src/lxc/mainloop.h index 09e50332b..c921a70e5 100644 --- a/src/lxc/mainloop.h +++ b/src/lxc/mainloop.h @@ -28,13 +28,13 @@ struct lxc_epoll_descr { struct lxc_list handlers; }; -typedef int (*lxc_mainloop_callback_t)(int fd, void *data, +typedef int (*lxc_mainloop_callback_t)(int fd, void *data, struct lxc_epoll_descr *descr); extern int lxc_mainloop(struct lxc_epoll_descr *descr); extern int lxc_mainloop_add_handler(struct lxc_epoll_descr *descr, int fd, - lxc_mainloop_callback_t callback, + lxc_mainloop_callback_t callback, void *data); extern int lxc_mainloop_del_handler(struct lxc_epoll_descr *descr, int fd); diff --git a/src/lxc/monitor.c b/src/lxc/monitor.c index e51b59aac..2eb03978f 100644 --- a/src/lxc/monitor.c +++ b/src/lxc/monitor.c @@ -98,11 +98,28 @@ int lxc_monitor_open(void) return fd; } -int lxc_monitor_read(int fd, struct lxc_msg *msg) +/* timeout of 0 means return immediately; -1 means wait forever */ +int lxc_monitor_read_timeout(int fd, struct lxc_msg *msg, int timeout) { struct sockaddr_un from; socklen_t len = sizeof(from); int ret; + fd_set rfds; + struct timeval tv; + + if (timeout != -1) { + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + + tv.tv_sec = timeout; + tv.tv_usec = 0; + + ret = select(fd+1, &rfds, NULL, NULL, &tv); + if (ret == -1) + return -1; + else if (!ret) + return -2; // timed out + } ret = recvfrom(fd, msg, sizeof(*msg), 0, (struct sockaddr *)&from, &len); @@ -114,6 +131,11 @@ int lxc_monitor_read(int fd, struct lxc_msg *msg) return ret; } +int lxc_monitor_read(int fd, struct lxc_msg *msg) +{ + return lxc_monitor_read_timeout(fd, msg, -1); +} + int lxc_monitor_close(int fd) { return close(fd); diff --git a/src/lxc/namespace.c b/src/lxc/namespace.c index 3e6fc3aad..3fa027b53 100644 --- a/src/lxc/namespace.c +++ b/src/lxc/namespace.c @@ -69,3 +69,48 @@ pid_t lxc_clone(int (*fn)(void *), void *arg, int flags) return ret; } + +static char *namespaces_list[] = { + "MOUNT", "PID", "UTSNAME", "IPC", + "USER", "NETWORK" +}; +static int cloneflags_list[] = { + CLONE_NEWNS, CLONE_NEWPID, CLONE_NEWUTS, CLONE_NEWIPC, + CLONE_NEWUSER, CLONE_NEWNET +}; + +int lxc_namespace_2_cloneflag(char *namespace) +{ + int i, len; + len = sizeof(namespaces_list)/sizeof(namespaces_list[0]); + for (i = 0; i < len; i++) + if (!strcmp(namespaces_list[i], namespace)) + return cloneflags_list[i]; + + ERROR("invalid namespace name %s", namespace); + return -1; +} + +int lxc_fill_namespace_flags(char *flaglist, int *flags) +{ + char *token, *saveptr = NULL; + int aflag; + + if (!flaglist) { + ERROR("need at least one namespace to unshare"); + return -1; + } + + token = strtok_r(flaglist, "|", &saveptr); + while (token) { + + aflag = lxc_namespace_2_cloneflag(token); + if (aflag < 0) + return -1; + + *flags |= aflag; + + token = strtok_r(NULL, "|", &saveptr); + } + return 0; +} diff --git a/src/lxc/namespace.h b/src/lxc/namespace.h index f839f4510..715dffa0e 100644 --- a/src/lxc/namespace.h +++ b/src/lxc/namespace.h @@ -54,4 +54,7 @@ int clone(int (*fn)(void *), void *child_stack, extern pid_t lxc_clone(int (*fn)(void *), void *arg, int flags); +extern int lxc_namespace_2_cloneflag(char *namespace); +extern int lxc_fill_namespace_flags(char *flaglist, int *flags); + #endif diff --git a/src/lxc/network.c b/src/lxc/network.c index c8aecfb82..f97e685c0 100644 --- a/src/lxc/network.c +++ b/src/lxc/network.c @@ -47,6 +47,7 @@ #include "nl.h" #include "network.h" +#include "conf.h" #ifndef IFLA_LINKMODE # define IFLA_LINKMODE 17 @@ -1004,3 +1005,18 @@ int lxc_bridge_attach(const char *bridge, const char *ifname) return err; } + +static char* lxc_network_types[LXC_NET_MAXCONFTYPE + 1] = { + [LXC_NET_VETH] = "veth", + [LXC_NET_MACVLAN] = "macvlan", + [LXC_NET_VLAN] = "vlan", + [LXC_NET_PHYS] = "phys", + [LXC_NET_EMPTY] = "empty", +}; + +const char *lxc_net_type_to_str(int type) +{ + if (type < 0 || type > LXC_NET_MAXCONFTYPE) + return NULL; + return lxc_network_types[type]; +} diff --git a/src/lxc/network.h b/src/lxc/network.h index 0fd5e5d36..3f45f7fcd 100644 --- a/src/lxc/network.h +++ b/src/lxc/network.h @@ -100,7 +100,7 @@ extern int lxc_ipv6_gateway_add(int ifindex, struct in6_addr *gw); */ extern int lxc_bridge_attach(const char *bridge, const char *ifname); -/* +/* * Create default gateway */ extern int lxc_route_create_default(const char *addr, const char *ifname, @@ -122,4 +122,5 @@ extern int lxc_neigh_proxy_on(const char *name, int family); */ extern int lxc_neigh_proxy_off(const char *name, int family); +extern const char *lxc_net_type_to_str(int type); #endif diff --git a/src/lxc/nl.c b/src/lxc/nl.c index 6294442ec..06ff40105 100644 --- a/src/lxc/nl.c +++ b/src/lxc/nl.c @@ -48,7 +48,7 @@ extern void *nlmsg_data(struct nlmsg *nlmsg) return data; } -static int nla_put(struct nlmsg *nlmsg, int attr, +static int nla_put(struct nlmsg *nlmsg, int attr, const void *data, size_t len) { struct rtattr *rta; @@ -63,7 +63,7 @@ static int nla_put(struct nlmsg *nlmsg, int attr, return 0; } -extern int nla_put_buffer(struct nlmsg *nlmsg, int attr, +extern int nla_put_buffer(struct nlmsg *nlmsg, int attr, const void *data, size_t size) { return nla_put(nlmsg, attr, data, size); @@ -193,7 +193,7 @@ extern int netlink_send(struct nl_handler *handler, struct nlmsg *nlmsg) #ifndef NLMSG_ERROR #define NLMSG_ERROR 0x2 #endif -extern int netlink_transaction(struct nl_handler *handler, +extern int netlink_transaction(struct nl_handler *handler, struct nlmsg *request, struct nlmsg *answer) { int ret; @@ -226,11 +226,11 @@ extern int netlink_open(struct nl_handler *handler, int protocol) if (handler->fd < 0) return -errno; - if (setsockopt(handler->fd, SOL_SOCKET, SO_SNDBUF, + if (setsockopt(handler->fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) < 0) return -errno; - if (setsockopt(handler->fd, SOL_SOCKET, SO_RCVBUF, + if (setsockopt(handler->fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf,sizeof(rcvbuf)) < 0) return -errno; @@ -238,12 +238,12 @@ extern int netlink_open(struct nl_handler *handler, int protocol) handler->local.nl_family = AF_NETLINK; handler->local.nl_groups = 0; - if (bind(handler->fd, (struct sockaddr*)&handler->local, + if (bind(handler->fd, (struct sockaddr*)&handler->local, sizeof(handler->local)) < 0) return -errno; socklen = sizeof(handler->local); - if (getsockname(handler->fd, (struct sockaddr*)&handler->local, + if (getsockname(handler->fd, (struct sockaddr*)&handler->local, &socklen) < 0) return -errno; diff --git a/src/lxc/nl.h b/src/lxc/nl.h index 864780a5a..34aeb365c 100644 --- a/src/lxc/nl.h +++ b/src/lxc/nl.h @@ -39,7 +39,7 @@ * struct nl_handler : the handler for netlink sockets, this structure * is used all along the netlink socket life cycle to specify the * netlink socket to be used. - * + * * @fd: the file descriptor of the netlink socket * @seq: the sequence number of the netlink messages * @local: the bind address @@ -77,7 +77,7 @@ struct nlmsg { int netlink_open(struct nl_handler *handler, int protocol); /* - * netlink_close : close a netlink socket, after this call, + * netlink_close : close a netlink socket, after this call, * the handler is no longer valid * * @handler: a handler to the netlink socket @@ -87,8 +87,8 @@ int netlink_open(struct nl_handler *handler, int protocol); int netlink_close(struct nl_handler *handler); /* - * netlink_rcv : receive a netlink message from the kernel. - * It is up to the caller to manage the allocation of the + * netlink_rcv : receive a netlink message from the kernel. + * It is up to the caller to manage the allocation of the * netlink message * * @handler: a handler to the netlink socket @@ -110,8 +110,8 @@ int netlink_rcv(struct nl_handler *handler, struct nlmsg *nlmsg); int netlink_send(struct nl_handler *handler, struct nlmsg *nlmsg); /* - * netlink_transaction: send a request to the kernel and read the response. - * This is useful for transactional protocol. It is up to the caller + * netlink_transaction: send a request to the kernel and read the response. + * This is useful for transactional protocol. It is up to the caller * to manage the allocation of the netlink message. * * @handler: a handler to a opened netlink socket @@ -120,11 +120,11 @@ int netlink_send(struct nl_handler *handler, struct nlmsg *nlmsg); * * Returns 0 on success, < 0 otherwise */ -int netlink_transaction(struct nl_handler *handler, +int netlink_transaction(struct nl_handler *handler, struct nlmsg *request, struct nlmsg *anwser); /* - * nla_put_string: copy a null terminated string to a netlink message + * nla_put_string: copy a null terminated string to a netlink message * attribute * * @nlmsg: the netlink message to be filled @@ -146,7 +146,7 @@ int nla_put_string(struct nlmsg *nlmsg, int attr, const char *string); * * Returns 0 on success, < 0 otherwise */ -int nla_put_buffer(struct nlmsg *nlmsg, int attr, +int nla_put_buffer(struct nlmsg *nlmsg, int attr, const void *data, size_t size); /* @@ -172,7 +172,7 @@ int nla_put_u32(struct nlmsg *nlmsg, int attr, int value); int nla_put_u16(struct nlmsg *nlmsg, int attr, ushort value); /* - * nla_put_attr: add an attribute name to a netlink + * nla_put_attr: add an attribute name to a netlink * * @nlmsg: the netlink message to be filled * @attr: the attribute name of the integer @@ -185,7 +185,7 @@ int nla_put_attr(struct nlmsg *nlmsg, int attr); * nla_begin_nested: begin the nesting attribute * * @nlmsg: the netlink message to be filled - * @attr: the netsted attribute name + * @attr: the netsted attribute name * * Returns current nested pointer to be reused * to nla_end_nested. @@ -198,17 +198,17 @@ struct rtattr *nla_begin_nested(struct nlmsg *nlmsg, int attr); * @nlmsg: the netlink message * @nested: the nested pointer * - * Returns the current + * Returns the current */ void nla_end_nested(struct nlmsg *nlmsg, struct rtattr *attr); /* - * nlmsg_allocate : allocate a netlink message. The netlink format message + * nlmsg_allocate : allocate a netlink message. The netlink format message * is a header, a padding, a payload and a padding again. - * When a netlink message is allocated, the size specify the + * When a netlink message is allocated, the size specify the * payload we want. So the real size of the allocated message * is sizeof(header) + sizeof(padding) + payloadsize + sizeof(padding), - * in other words, the function will allocate more than specified. When + * in other words, the function will allocate more than specified. When * the buffer is allocated, the content is zeroed. * The function will also fill the field nlmsg_len with computed size. * If the allocation must be for the specified size, just use malloc. @@ -228,7 +228,7 @@ void nlmsg_free(struct nlmsg *nlmsg); /* * nlmsg_data : returns a pointer to the data contained in the netlink message - * + * * @nlmsg : the netlink message to get the data * * Returns a pointer to the netlink data or NULL if there is no data diff --git a/src/lxc/rtnl.c b/src/lxc/rtnl.c index ec1eff2ac..861b211b3 100644 --- a/src/lxc/rtnl.c +++ b/src/lxc/rtnl.c @@ -53,7 +53,7 @@ extern int rtnetlink_send(struct rtnl_handler *handler, struct rtnlmsg *rtnlmsg) return netlink_send(&handler->nlh, (struct nlmsg *)&rtnlmsg->nlmsghdr); } -extern int rtnetlink_transaction(struct rtnl_handler *handler, +extern int rtnetlink_transaction(struct rtnl_handler *handler, struct rtnlmsg *request, struct rtnlmsg *answer) { return netlink_transaction(&handler->nlh, (struct nlmsg *)&request->nlmsghdr, diff --git a/src/lxc/rtnl.h b/src/lxc/rtnl.h index 63aca0ad8..6ef04966b 100644 --- a/src/lxc/rtnl.h +++ b/src/lxc/rtnl.h @@ -30,7 +30,7 @@ #define RTNLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + RTNL_HDRLEN)) /* - * struct genl_handler : the structure which store the netlink handler + * struct genl_handler : the structure which store the netlink handler * and the family number * * @nlh: the netlink socket handler @@ -105,6 +105,6 @@ void rtnlmsg_free(struct rtnlmsg *rtnlmsg); * * Returns 0 on success, < 0 otherwise */ -int rtnetlink_transaction(struct rtnl_handler *handler, +int rtnetlink_transaction(struct rtnl_handler *handler, struct rtnlmsg *request, struct rtnlmsg *answer); #endif diff --git a/src/lxc/seccomp.c b/src/lxc/seccomp.c new file mode 100644 index 000000000..2f0b44708 --- /dev/null +++ b/src/lxc/seccomp.c @@ -0,0 +1,155 @@ +/* + * lxc: linux Container library + * + * (C) Copyright Canonical, Inc. 2012 + * + * Authors: + * Serge Hallyn + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include "config.h" +#include "lxcseccomp.h" + +#include "log.h" + +lxc_log_define(lxc_seccomp, lxc); + +/* + * The first line of the config file has a policy language version + * the second line has some directives + * then comes policy subject to the directives + * right now version must be '1' + * the directives must include 'whitelist' (only type of policy currently + * supported) and can include 'debug' (though debug is not yet supported). + */ +static int parse_config(FILE *f, struct lxc_conf *conf) +{ + char line[1024]; + int ret, version; + + ret = fscanf(f, "%d\n", &version); + if (ret != 1 || version != 1) { + ERROR("invalid version"); + return -1; + } + if (!fgets(line, 1024, f)) { + ERROR("invalid config file"); + return -1; + } + if (!strstr(line, "whitelist")) { + ERROR("only whitelist policy is supported"); + return -1; + } + if (strstr(line, "debug")) { + ERROR("debug not yet implemented"); + return -1; + } + /* now read in the whitelist entries one per line */ + while (fgets(line, 1024, f)) { + int nr; + ret = sscanf(line, "%d", &nr); + if (ret != 1) + return -1; + ret = seccomp_rule_add( +#if HAVE_SCMP_FILTER_CTX + conf->seccomp_ctx, +#endif + SCMP_ACT_ALLOW, nr, 0); + if (ret < 0) { + ERROR("failed loading allow rule for %d\n", nr); + return ret; + } + } + return 0; +} + +int lxc_read_seccomp_config(struct lxc_conf *conf) +{ + FILE *f; + int ret; + + if (!conf->seccomp) + return 0; + +#if HAVE_SCMP_FILTER_CTX + /* XXX for debug, pass in SCMP_ACT_TRAP */ + conf->seccomp_ctx = seccomp_init(SCMP_ACT_ERRNO(31)); + ret = !conf->seccomp_ctx; +#else + ret = seccomp_init(SCMP_ACT_ERRNO(31)) < 0; +#endif + if (ret) { + ERROR("failed initializing seccomp"); + return -1; + } + + /* turn of no-new-privs. We don't want it in lxc, and it breaks + * with apparmor */ + if (seccomp_attr_set( +#if HAVE_SCMP_FILTER_CTX + conf->seccomp_ctx, +#endif + SCMP_FLTATR_CTL_NNP, 0)) { + ERROR("failed to turn off n-new-privs\n"); + return -1; + } + + f = fopen(conf->seccomp, "r"); + if (!f) { + SYSERROR("failed to open seccomp policy file %s\n", conf->seccomp); + return -1; + } + ret = parse_config(f, conf); + fclose(f); + return ret; +} + +int lxc_seccomp_load(struct lxc_conf *conf) +{ + int ret; + if (!conf->seccomp) + return 0; + ret = seccomp_load( +#if HAVE_SCMP_FILTER_CTX + conf->seccomp_ctx +#endif + ); + if (ret < 0) { + ERROR("Error loading the seccomp policy"); + return -1; + } + return 0; +} + +void lxc_seccomp_free(struct lxc_conf *conf) { + if (conf->seccomp) { + free(conf->seccomp); + conf->seccomp = NULL; + } +#if HAVE_SCMP_FILTER_CTX + if (conf->seccomp_ctx) { + seccomp_release(conf->seccomp_ctx); + conf->seccomp_ctx = NULL; + } +#endif +} diff --git a/src/lxc/start.c b/src/lxc/start.c index 19815fcab..7320d74a8 100644 --- a/src/lxc/start.c +++ b/src/lxc/start.c @@ -127,6 +127,7 @@ int signalfd(int fd, const sigset_t *mask, int flags) #include "sync.h" #include "namespace.h" #include "apparmor.h" +#include "lxcseccomp.h" lxc_log_define(lxc_start, lxc); @@ -278,6 +279,29 @@ int lxc_pid_callback(int fd, struct lxc_request *request, return 0; } +int lxc_clone_flags_callback(int fd, struct lxc_request *request, + struct lxc_handler *handler) +{ + struct lxc_answer answer; + int ret; + + answer.pid = 0; + answer.ret = handler->clone_flags; + + ret = send(fd, &answer, sizeof(answer), 0); + if (ret < 0) { + WARN("failed to send answer to the peer"); + return -1; + } + + if (ret != sizeof(answer)) { + ERROR("partial answer sent"); + return -1; + } + + return 0; +} + int lxc_set_state(const char *name, struct lxc_handler *handler, lxc_state_t state) { handler->state = state; @@ -353,6 +377,11 @@ struct lxc_handler *lxc_init(const char *name, struct lxc_conf *conf) goto out_free; } + if (lxc_read_seccomp_config(conf) != 0) { + ERROR("failed loading seccomp policy"); + goto out_free_name; + } + /* Begin the set the state to STARTING*/ if (lxc_set_state(name, handler, STARTING)) { ERROR("failed to set state '%s'", lxc_state2str(STARTING)); @@ -530,6 +559,9 @@ static int do_start(void *data) if (apparmor_load(handler) < 0) goto out_warn_father; + if (lxc_seccomp_load(handler->conf) != 0) + goto out_warn_father; + if (run_lxc_hooks(handler->name, "start", handler->conf)) { ERROR("failed to run start hooks for container '%s'.", handler->name); goto out_warn_father; @@ -547,9 +579,39 @@ out_warn_father: return -1; } +int save_phys_nics(struct lxc_conf *conf) +{ + struct lxc_list *iterator; + + lxc_list_for_each(iterator, &conf->network) { + struct lxc_netdev *netdev = iterator->elem; + + if (netdev->type != LXC_NET_PHYS) + continue; + conf->saved_nics = realloc(conf->saved_nics, + (conf->num_savednics+1)*sizeof(struct saved_nic)); + if (!conf->saved_nics) { + SYSERROR("failed to allocate memory"); + return -1; + } + conf->saved_nics[conf->num_savednics].ifindex = netdev->ifindex; + conf->saved_nics[conf->num_savednics].orig_name = strdup(netdev->link); + if (!conf->saved_nics[conf->num_savednics].orig_name) { + SYSERROR("failed to allocate memory"); + return -1; + } + INFO("stored saved_nic #%d idx %d name %s\n", conf->num_savednics, + conf->saved_nics[conf->num_savednics].ifindex, + conf->saved_nics[conf->num_savednics].orig_name); + conf->num_savednics++; + } + + return 0; +} + + int lxc_spawn(struct lxc_handler *handler) { - int clone_flags; int failed_before_rename = 0; const char *name = handler->name; int pinfd; @@ -557,10 +619,10 @@ int lxc_spawn(struct lxc_handler *handler) if (lxc_sync_init(handler)) return -1; - clone_flags = CLONE_NEWUTS|CLONE_NEWPID|CLONE_NEWIPC|CLONE_NEWNS; + handler->clone_flags = CLONE_NEWUTS|CLONE_NEWPID|CLONE_NEWIPC|CLONE_NEWNS; if (!lxc_list_empty(&handler->conf->network)) { - clone_flags |= CLONE_NEWNET; + handler->clone_flags |= CLONE_NEWNET; /* Find gateway addresses from the link device, which is * no longer accessible inside the container. Do this @@ -582,6 +644,11 @@ int lxc_spawn(struct lxc_handler *handler) } } + if (save_phys_nics(handler->conf)) { + ERROR("failed to save physical nic info"); + goto out_abort; + } + /* * if the rootfs is not a blockdev, prevent the container from * marking it readonly. @@ -594,7 +661,7 @@ int lxc_spawn(struct lxc_handler *handler) } /* Create a process in a new set of namespaces */ - handler->pid = lxc_clone(do_start, handler, clone_flags); + handler->pid = lxc_clone(do_start, handler, handler->clone_flags); if (handler->pid < 0) { SYSERROR("failed to fork into a new namespace"); goto out_delete_net; @@ -612,7 +679,7 @@ int lxc_spawn(struct lxc_handler *handler) goto out_delete_net; /* Create the network configuration */ - if (clone_flags & CLONE_NEWNET) { + if (handler->clone_flags & CLONE_NEWNET) { if (lxc_assign_network(&handler->conf->network, handler->pid)) { ERROR("failed to create the configured network"); goto out_delete_net; @@ -642,8 +709,8 @@ int lxc_spawn(struct lxc_handler *handler) return 0; out_delete_net: - if (clone_flags & CLONE_NEWNET) - lxc_delete_network(&handler->conf->network); + if (handler->clone_flags & CLONE_NEWNET) + lxc_delete_network(handler); out_abort: lxc_abort(name, handler); lxc_sync_fini(handler); @@ -675,7 +742,7 @@ int __lxc_start(const char *name, struct lxc_conf *conf, err = lxc_spawn(handler); if (err) { ERROR("failed to spawn '%s'", name); - goto out_fini; + goto out_fini_nonet; } err = lxc_poll(name, handler); @@ -708,8 +775,13 @@ int __lxc_start(const char *name, struct lxc_conf *conf, } } + lxc_rename_phys_nics_on_shutdown(handler->conf); + err = lxc_error_set_and_log(handler->pid, status); out_fini: + lxc_delete_network(handler); + +out_fini_nonet: lxc_cgroup_destroy(name); lxc_fini(name, handler); return err; diff --git a/src/lxc/start.h b/src/lxc/start.h index 0e12abaed..4b2e2b54e 100644 --- a/src/lxc/start.h +++ b/src/lxc/start.h @@ -39,6 +39,7 @@ struct lxc_handler { pid_t pid; char *name; lxc_state_t state; + int clone_flags; int sigfd; sigset_t oldmask; struct lxc_conf *conf; diff --git a/src/lxc/state.c b/src/lxc/state.c index 99836562f..7ce4b533c 100644 --- a/src/lxc/state.c +++ b/src/lxc/state.c @@ -21,6 +21,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include +#include #include #include #include @@ -31,9 +32,11 @@ #include #include +#include #include #include #include +#include #include "commands.h" #include "config.h" @@ -162,3 +165,115 @@ out: return ret; } +static int fillwaitedstates(const char *strstates, int *states) +{ + char *token, *saveptr = NULL; + char *strstates_dup = strdup(strstates); + int state; + + if (!strstates_dup) + return -1; + + token = strtok_r(strstates_dup, "|", &saveptr); + while (token) { + + state = lxc_str2state(token); + if (state < 0) { + free(strstates_dup); + return -1; + } + + states[state] = 1; + + token = strtok_r(NULL, "|", &saveptr); + } + free(strstates_dup); + return 0; +} + +extern int lxc_wait(const char *lxcname, const char *states, int timeout) +{ + struct lxc_msg msg; + int state, ret; + int s[MAX_STATE] = { }, fd; + + if (fillwaitedstates(states, s)) + return -1; + + fd = lxc_monitor_open(); + if (fd < 0) + return -1; + + /* + * if container present, + * then check if already in requested state + */ + ret = -1; + state = lxc_getstate(lxcname); + if (state < 0) { + goto out_close; + } else if ((state >= 0) && (s[state])) { + ret = 0; + goto out_close; + } + + for (;;) { + int elapsed_time, curtime = 0; + struct timeval tv; + int stop = 0; + int retval; + + if (timeout != -1) { + retval = gettimeofday(&tv, NULL); + if (retval) + goto out_close; + curtime = tv.tv_sec; + } + if (lxc_monitor_read_timeout(fd, &msg, timeout) < 0) + goto out_close; + + if (timeout != -1) { + retval = gettimeofday(&tv, NULL); + if (retval) + goto out_close; + elapsed_time = tv.tv_sec - curtime; + if (timeout - elapsed_time <= 0) + stop = 1; + timeout -= elapsed_time; + } + + if (strcmp(lxcname, msg.name)) { + if (stop) { + ret = -2; + goto out_close; + } + continue; + } + + switch (msg.type) { + case lxc_msg_state: + if (msg.value < 0 || msg.value >= MAX_STATE) { + ERROR("Receive an invalid state number '%d'", + msg.value); + goto out_close; + } + + if (s[msg.value]) { + ret = 0; + goto out_close; + } + break; + default: + if (stop) { + ret = -2; + goto out_close; + } + /* just ignore garbage */ + break; + } + } + +out_close: + lxc_monitor_close(fd); + return ret; +} diff --git a/src/lxc/state.h b/src/lxc/state.h index f64169873..df8070ac2 100644 --- a/src/lxc/state.h +++ b/src/lxc/state.h @@ -33,5 +33,6 @@ extern lxc_state_t lxc_getstate(const char *name); extern lxc_state_t lxc_str2state(const char *state); extern const char *lxc_state2str(lxc_state_t state); +extern int lxc_wait(const char *lxcname, const char *states, int timeout); #endif diff --git a/src/python-lxc/Makefile.am b/src/python-lxc/Makefile.am new file mode 100644 index 000000000..9d775c3ec --- /dev/null +++ b/src/python-lxc/Makefile.am @@ -0,0 +1,22 @@ +if ENABLE_PYTHON + +if HAVE_DEBIAN + DISTSETUPOPTS=--install-layout=deb +else + DISTSETUPOPTS= +endif + +all: + CFLAGS="$(CFLAGS) -I ../../src -L../../src/lxc/" $(PYTHON) setup.py build + +install: + if [ "$(DESTDIR)" = "" ]; then \ + $(PYTHON) setup.py install --prefix=$(prefix) --no-compile $(DISTSETUPOPTS); \ + else \ + $(PYTHON) setup.py install --root=$(DESTDIR) --prefix=$(prefix) --no-compile $(DISTSETUPOPTS); \ + fi + +clean: + rm -rf build + +endif diff --git a/src/python-lxc/examples/api_test.py.in b/src/python-lxc/examples/api_test.py.in new file mode 100644 index 000000000..7711291ee --- /dev/null +++ b/src/python-lxc/examples/api_test.py.in @@ -0,0 +1,158 @@ +#!/usr/bin/python3 +# +# api_test.py: Test/demo of the python3-lxc API +# +# (C) Copyright Canonical Ltd. 2012 +# +# Authors: +# Stéphane Graber +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import warnings +warnings.filterwarnings("ignore", "The python-lxc API isn't yet stable") + +import lxc +import uuid +import sys + +# Some constants +LXC_PATH_LIB = "@LXCPATH@" +LXC_TEMPLATE = "ubuntu" + +# Let's pick a random name, avoiding clashes +CONTAINER_NAME = str(uuid.uuid1()) +CLONE_NAME = str(uuid.uuid1()) + +## Instantiate the container instance +print("Getting instance for '%s'" % CONTAINER_NAME) +container = lxc.Container(CONTAINER_NAME) + +# A few basic checks of the current state +assert(container.config_file_name == "%s/%s/config" % + (LXC_PATH_LIB, CONTAINER_NAME)) +assert(not container.defined) +assert(container.init_pid == -1) +assert(container.name == CONTAINER_NAME) +assert(not container.running) +assert(container.state == "STOPPED") + +## Create a rootfs +print("Creating rootfs using '%s'" % LXC_TEMPLATE) +container.create(LXC_TEMPLATE) + +assert(container.defined) +assert(container.name == CONTAINER_NAME + == container.get_config_item("lxc.utsname")) +assert(container.name in lxc.list_containers()) + +## Test the config +print("Testing the configuration") +capdrop = container.get_config_item("lxc.cap.drop") +container.clear_config_item("lxc.cap.drop") +container.set_config_item("lxc.cap.drop", capdrop[:-1]) +container.append_config_item("lxc.cap.drop", capdrop[-1]) +container.save_config() + +# A few basic checks of the current state +assert(isinstance(capdrop, list)) +assert(capdrop == container.get_config_item("lxc.cap.drop")) + +## Test the networking +print("Testing the networking") + +# A few basic checks of the current state +assert("name" in container.get_keys("lxc.network.0")) +assert(len(container.network) == 1) +assert(container.network[0].hwaddr.startswith("00:16:3e")) + +## Starting the container +print("Starting the container") +container.start() +container.wait("RUNNING", 3) + +# A few basic checks of the current state +assert(container.init_pid > 1) +assert(container.running) +assert(container.state == "RUNNING") + +## Checking IP address +print("Getting the IP addresses") +ips = container.get_ips(timeout=10) +container.attach("NETWORK|UTSNAME", "/sbin/ifconfig", "eth0") + +# A few basic checks of the current state +assert(len(ips) > 0) + +## Testing cgroups a bit +print("Testing cgroup API") +max_mem = container.get_cgroup_item("memory.max_usage_in_bytes") +current_limit = container.get_cgroup_item("memory.limit_in_bytes") +assert(container.set_cgroup_item("memory.limit_in_bytes", max_mem)) +assert(container.get_cgroup_item("memory.limit_in_bytes") != current_limit) + +## Freezing the container +print("Freezing the container") +container.freeze() +container.wait("FROZEN", 3) + +# A few basic checks of the current state +assert(container.init_pid > 1) +assert(container.running) +assert(container.state == "FROZEN") + +## Unfreezing the container +print("Unfreezing the container") +container.unfreeze() +container.wait("RUNNING", 3) + +# A few basic checks of the current state +assert(container.init_pid > 1) +assert(container.running) +assert(container.state == "RUNNING") + +if len(sys.argv) > 1 and sys.argv[1] == "--with-console": + ## Attaching to tty1 + print("Attaching to tty1") + container.console(tty=1) + +## Shutting down the container +print("Shutting down the container") +container.shutdown(3) + +if container.running: + print("Stopping the container") + container.stop() + container.wait("STOPPED", 3) + +# A few basic checks of the current state +assert(container.init_pid == -1) +assert(not container.running) +assert(container.state == "STOPPED") + +## Cloning the container +print("Cloning the container") +clone = lxc.Container(CLONE_NAME) +clone.clone(container) +clone.start() +clone.stop() +clone.destroy() + +## Destroy the container +print("Destroying the container") +container.destroy() + +assert(not container.defined) diff --git a/src/python-lxc/lxc.c b/src/python-lxc/lxc.c new file mode 100644 index 000000000..83e265926 --- /dev/null +++ b/src/python-lxc/lxc.c @@ -0,0 +1,620 @@ +/* + * python-lxc: Python bindings for LXC + * + * (C) Copyright Canonical Ltd. 2012 + * + * Authors: + * Stéphane Graber + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include "structmember.h" +#include +#include +#include + +typedef struct { + PyObject_HEAD + struct lxc_container *container; +} Container; + +char** +convert_tuple_to_char_pointer_array(PyObject *argv) { + int argc = PyTuple_Size(argv); + int i; + + char **result = (char**) malloc(sizeof(char*)*argc + 1); + + for (i = 0; i < argc; i++) { + PyObject *pyobj = PyTuple_GetItem(argv, i); + + char *str = NULL; + PyObject *pystr; + if (!PyUnicode_Check(pyobj)) { + PyErr_SetString(PyExc_ValueError, "Expected a string"); + return NULL; + } + + pystr = PyUnicode_AsUTF8String(pyobj); + str = PyBytes_AsString(pystr); + memcpy((char *) &result[i], (char *) &str, sizeof(str)); + } + + result[argc] = NULL; + + return result; +} + +static void +Container_dealloc(Container* self) +{ + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static PyObject * +Container_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + Container *self; + + self = (Container *)type->tp_alloc(type, 0); + + return (PyObject *)self; +} + +static int +Container_init(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"name", NULL}; + char *name = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|", kwlist, + &name)) + return -1; + + self->container = lxc_container_new(name); + if (!self->container) { + fprintf(stderr, "%d: error creating lxc_container %s\n", __LINE__, name); + return -1; + } + + return 0; +} + +// Container properties +static PyObject * +Container_config_file_name(Container *self, PyObject *args, PyObject *kwds) +{ + return PyUnicode_FromString(self->container->config_file_name(self->container)); +} + +static PyObject * +Container_defined(Container *self, PyObject *args, PyObject *kwds) +{ + if (self->container->is_defined(self->container)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_init_pid(Container *self, PyObject *args, PyObject *kwds) +{ + return Py_BuildValue("i", self->container->init_pid(self->container)); +} + +static PyObject * +Container_name(Container *self, PyObject *args, PyObject *kwds) +{ + return PyUnicode_FromString(self->container->name); +} + +static PyObject * +Container_running(Container *self, PyObject *args, PyObject *kwds) +{ + if (self->container->is_running(self->container)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_state(Container *self, PyObject *args, PyObject *kwds) +{ + return PyUnicode_FromString(self->container->state(self->container)); +} + +// Container Functions +static PyObject * +Container_clear_config_item(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"key", NULL}; + char *key = NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|", kwlist, + &key)) + Py_RETURN_FALSE; + + if (self->container->clear_config_item(self->container, key)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_create(Container *self, PyObject *args, PyObject *kwds) +{ + char* template_name = NULL; + char** create_args = {NULL}; + PyObject *vargs = NULL; + static char *kwlist[] = {"template", "args", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|O", kwlist, + &template_name, &vargs)) + Py_RETURN_FALSE; + + if (vargs && PyTuple_Check(vargs)) { + create_args = convert_tuple_to_char_pointer_array(vargs); + if (!create_args) { + return NULL; + } + } + + if (self->container->create(self->container, template_name, create_args)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_destroy(Container *self, PyObject *args, PyObject *kwds) +{ + if (self->container->destroy(self->container)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_freeze(Container *self, PyObject *args, PyObject *kwds) +{ + if (self->container->freeze(self->container)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_get_cgroup_item(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"key", NULL}; + char* key = NULL; + int len = 0; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|", kwlist, + &key)) + Py_RETURN_FALSE; + + len = self->container->get_cgroup_item(self->container, key, NULL, 0); + + if (len <= 0) { + Py_RETURN_FALSE; + } + + char* value = (char*) malloc(sizeof(char)*len + 1); + if (self->container->get_cgroup_item(self->container, key, value, len + 1) != len) { + Py_RETURN_FALSE; + } + + return PyUnicode_FromString(value); +} + +static PyObject * +Container_get_config_item(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"key", NULL}; + char* key = NULL; + int len = 0; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|", kwlist, + &key)) + Py_RETURN_FALSE; + + len = self->container->get_config_item(self->container, key, NULL, 0); + + if (len <= 0) { + Py_RETURN_FALSE; + } + + char* value = (char*) malloc(sizeof(char)*len + 1); + if (self->container->get_config_item(self->container, key, value, len + 1) != len) { + Py_RETURN_FALSE; + } + + return PyUnicode_FromString(value); +} + +static PyObject * +Container_get_keys(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"key", NULL}; + char* key = NULL; + int len = 0; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "|s", kwlist, + &key)) + Py_RETURN_FALSE; + + len = self->container->get_keys(self->container, key, NULL, 0); + + if (len <= 0) { + Py_RETURN_FALSE; + } + + char* value = (char*) malloc(sizeof(char)*len + 1); + if (self->container->get_keys(self->container, key, value, len + 1) != len) { + Py_RETURN_FALSE; + } + + return PyUnicode_FromString(value); +} + +static PyObject * +Container_load_config(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"path", NULL}; + char* path = NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "|s", kwlist, + &path)) + Py_RETURN_FALSE; + + if (self->container->load_config(self->container, path)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_save_config(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"path", NULL}; + char* path = NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "|s", kwlist, + &path)) + Py_RETURN_FALSE; + + if (self->container->save_config(self->container, path)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_set_cgroup_item(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"key", "value", NULL}; + char *key = NULL; + char *value = NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "ss|", kwlist, + &key, &value)) + Py_RETURN_FALSE; + + if (self->container->set_cgroup_item(self->container, key, value)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_set_config_item(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"key", "value", NULL}; + char *key = NULL; + char *value = NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "ss|", kwlist, + &key, &value)) + Py_RETURN_FALSE; + + if (self->container->set_config_item(self->container, key, value)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_shutdown(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"timeout", NULL}; + int timeout = -1; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "|i", kwlist, + &timeout)) + Py_RETURN_FALSE; + + if (self->container->shutdown(self->container, timeout)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_start(Container *self, PyObject *args, PyObject *kwds) +{ + char** init_args = {NULL}; + PyObject *useinit = NULL, *vargs = NULL; + int init_useinit = 0; + static char *kwlist[] = {"useinit", "cmd", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OO", kwlist, + &useinit, &vargs)) + Py_RETURN_FALSE; + + if (useinit && useinit == Py_True) { + init_useinit = 1; + } + + if (vargs && PyTuple_Check(vargs)) { + init_args = convert_tuple_to_char_pointer_array(vargs); + if (!init_args) { + return NULL; + } + } + + self->container->want_daemonize(self->container); + + if (self->container->start(self->container, init_useinit, init_args)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_stop(Container *self, PyObject *args, PyObject *kwds) +{ + if (self->container->stop(self->container)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_unfreeze(Container *self, PyObject *args, PyObject *kwds) +{ + if (self->container->unfreeze(self->container)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_wait(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"state", "timeout", NULL}; + char *state = NULL; + int timeout = -1; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|i", kwlist, + &state, &timeout)) + Py_RETURN_FALSE; + + if (self->container->wait(self->container, state, timeout)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyGetSetDef Container_getseters[] = { + {"config_file_name", + (getter)Container_config_file_name, 0, + "Path to the container configuration", + NULL}, + {"defined", + (getter)Container_defined, 0, + "Boolean indicating whether the container configuration exists", + NULL}, + {"init_pid", + (getter)Container_init_pid, 0, + "PID of the container's init process in the host's PID namespace", + NULL}, + {"name", + (getter)Container_name, 0, + "Container name", + NULL}, + {"running", + (getter)Container_running, 0, + "Boolean indicating whether the container is running or not", + NULL}, + {"state", + (getter)Container_state, 0, + "Container state", + NULL}, +}; + +static PyMethodDef Container_methods[] = { + {"clear_config_item", (PyCFunction)Container_clear_config_item, METH_VARARGS | METH_KEYWORDS, + "clear_config_item(key) -> boolean\n" + "\n" + "Clear the current value of a config key." + }, + {"create", (PyCFunction)Container_create, METH_VARARGS | METH_KEYWORDS, + "create(template, args = (,)) -> boolean\n" + "\n" + "Create a new rootfs for the container, using the given template " + "and passing some optional arguments to it." + }, + {"destroy", (PyCFunction)Container_destroy, METH_NOARGS, + "destroy() -> boolean\n" + "\n" + "Destroys the container." + }, + {"freeze", (PyCFunction)Container_freeze, METH_NOARGS, + "freeze() -> boolean\n" + "\n" + "Freezes the container and returns its return code." + }, + {"get_cgroup_item", (PyCFunction)Container_get_cgroup_item, METH_VARARGS | METH_KEYWORDS, + "get_cgroup_item(key) -> string\n" + "\n" + "Get the current value of a cgroup entry." + }, + {"get_config_item", (PyCFunction)Container_get_config_item, METH_VARARGS | METH_KEYWORDS, + "get_config_item(key) -> string\n" + "\n" + "Get the current value of a config key." + }, + {"get_keys", (PyCFunction)Container_get_keys, METH_VARARGS | METH_KEYWORDS, + "get_keys(key) -> string\n" + "\n" + "Get a list of valid sub-keys for a key." + }, + {"load_config", (PyCFunction)Container_load_config, METH_VARARGS | METH_KEYWORDS, + "load_config(path = DEFAULT) -> boolean\n" + "\n" + "Read the container configuration from its default " + "location or from an alternative location if provided." + }, + {"save_config", (PyCFunction)Container_save_config, METH_VARARGS | METH_KEYWORDS, + "save_config(path = DEFAULT) -> boolean\n" + "\n" + "Save the container configuration to its default " + "location or to an alternative location if provided." + }, + {"set_cgroup_item", (PyCFunction)Container_set_cgroup_item, METH_VARARGS | METH_KEYWORDS, + "set_cgroup_item(key, value) -> boolean\n" + "\n" + "Set a cgroup entry to the provided value." + }, + {"set_config_item", (PyCFunction)Container_set_config_item, METH_VARARGS | METH_KEYWORDS, + "set_config_item(key, value) -> boolean\n" + "\n" + "Set a config key to the provided value." + }, + {"shutdown", (PyCFunction)Container_shutdown, METH_VARARGS | METH_KEYWORDS, + "shutdown(timeout = -1) -> boolean\n" + "\n" + "Sends SIGPWR to the container and wait for it to shutdown " + "unless timeout is set to a positive value, in which case " + "the container will be killed when the timeout is reached." + }, + {"start", (PyCFunction)Container_start, METH_VARARGS | METH_KEYWORDS, + "start(useinit = False, cmd = (,)) -> boolean\n" + "\n" + "Start the container, optionally using lxc-init and " + "an alternate init command, then returns its return code." + }, + {"stop", (PyCFunction)Container_stop, METH_NOARGS, + "stop() -> boolean\n" + "\n" + "Stop the container and returns its return code." + }, + {"unfreeze", (PyCFunction)Container_unfreeze, METH_NOARGS, + "unfreeze() -> boolean\n" + "\n" + "Unfreezes the container and returns its return code." + }, + {"wait", (PyCFunction)Container_wait, METH_VARARGS | METH_KEYWORDS, + "wait(state, timeout = -1) -> boolean\n" + "\n" + "Wait for the container to reach a given state or timeout." + }, + {NULL} /* Sentinel */ +}; + +static PyTypeObject _lxc_ContainerType = { +PyVarObject_HEAD_INIT(NULL, 0) + "lxc.Container", /* tp_name */ + sizeof(Container), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)Container_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + "Container objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + Container_methods, /* tp_methods */ + 0, /* tp_members */ + Container_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)Container_init, /* tp_init */ + 0, /* tp_alloc */ + Container_new, /* tp_new */ +}; + +static PyModuleDef _lxcmodule = { + PyModuleDef_HEAD_INIT, + "_lxc", + "Binding for liblxc in python", + -1, + NULL, NULL, NULL, NULL, NULL +}; + +PyMODINIT_FUNC +PyInit__lxc(void) +{ + PyObject* m; + + if (PyType_Ready(&_lxc_ContainerType) < 0) + return NULL; + + m = PyModule_Create(&_lxcmodule); + if (m == NULL) + return NULL; + + Py_INCREF(&_lxc_ContainerType); + PyModule_AddObject(m, "Container", (PyObject *)&_lxc_ContainerType); + return m; +} diff --git a/src/python-lxc/lxc/__init__.py.in b/src/python-lxc/lxc/__init__.py.in new file mode 100644 index 000000000..07c956bd6 --- /dev/null +++ b/src/python-lxc/lxc/__init__.py.in @@ -0,0 +1,468 @@ +# +# python-lxc: Python bindings for LXC +# +# (C) Copyright Canonical Ltd. 2012 +# +# Authors: +# Stéphane Graber +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import _lxc +import glob +import os +import subprocess +import stat +import tempfile +import time +import warnings + +warnings.warn("The python-lxc API isn't yet stable " + "and may change at any point in the future.", Warning, 2) + + +class ContainerNetwork(): + props = {} + + def __init__(self, container, index): + self.container = container + self.index = index + + for key in self.container.get_keys("lxc.network.%s" % self.index): + if "." in key: + self.props[key.replace(".", "_")] = key + else: + self.props[key] = key + + if not self.props: + return False + + def __delattr__(self, key): + if key in ["container", "index", "props"]: + return object.__delattr__(self, key) + + if key not in self.props: + raise AttributeError("'%s' network has no attribute '%s'" % ( + self.__get_network_item("type"), key)) + + return self.__clear_network_item(self.props[key]) + + def __dir__(self): + return sorted(self.props.keys()) + + def __getattr__(self, key): + if key in ["container", "index", "props"]: + return object.__getattribute__(self, key) + + if key not in self.props: + raise AttributeError("'%s' network has no attribute '%s'" % ( + self.__get_network_item("type"), key)) + + return self.__get_network_item(self.props[key]) + + def __hasattr__(self, key): + if key in ["container", "index", "props"]: + return object.__hasattr__(self, key) + + if key not in self.props: + raise AttributeError("'%s' network has no attribute '%s'" % ( + self.__get_network_item("type"), key)) + + return True + + def __repr__(self): + return "'%s' network at index '%s'" % ( + self.__get_network_item("type"), self.index) + + def __setattr__(self, key, value): + if key in ["container", "index", "props"]: + return object.__setattr__(self, key, value) + + if key not in self.props: + raise AttributeError("'%s' network has no attribute '%s'" % ( + self.__get_network_item("type"), key)) + + return self.__set_network_item(self.props[key], value) + + def __clear_network_item(self, key): + return self.container.clear_config_item("lxc.network.%s.%s" % ( + self.index, key)) + + def __get_network_item(self, key): + return self.container.get_config_item("lxc.network.%s.%s" % ( + self.index, key)) + + def __set_network_item(self, key, value): + return self.container.set_config_item("lxc.network.%s.%s" % ( + self.index, key), value) + + +class ContainerNetworkList(): + def __init__(self, container): + self.container = container + + def __getitem__(self, index): + if index >= len(self): + raise IndexError("list index out of range") + + return ContainerNetwork(self.container, index) + + def __len__(self): + values = self.container.get_config_item("lxc.network") + + if values: + return len(values) + else: + return 0 + + def add(self, network_type): + index = len(self) + + return self.container.set_config_item("lxc.network.%s.type" % index, + network_type) + + def remove(self, index): + count = len(self) + if index >= count: + raise IndexError("list index out of range") + + return self.container.clear_config_item("lxc.network.%s" % index) + + +class Container(_lxc.Container): + def __init__(self, name): + """ + Creates a new Container instance. + """ + + if os.geteuid() != 0: + raise Exception("Running as non-root.") + + _lxc.Container.__init__(self, name) + self.network = ContainerNetworkList(self) + + def add_device_node(self, path, destpath=None): + """ + Add block/char device to running container. + """ + + if not self.running: + return False + + if not destpath: + destpath = path + + if not os.path.exists(path): + return False + + # Lookup the source + path_stat = os.stat(path) + mode = stat.S_IMODE(path_stat.st_mode) + + # Allow the target + if stat.S_ISBLK(path_stat.st_mode): + self.set_cgroup_item("devices.allow", + "b %s:%s rwm" % + (int(path_stat.st_rdev / 256), + int(path_stat.st_rdev % 256))) + elif stat.S_ISCHR(path_stat.st_mode): + self.set_cgroup_item("devices.allow", + "c %s:%s rwm" % + (int(path_stat.st_rdev / 256), + int(path_stat.st_rdev % 256))) + + # Create the target + rootfs = "/proc/%s/root/" % self.init_pid + container_path = "%s/%s" % (rootfs, destpath) + + if os.path.exists(container_path): + os.remove(container_path) + + os.mknod(container_path, path_stat.st_mode, path_stat.st_rdev) + os.chmod(container_path, mode) + os.chown(container_path, 0, 0) + + return True + + def add_device_net(self, name, destname=None): + """ + Add network device to running container. + """ + + if not self.running: + return False + + if not destname: + destname = name + + if not os.path.exists("/sys/class/net/%s/" % name): + return False + + return subprocess.call(['ip', 'link', 'set', + 'dev', name, + 'netns', str(self.init_pid), + 'name', destname]) == 0 + + def append_config_item(self, key, value): + """ + Append 'value' to 'key', assuming 'key' is a list. + If 'key' isn't a list, 'value' will be set as the value of 'key'. + """ + + return _lxc.Container.set_config_item(self, key, value) + + def attach(self, namespace="ALL", *cmd): + """ + Attach to a running container. + """ + + if not self.running: + return False + + attach = ["lxc-attach", "-n", self.name] + if namespace != "ALL": + attach += ["-s", namespace] + + if cmd: + attach += ["--"] + list(cmd) + + if subprocess.call( + attach, + universal_newlines=True) != 0: + return False + return True + + def create(self, template, args={}): + """ + Create a new rootfs for the container. + + "template" must be a valid template name. + + "args" (optional) is a dictionary of parameters and values to pass + to the template. + """ + + template_args = [] + for item in args.items(): + template_args.append("--%s" % item[0]) + template_args.append("%s" % item[1]) + + return _lxc.Container.create(self, template, tuple(template_args)) + + def clone(self, container): + """ + Clone an existing container into a new one. + """ + + if self.defined: + return False + + if isinstance(container, Container): + source = container + else: + source = Container(container) + + if not source.defined: + return False + + if subprocess.call(["lxc-clone", "-o", source.name, "-n", self.name], + universal_newlines=True) != 0: + return False + + self.load_config() + return True + + def console(self, tty="1"): + """ + Access the console of a container. + """ + + if not self.running: + return False + + if subprocess.call(["lxc-console", "-n", self.name, "-t", "%s" % tty], + universal_newlines=True) != 0: + return False + return True + + def get_cgroup_item(self, key): + """ + Returns the value for a given cgroup entry. + A list is returned when multiple values are set. + """ + value = _lxc.Container.get_cgroup_item(self, key) + + if value is False: + return False + else: + return value.rstrip("\n") + + def get_config_item(self, key): + """ + Returns the value for a given config key. + A list is returned when multiple values are set. + """ + value = _lxc.Container.get_config_item(self, key) + + if value is False: + return False + elif value.endswith("\n"): + return value.rstrip("\n").split("\n") + else: + return value + + def get_ips(self, timeout=60, interface=None, protocol=None): + """ + Returns the list of IP addresses for the container. + """ + + if not self.defined or not self.running: + return False + + try: + os.makedirs("/run/netns") + except: + pass + + path = tempfile.mktemp(dir="/run/netns") + + os.symlink("/proc/%s/ns/net" % self.init_pid, path) + + ips = [] + + count = 0 + while count < timeout: + if count != 0: + time.sleep(1) + + base_cmd = ["ip", "netns", "exec", path.split("/")[-1], "ip"] + + # Get IPv6 + if protocol in ("ipv6", None): + ip6_cmd = base_cmd + ["-6", "addr", "show", "scope", "global"] + if interface: + ip = subprocess.Popen(ip6_cmd + ["dev", interface], + stdout=subprocess.PIPE, + universal_newlines=True) + else: + ip = subprocess.Popen(ip6_cmd, stdout=subprocess.PIPE, + universal_newlines=True) + + ip.wait() + for line in ip.stdout.read().split("\n"): + fields = line.split() + if len(fields) > 2 and fields[0] == "inet6": + ips.append(fields[1].split('/')[0]) + + # Get IPv4 + if protocol in ("ipv4", None): + ip4_cmd = base_cmd + ["-4", "addr", "show", "scope", "global"] + if interface: + ip = subprocess.Popen(ip4_cmd + ["dev", interface], + stdout=subprocess.PIPE, + universal_newlines=True) + else: + ip = subprocess.Popen(ip4_cmd, stdout=subprocess.PIPE, + universal_newlines=True) + + ip.wait() + for line in ip.stdout.read().split("\n"): + fields = line.split() + if len(fields) > 2 and fields[0] == "inet": + ips.append(fields[1].split('/')[0]) + + if ips: + break + + count += 1 + + os.remove(path) + return ips + + def get_keys(self, key=None): + """ + Returns a list of valid sub-keys. + """ + if key: + value = _lxc.Container.get_keys(self, key) + else: + value = _lxc.Container.get_keys(self) + + if value is False: + return False + elif value.endswith("\n"): + return value.rstrip("\n").split("\n") + else: + return value + + def set_config_item(self, key, value): + """ + Set a config key to a provided value. + The value can be a list for the keys supporting multiple values. + """ + old_value = self.get_config_item(key) + + # Check if it's a list + def set_key(key, value): + self.clear_config_item(key) + if isinstance(value, list): + for entry in value: + if not _lxc.Container.set_config_item(self, key, entry): + return False + else: + _lxc.Container.set_config_item(self, key, value) + + set_key(key, value) + new_value = self.get_config_item(key) + + if (isinstance(value, str) and isinstance(new_value, str) and + value == new_value): + return True + elif (isinstance(value, list) and isinstance(new_value, list) and + set(value) == set(new_value)): + return True + elif (isinstance(value, str) and isinstance(new_value, list) and + set([value]) == set(new_value)): + return True + elif old_value: + set_key(key, old_value) + return False + else: + self.clear_config_item(key) + return False + + def wait(self, state, timeout=-1): + """ + Wait for the container to reach a given state or timeout. + """ + + if isinstance(state, str): + state = state.upper() + + return _lxc.Container.wait(self, state, timeout) + + +def list_containers(as_object=False): + """ + List the containers on the system. + """ + containers = [] + for entry in glob.glob("@LXCPATH@/*/config"): + if as_object: + containers.append(Container(entry.split("/")[-2])) + else: + containers.append(entry.split("/")[-2]) + return containers diff --git a/src/python-lxc/setup.py b/src/python-lxc/setup.py new file mode 100644 index 000000000..8c22961be --- /dev/null +++ b/src/python-lxc/setup.py @@ -0,0 +1,10 @@ +from distutils.core import setup, Extension + +module = Extension('_lxc', sources=['lxc.c'], libraries=['lxc']) + +setup(name='_lxc', + version='0.1', + description='LXC', + packages=['lxc'], + package_dir={'lxc': 'lxc'}, + ext_modules=[module]) diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am new file mode 100644 index 000000000..fa61f7058 --- /dev/null +++ b/src/tests/Makefile.am @@ -0,0 +1,23 @@ +if ENABLE_TESTS + +LDADD = ../lxc/liblxc.so -lpthread +lxc_test_containertests_SOURCES = containertests.c +lxc_test_locktests_SOURCES = locktests.c +lxc_test_startone_SOURCES = startone.c +lxc_test_destroytest_SOURCES = destroytest.c +lxc_test_saveconfig_SOURCES = saveconfig.c +lxc_test_createtest_SOURCES = createtest.c +lxc_test_shutdowntest_SOURCES = shutdowntest.c +lxc_test_get_item_SOURCES = get_item.c +lxc_test_getkeys_SOURCES = getkeys.c + +AM_CFLAGS=-I$(top_srcdir)/src \ + -DLXCROOTFSMOUNT=\"$(LXCROOTFSMOUNT)\" \ + -DLXCPATH=\"$(LXCPATH)\" \ + -DLXCINITDIR=\"$(LXCINITDIR)\" + +bin_PROGRAMS = lxc-test-containertests lxc-test-locktests lxc-test-startone \ + lxc-test-destroytest lxc-test-saveconfig lxc-test-createtest \ + lxc-test-shutdowntest lxc-test-get_item lxc-test-getkeys + +endif diff --git a/src/tests/containertests.c b/src/tests/containertests.c new file mode 100644 index 000000000..3ac0fc574 --- /dev/null +++ b/src/tests/containertests.c @@ -0,0 +1,262 @@ +/* liblxcapi + * + * Copyright © 2012 Serge Hallyn . + * Copyright © 2012 Canonical Ltd. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "../lxc/lxccontainer.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MYNAME "lxctest1" + +static int destroy_busybox(void) +{ + int status, ret; + pid_t pid = fork(); + + if (pid < 0) { + perror("fork"); + return -1; + } + if (pid == 0) { + ret = execlp("lxc-destroy", "lxc-destroy", "-f", "-n", MYNAME, NULL); + // Should not return + perror("execl"); + exit(1); + } +again: + ret = waitpid(pid, &status, 0); + if (ret == -1) { + if (errno == -EINTR) + goto again; + perror("waitpid"); + return -1; + } + if (ret != pid) + goto again; + if (!WIFEXITED(status)) { // did not exit normally + fprintf(stderr, "%d: lxc-create exited abnormally\n", __LINE__); + return -1; + } + return WEXITSTATUS(status); +} + +static int create_busybox(void) +{ + int status, ret; + pid_t pid = fork(); + + if (pid < 0) { + perror("fork"); + return -1; + } + if (pid == 0) { + ret = execlp("lxc-create", "lxc-create", "-t", "busybox", "-f", "/etc/lxc/lxc.conf", "-n", MYNAME, NULL); + // Should not return + perror("execl"); + exit(1); + } +again: + ret = waitpid(pid, &status, 0); + if (ret == -1) { + if (errno == -EINTR) + goto again; + perror("waitpid"); + return -1; + } + if (ret != pid) + goto again; + if (!WIFEXITED(status)) { // did not exit normally + fprintf(stderr, "%d: lxc-create exited abnormally\n", __LINE__); + return -1; + } + return WEXITSTATUS(status); +} + +int main(int argc, char *argv[]) +{ + struct lxc_container *c; + int ret = 0; + const char *s; + bool b; + char *str; + + ret = 1; + /* test refcounting */ + c = lxc_container_new(MYNAME); + if (!c) { + fprintf(stderr, "%d: error creating lxc_container %s\n", __LINE__, MYNAME); + goto out; + } + if (!lxc_container_get(c)) { + fprintf(stderr, "%d: error getting refcount\n", __LINE__); + goto out; + } + /* peek in, inappropriately, make sure refcount is a we'd like */ + if (c->numthreads != 2) { + fprintf(stderr, "%d: refcount is %d, not %d\n", __LINE__, c->numthreads, 2); + goto out; + } + if (strcmp(c->name, MYNAME) != 0) { + fprintf(stderr, "%d: container has wrong name (%s not %s)\n", __LINE__, c->name, MYNAME); + goto out; + } + str = c->config_file_name(c); +#define CONFIGFNAM LXCPATH "/" MYNAME "/config" + if (!str || strcmp(str, CONFIGFNAM)) { + fprintf(stderr, "%d: got wrong config file name (%s, not %s)\n", __LINE__, str, CONFIGFNAM); + goto out; + } + free(str); + free(c->configfile); + c->configfile = NULL; + str = c->config_file_name(c); + if (str) { + fprintf(stderr, "%d: config file name was not NULL as it should have been\n", __LINE__); + goto out; + } + if (lxc_container_put(c) != 0) { + fprintf(stderr, "%d: c was freed on non-final put\n", __LINE__); + goto out; + } + if (c->numthreads != 1) { + fprintf(stderr, "%d: refcount is %d, not %d\n", __LINE__, c->numthreads, 1); + goto out; + } + if (lxc_container_put(c) != 1) { + fprintf(stderr, "%d: c was not freed on final put\n", __LINE__); + goto out; + } + + /* test a real container */ + c = lxc_container_new(MYNAME); + if (!c) { + fprintf(stderr, "%d: error creating lxc_container %s\n", __LINE__, MYNAME); + ret = 1; + goto out; + } + + if (c->lxc_conf != NULL) { + fprintf(stderr, "%d: lxc_conf is not NULL as it should be\n", __LINE__); + ret = 1; + goto out; + } + b = c->is_defined(c); + if (b) { + fprintf(stderr, "%d: %s thought it was defined\n", __LINE__, MYNAME); + goto out; + } + + s = c->state(c); + if (s && strcmp(s, "STOPPED") != 0) { + // liblxc says a container is STOPPED if it doesn't exist. That's because + // the container may be an application container - it's not wrong, just + // sometimes unintuitive. + fprintf(stderr, "%d: %s thinks it is in state %s\n", __LINE__, c->name, s); + goto out; + } + + // create a container + // the liblxc api does not support creation - it probably will eventually, + // but not yet. + // So we just call out to lxc-create. We'll create a busybox container. + ret = create_busybox(); + if (ret) { + fprintf(stderr, "%d: failed to create a busybox container\n", __LINE__); + goto out; + } + + b = c->is_defined(c); + if (!b) { + fprintf(stderr, "%d: %s thought it was not defined\n", __LINE__, MYNAME); + goto out; + } + + s = c->state(c); + if (!s || strcmp(s, "STOPPED")) { + fprintf(stderr, "%d: %s is in state %s, not in STOPPED.\n", __LINE__, c->name, s ? s : "undefined"); + goto out; + } + + b = c->load_config(c, NULL); + if (!b) { + fprintf(stderr, "%d: %s failed to read its config\n", __LINE__, c->name); + goto out; + } + + // test wait states + int numstates = lxc_get_wait_states(NULL); + if (numstates != MAX_STATE) { + fprintf(stderr, "%d: lxc_get_wait_states gave %d not %d\n", __LINE__, numstates, MAX_STATE); + goto out; + } + const char **sstr = malloc(numstates * sizeof(const char *)); + numstates = lxc_get_wait_states(sstr); + int i; + for (i=0; iwant_daemonize(c); + if (!c->startl(c, 0, NULL, NULL)) { + fprintf(stderr, "%d: %s failed to start daemonized\n", __LINE__, c->name); + goto out; + } + + if (!c->wait(c, "RUNNING", -1)) { + fprintf(stderr, "%d: failed waiting for state RUNNING\n", __LINE__); + goto out; + } + + sleep(3); + s = c->state(c); + if (!s || strcmp(s, "RUNNING")) { + fprintf(stderr, "%d: %s is in state %s, not in RUNNING.\n", __LINE__, c->name, s ? s : "undefined"); + goto out; + } + + printf("hit return to finish"); + ret = scanf("%c", &mychar); + if (ret < 0) + goto out; + + + fprintf(stderr, "all lxc_container tests passed for %s\n", c->name); + ret = 0; + +out: + if (c) { + c->stop(c); + destroy_busybox(); + } + lxc_container_put(c); + exit(ret); +} diff --git a/src/tests/createtest.c b/src/tests/createtest.c new file mode 100644 index 000000000..48ce922e6 --- /dev/null +++ b/src/tests/createtest.c @@ -0,0 +1,92 @@ +/* liblxcapi + * + * Copyright © 2012 Serge Hallyn . + * Copyright © 2012 Canonical Ltd. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "../lxc/lxccontainer.h" + +#include +#include +#include +#include +#include +#include +#include + +#define MYNAME "lxctest1" + +int main(int argc, char *argv[]) +{ + struct lxc_container *c; + int ret = 1; + + if ((c = lxc_container_new(MYNAME)) == NULL) { + fprintf(stderr, "%d: error opening lxc_container %s\n", __LINE__, MYNAME); + ret = 1; + goto out; + } + + if (c->is_defined(c)) { + fprintf(stderr, "%d: %s thought it was defined\n", __LINE__, MYNAME); + goto out; + } + + if (!c->set_config_item(c, "lxc.network.type", "veth")) { + fprintf(stderr, "%d: failed to set network type\n", __LINE__); + goto out; + } + c->set_config_item(c, "lxc.network.link", "lxcbr0"); + c->set_config_item(c, "lxc.network.flags", "up"); + if (!c->createl(c, "ubuntu", "-r", "lucid", NULL)) { + fprintf(stderr, "%d: failed to create a lucid container\n", __LINE__); + goto out; + } + + if (!c->is_defined(c)) { + fprintf(stderr, "%d: %s thought it was not defined\n", __LINE__, MYNAME); + goto out; + } + + c->load_config(c, NULL); + c->want_daemonize(c); + if (!c->startl(c, 0, NULL)) { + fprintf(stderr, "%d: failed to start %s\n", __LINE__, MYNAME); + goto out; + } + fprintf(stderr, "%d: %s started, you have 60 seconds to test a console\n", __LINE__, MYNAME); + sleep(60); // wait a minute to let user connect to console + + if (!c->stop(c)) { + fprintf(stderr, "%d: failed to stop %s\n", __LINE__, MYNAME); + goto out; + } + + if (!c->destroy(c)) { + fprintf(stderr, "%d: error deleting %s\n", __LINE__, MYNAME); + goto out; + } + + if (c->is_defined(c)) { + fprintf(stderr, "%d: %s thought it was defined\n", __LINE__, MYNAME); + goto out; + } + + fprintf(stderr, "all lxc_container tests passed for %s\n", c->name); + ret = 0; +out: + lxc_container_put(c); + exit(ret); +} diff --git a/src/tests/destroytest.c b/src/tests/destroytest.c new file mode 100644 index 000000000..3fe35dfef --- /dev/null +++ b/src/tests/destroytest.c @@ -0,0 +1,104 @@ +/* liblxcapi + * + * Copyright © 2012 Serge Hallyn . + * Copyright © 2012 Canonical Ltd. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "../lxc/lxccontainer.h" + +#include +#include +#include +#include +#include +#include +#include + +#define MYNAME "lxctest1" + +static int create_ubuntu(void) +{ + int status, ret; + pid_t pid = fork(); + + if (pid < 0) { + perror("fork"); + return -1; + } + if (pid == 0) { + ret = execlp("lxc-create", "lxc-create", "-t", "ubuntu", "-f", "/etc/lxc/lxc.conf", "-n", MYNAME, NULL); + // Should not return + perror("execl"); + exit(1); + } +again: + ret = waitpid(pid, &status, 0); + if (ret == -1) { + if (errno == -EINTR) + goto again; + perror("waitpid"); + return -1; + } + if (ret != pid) + goto again; + if (!WIFEXITED(status)) { // did not exit normally + fprintf(stderr, "%d: lxc-create exited abnormally\n", __LINE__); + return -1; + } + return WEXITSTATUS(status); +} + +int main(int argc, char *argv[]) +{ + struct lxc_container *c; + int ret = 1; + + if ((c = lxc_container_new(MYNAME)) == NULL) { + fprintf(stderr, "%d: error opening lxc_container %s\n", __LINE__, MYNAME); + ret = 1; + goto out; + } + + if (c->is_defined(c)) { + fprintf(stderr, "%d: %s thought it was defined\n", __LINE__, MYNAME); + goto out; + } + + if (create_ubuntu()) { + fprintf(stderr, "%d: failed to create a ubuntu container\n", __LINE__); + goto out; + } + + if (!c->is_defined(c)) { + fprintf(stderr, "%d: %s thought it was not defined\n", __LINE__, MYNAME); + goto out; + } + + if (!c->destroy(c)) { + fprintf(stderr, "%d: error deleting %s\n", __LINE__, MYNAME); + goto out; + } + + if (c->is_defined(c)) { + fprintf(stderr, "%d: %s thought it was defined\n", __LINE__, MYNAME); + goto out; + } + + fprintf(stderr, "all lxc_container tests passed for %s\n", c->name); + ret = 0; +out: + lxc_container_put(c); + exit(ret); +} diff --git a/src/tests/get_item.c b/src/tests/get_item.c new file mode 100644 index 000000000..0d32eca1e --- /dev/null +++ b/src/tests/get_item.c @@ -0,0 +1,308 @@ +/* liblxcapi + * + * Copyright © 2012 Serge Hallyn . + * Copyright © 2012 Canonical Ltd. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "../lxc/lxccontainer.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MYNAME "lxctest1" + +int main(int argc, char *argv[]) +{ + struct lxc_container *c; + int ret; + char v1[2], v2[256], v3[2048]; + + if ((c = lxc_container_new("testxyz")) == NULL) { + fprintf(stderr, "%d: error opening lxc_container %s\n", __LINE__, MYNAME); + ret = 1; + goto out; + } + + if (!c->set_config_item(c, "lxc.hook.pre-start", "hi there")) { + fprintf(stderr, "%d: failed to set hook.pre-start\n", __LINE__); + ret = 1; + goto out; + } + ret = c->get_config_item(c, "lxc.hook.pre-start", v2, 255); + if (ret < 0) { + fprintf(stderr, "%d: get_config_item(lxc.hook.pre-start) returned %d\n", __LINE__, ret); + ret = 1; + goto out; + } + fprintf(stderr, "lxc.hook.pre-start returned %d %s\n", ret, v2); + + ret = c->get_config_item(c, "lxc.network", v2, 255); + if (ret < 0) { + fprintf(stderr, "%d: get_config_item returned %d\n", __LINE__, ret); + ret = 1; + goto out; + } + fprintf(stderr, "%d: get_config_item(lxc.network) returned %d %s\n", __LINE__, ret, v2); + if (!c->set_config_item(c, "lxc.tty", "4")) { + fprintf(stderr, "%d: failed to set tty\n", __LINE__); + ret = 1; + goto out; + } + ret = c->get_config_item(c, "lxc.tty", v2, 255); + if (ret < 0) { + fprintf(stderr, "%d: get_config_item(lxc.tty) returned %d\n", __LINE__, ret); + ret = 1; + goto out; + } + fprintf(stderr, "lxc.tty returned %d %s\n", ret, v2); + + if (!c->set_config_item(c, "lxc.arch", "x86")) { + fprintf(stderr, "%d: failed to set arch\n", __LINE__); + ret = 1; + goto out; + } + ret = c->get_config_item(c, "lxc.arch", v2, 255); + if (ret < 0) { + fprintf(stderr, "%d: get_config_item(lxc.arch) returned %d\n", __LINE__, ret); + ret = 1; + goto out; + } + printf("lxc.arch returned %d %s\n", ret, v2); + +#define HNAME "hostname1" + // demonstrate proper usage: + char *alloced; + if (!c->set_config_item(c, "lxc.utsname", HNAME)) { + fprintf(stderr, "%d: failed to set utsname\n", __LINE__); + ret = 1; + goto out; + } + + int len; + len = c->get_config_item(c, "lxc.utsname", NULL, 0); // query the size of the string + if (len < 0) { + fprintf(stderr, "%d: get_config_item(lxc.utsname) returned %d\n", __LINE__, len); + ret = 1; + goto out; + } + printf("lxc.utsname returned %d\n", len); + + // allocate the length of string + 1 for trailing \0 + alloced = malloc(len+1); + if (!alloced) { + fprintf(stderr, "%d: failed to allocate %d bytes for utsname\n", __LINE__, len); + ret = 1; + goto out; + } + // now pass in the malloc'd array, and pass in length of string + 1: again + // because we need room for the trailing \0 + ret = c->get_config_item(c, "lxc.utsname", alloced, len+1); + if (ret < 0) { + fprintf(stderr, "%d: get_config_item(lxc.utsname) returned %d\n", __LINE__, ret); + ret = 1; + goto out; + } + if (strcmp(alloced, HNAME) != 0 || ret != len) { + fprintf(stderr, "lxc.utsname returned wrong value: %d %s not %d %s\n", ret, alloced, len, HNAME); + ret = 1; + goto out; + } + printf("lxc.utsname returned %d %s\n", len, alloced); + free(alloced); + + if (!c->set_config_item(c, "lxc.mount.entry", "hi there")) { + fprintf(stderr, "%d: failed to set mount.entry\n", __LINE__); + ret = 1; + goto out; + } + ret = c->get_config_item(c, "lxc.mount.entry", v2, 255); + if (ret < 0) { + fprintf(stderr, "%d: get_config_item(lxc.mount.entry) returned %d\n", __LINE__, ret); + ret = 1; + goto out; + } + printf("lxc.mount.entry returned %d %s\n", ret, v2); + + if (!c->set_config_item(c, "lxc.aa_profile", "unconfined")) { + fprintf(stderr, "%d: failed to set aa_profile\n", __LINE__); + ret = 1; + goto out; + } + ret = c->get_config_item(c, "lxc.aa_profile", v2, 255); + if (ret < 0) { + fprintf(stderr, "%d: get_config_item(lxc.aa_profile) returned %d\n", __LINE__, ret); + ret = 1; + goto out; + } + printf("lxc.aa_profile returned %d %s\n", ret, v2); + + lxc_container_put(c); + + // new test with real container + if ((c = lxc_container_new(MYNAME)) == NULL) { + fprintf(stderr, "%d: error opening lxc_container %s\n", __LINE__, MYNAME); + ret = 1; + goto out; + } + c->destroy(c); + lxc_container_put(c); + + if ((c = lxc_container_new(MYNAME)) == NULL) { + fprintf(stderr, "%d: error opening lxc_container %s\n", __LINE__, MYNAME); + ret = 1; + goto out; + } + if (!c->createl(c, "ubuntu", "-r", "lucid", NULL)) { + fprintf(stderr, "%d: failed to create a lucid container\n", __LINE__); + ret = 1; + goto out; + } + + lxc_container_put(c); + + /* XXX TODO load_config needs to clear out any old config first */ + if ((c = lxc_container_new(MYNAME)) == NULL) { + fprintf(stderr, "%d: error opening lxc_container %s\n", __LINE__, MYNAME); + ret = 1; + goto out; + } + + ret = c->get_config_item(c, "lxc.cap.drop", NULL, 300); + if (ret < 5 || ret > 255) { + fprintf(stderr, "%d: get_config_item(lxc.cap.drop) with NULL returned %d\n", __LINE__, ret); + ret = 1; + goto out; + } + ret = c->get_config_item(c, "lxc.cap.drop", v1, 1); + if (ret < 5 || ret > 255) { + fprintf(stderr, "%d: get_config_item(lxc.cap.drop) returned %d\n", __LINE__, ret); + ret = 1; + goto out; + } + ret = c->get_config_item(c, "lxc.cap.drop", v2, 255); + if (ret < 0) { + fprintf(stderr, "%d: get_config_item(lxc.cap.drop) returned %d %s\n", __LINE__, ret, v2); + ret = 1; + goto out; + } + printf("%d: get_config_item(lxc.cap.drop) returned %d %s\n", __LINE__, ret, v2); + ret = c->get_config_item(c, "lxc.network", v2, 255); + if (ret < 0) { + fprintf(stderr, "%d: get_config_item returned %d\n", __LINE__, ret); + ret = 1; + goto out; + } + printf("%d: get_config_item(lxc.network) returned %d %s\n", __LINE__, ret, v2); + + if (!c->set_config_item(c, "lxc.network.ipv4", "10.2.3.4")) { + fprintf(stderr, "%d: failed to set ipv4\n", __LINE__); + ret = 1; + goto out; + } + + ret = c->get_config_item(c, "lxc.network.0.ipv4", v2, 255); + if (ret <= 0) { + fprintf(stderr, "%d: lxc.network.0.ipv4 returned %d\n", __LINE__, ret); + ret = 1; + goto out; + } + if (!c->clear_config_item(c, "lxc.network.0.ipv4")) { + fprintf(stderr, "%d: failed clearing all ipv4 entries\n", __LINE__); + ret = 1; + goto out; + } + ret = c->get_config_item(c, "lxc.network.0.ipv4", v2, 255); + if (ret != 0) { + fprintf(stderr, "%d: after clearing ipv4 entries get_item(lxc.network.0.ipv4 returned %d\n", __LINE__, ret); + ret = 1; + goto out; + } + + ret = c->get_config_item(c, "lxc.network.0.link", v2, 255); + if (ret < 0) { + fprintf(stderr, "%d: get_config_item returned %d\n", __LINE__, ret); + ret = 1; + goto out; + } + printf("%d: get_config_item (link) returned %d %s\n", __LINE__, ret, v2); + ret = c->get_config_item(c, "lxc.network.0.name", v2, 255); + if (ret < 0) { + fprintf(stderr, "%d: get_config_item returned %d\n", __LINE__, ret); + ret = 1; + goto out; + } + printf("%d: get_config_item (name) returned %d %s\n", __LINE__, ret, v2); + + if (!c->clear_config_item(c, "lxc.network")) { + fprintf(stderr, "%d: clear_config_item failed\n", __LINE__); + ret = 1; + goto out; + } + ret = c->get_config_item(c, "lxc.network", v2, 255); + if (ret != 0) { + fprintf(stderr, "%d: network was not actually cleared (get_network returned %d)\n", __LINE__, ret); + ret = 1; + goto out; + } + + ret = c->get_config_item(c, "lxc.cgroup", v3, 2047); + if (ret < 0) { + fprintf(stderr, "%d: get_config_item(cgroup.devices) returned %d\n", __LINE__, ret); + ret = 1; + goto out; + } + printf("%d: get_config_item (cgroup.devices) returned %d %s\n", __LINE__, ret, v3); + + ret = c->get_config_item(c, "lxc.cgroup.devices.allow", v3, 2047); + if (ret < 0) { + fprintf(stderr, "%d: get_config_item(cgroup.devices.devices.allow) returned %d\n", __LINE__, ret); + ret = 1; + goto out; + } + printf("%d: get_config_item (cgroup.devices.devices.allow) returned %d %s\n", __LINE__, ret, v3); + + if (!c->clear_config_item(c, "lxc.cgroup")) { + fprintf(stderr, "%d: failed clearing lxc.cgroup", __LINE__); + ret = 1; + goto out; + } + if (!c->clear_config_item(c, "lxc.cap.drop")) { + fprintf(stderr, "%d: failed clearing lxc.cap.drop", __LINE__); + ret = 1; + goto out; + } + if (!c->clear_config_item(c, "lxc.mount.entries")) { + fprintf(stderr, "%d: failed clearing lxc.mount.entries", __LINE__); + ret = 1; + goto out; + } + if (!c->clear_config_item(c, "lxc.hook")) { + fprintf(stderr, "%d: failed clearing lxc.hook", __LINE__); + ret = 1; + goto out; + } + c->destroy(c); + printf("All get_item tests passed\n"); + ret = 0; +out: + lxc_container_put(c); + exit(ret); +}; diff --git a/src/tests/getkeys.c b/src/tests/getkeys.c new file mode 100644 index 000000000..9bb559395 --- /dev/null +++ b/src/tests/getkeys.c @@ -0,0 +1,71 @@ +/* liblxcapi + * + * Copyright © 2012 Serge Hallyn . + * Copyright © 2012 Canonical Ltd. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "../lxc/lxccontainer.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MYNAME "lxctest1" + +int main(int argc, char *argv[]) +{ + struct lxc_container *c; + int len, ret; + char v3[2048]; + + if ((c = lxc_container_new(MYNAME)) == NULL) { + fprintf(stderr, "%d: error opening lxc_container %s\n", __LINE__, MYNAME); + ret = 1; + goto out; + } + + c->set_config_item(c, "lxc.network.type", "veth"); + + len = c->get_keys(c, NULL, NULL, 0); + if (len < 0) { + fprintf(stderr, "%d: failed to get length of all keys (%d)\n", __LINE__, len); + ret = 1; + goto out; + } + ret = c->get_keys(c, NULL, v3, len+1); + if (ret != len) { + fprintf(stderr, "%d: failed to get keys (%d)\n", __LINE__, ret); + ret = 1; + goto out; + } + printf("get_keys returned %d\n%s", ret, v3); + + ret = c->get_keys(c, "lxc.network.0", v3, 2000); + if (ret < 0) { + fprintf(stderr, "%d: failed to get nic 0 keys(%d)\n", __LINE__, ret); + ret = 1; + goto out; + } + printf("get_keys for nic 1 returned %d\n%s", ret, v3); + +out: + lxc_container_put(c); + exit(ret); +} diff --git a/src/tests/locktests.c b/src/tests/locktests.c new file mode 100644 index 000000000..f08555805 --- /dev/null +++ b/src/tests/locktests.c @@ -0,0 +1,239 @@ +/* liblxcapi + * + * Copyright © 2012 Serge Hallyn . + * Copyright © 2012 Canonical Ltd. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "../lxc/lxclock.h" +#include +#include +#include +#include +#include +#include + +#define mycontainername "lxctest.sem" +#define TIMEOUT_SECS 3 + +int timedout; +int pid_to_kill; + +void timeouthandler(int sig) +{ + // timeout received + timedout = 1; + kill(pid_to_kill, SIGTERM); +} + +void starttimer(int secs) +{ + timedout = 0; + signal(SIGALRM, timeouthandler); + alarm(secs); +} +void stoptimer(void) +{ + alarm(0); + signal(SIGALRM, NULL); +} + +int test_one_lock(sem_t *lock) +{ + int ret; + starttimer(TIMEOUT_SECS); + ret = lxclock(lock, TIMEOUT_SECS*2); + stoptimer(); + if (ret == 0) { + lxcunlock(lock); + return 0; + } + if (timedout) + fprintf(stderr, "%d: timed out waiting for lock\n", __LINE__); + else + fprintf(stderr, "%d: failed to get single lock\n", __LINE__); + return 1; +} + +/* + * get one lock. Fork a second task to try to get a second lock, + * with infinite timeout. If our alarm hits, kill the second + * task. If second task does not + */ +int test_two_locks(sem_t *lock) +{ + int status; + int ret; + + ret = lxclock(lock, 1); + if (ret) { + fprintf(stderr, "%d: Error getting first lock\n", __LINE__); + return 2; + } + + pid_to_kill = fork(); + if (pid_to_kill < 0) { + fprintf(stderr, "%d: Failed to fork\n", __LINE__); + lxcunlock(lock); + return 3; + } + + if (pid_to_kill == 0) { // child + ret = lxclock(lock, TIMEOUT_SECS*2); + if (ret == 0) { + lxcunlock(lock); + exit(0); + } + fprintf(stderr, "%d: child, was not able to get lock\n", __LINE__); + exit(1); + } + starttimer(TIMEOUT_SECS); + waitpid(pid_to_kill, &status, 0); + stoptimer(); + if (WIFEXITED(status)) { + // child exited normally - timeout didn't kill it + if (WEXITSTATUS(status) == 0) + fprintf(stderr, "%d: child was able to get the lock\n", __LINE__); + else + fprintf(stderr, "%d: child timed out too early\n", __LINE__); + lxcunlock(lock); + return 1; + } + lxcunlock(lock); + return 0; +} + +/* + * get one lock. try to get second lock, but asking for timeout. If + * should return failure. If our own alarm, set at twice the lock + * request's timeout, hits, then lxclock() did not properly time out. + */ +int test_with_timeout(sem_t *lock) +{ + int status; + int ret = 0; + + ret = lxclock(lock, 0); + if (ret) { + fprintf(stderr, "%d: Error getting first lock\n", __LINE__); + return 2; + } + pid_to_kill = fork(); + if (pid_to_kill < 0) { + fprintf(stderr, "%d: Error on fork\n", __LINE__); + lxcunlock(lock); + return 2; + } + if (pid_to_kill == 0) { + ret = lxclock(lock, TIMEOUT_SECS); + if (ret == 0) { + lxcunlock(lock); + exit(0); + } + exit(1); + } + starttimer(TIMEOUT_SECS * 2); + waitpid(pid_to_kill, &status, 0); + stoptimer(); + if (!WIFEXITED(status)) { + fprintf(stderr, "%d: lxclock did not honor its timeout\n", __LINE__); + lxcunlock(lock); + return 1; + } + if (WEXITSTATUS(status) == 0) { + fprintf(stderr, "%d: child was able to get lock, should have failed with timeout\n", __LINE__); + ret = 1; + } + lxcunlock(lock); + return ret; +} + +int main(int argc, char *argv[]) +{ + int ret, sval, r; + sem_t *lock; + + lock = lxc_newlock(NULL); + if (!lock) { + fprintf(stderr, "%d: failed to get unnamed lock\n", __LINE__); + exit(1); + } + ret = lxclock(lock, 0); + if (ret) { + fprintf(stderr, "%d: failed to take unnamed lock (%d)\n", __LINE__, ret); + exit(1); + } + + ret = lxcunlock(lock); + if (ret) { + fprintf(stderr, "%d: failed to put unnamed lock (%d)\n", __LINE__, ret); + exit(1); + } + + sem_destroy(lock); + free(lock); + + lock = lxc_newlock(mycontainername); + if (!lock) { + fprintf(stderr, "%d: failed to get lock\n", __LINE__); + exit(1); + } + r = sem_getvalue(lock, &sval); + if (!r) { + fprintf(stderr, "%d: sem value at start is %d\n", __LINE__, sval); + } else { + fprintf(stderr, "%d: failed to get initial value\n", __LINE__); + } + + ret = test_one_lock(lock); + if (ret) { + fprintf(stderr, "%d: test failed\n", __LINE__); + goto out; + } + r = sem_getvalue(lock, &sval); + if (!r) { + fprintf(stderr, "%d: sem value is %d\n", __LINE__, sval); + } else { + fprintf(stderr, "%d: failed to get sem value\n", __LINE__); + } + + ret = test_two_locks(lock); + if (ret) { + fprintf(stderr, "%d: test failed\n", __LINE__); + goto out; + } + r = sem_getvalue(lock, &sval); + if (!r) { + fprintf(stderr, "%d: sem value is %d\n", __LINE__, sval); + } else { + fprintf(stderr, "%d: failed to get value\n", __LINE__); + } + + ret = test_with_timeout(lock); + if (ret) { + fprintf(stderr, "%d: test failed\n", __LINE__); + goto out; + } + r = sem_getvalue(lock, &sval); + if (!r) { + fprintf(stderr, "%d: sem value is %d\n", __LINE__, sval); + } else { + fprintf(stderr, "%d: failed to get value\n", __LINE__); + } + + fprintf(stderr, "all tests passed\n"); + +out: + exit(ret); +} diff --git a/src/tests/saveconfig.c b/src/tests/saveconfig.c new file mode 100644 index 000000000..21450b288 --- /dev/null +++ b/src/tests/saveconfig.c @@ -0,0 +1,106 @@ +/* liblxcapi + * + * Copyright © 2012 Serge Hallyn . + * Copyright © 2012 Canonical Ltd. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "../lxc/lxccontainer.h" + +#include +#include +#include +#include +#include +#include +#include + +#define MYNAME "lxctest1" + +static int create_ubuntu(void) +{ + int status, ret; + pid_t pid = fork(); + + if (pid < 0) { + perror("fork"); + return -1; + } + if (pid == 0) { + ret = execlp("lxc-create", "lxc-create", "-t", "ubuntu", "-f", "/etc/lxc/lxc.conf", "-n", MYNAME, NULL); + // Should not return + perror("execl"); + exit(1); + } +again: + ret = waitpid(pid, &status, 0); + if (ret == -1) { + if (errno == -EINTR) + goto again; + perror("waitpid"); + return -1; + } + if (ret != pid) + goto again; + if (!WIFEXITED(status)) { // did not exit normally + fprintf(stderr, "%d: lxc-create exited abnormally\n", __LINE__); + return -1; + } + return WEXITSTATUS(status); +} + +int main(int argc, char *argv[]) +{ + struct lxc_container *c; + int ret = 1; + + if ((c = lxc_container_new(MYNAME)) == NULL) { + fprintf(stderr, "%d: error opening lxc_container %s\n", __LINE__, MYNAME); + ret = 1; + goto out; + } + + if (c->is_defined(c)) { + fprintf(stderr, "%d: %s thought it was defined\n", __LINE__, MYNAME); + goto out; + } + + if (create_ubuntu()) { + fprintf(stderr, "%d: failed to create a ubuntu container\n", __LINE__); + goto out; + } + + if (!c->is_defined(c)) { + fprintf(stderr, "%d: %s thought it was not defined\n", __LINE__, MYNAME); + goto out; + } + + c->load_config(c, NULL); + unlink("/tmp/lxctest1"); + if (!c->save_config(c, "/tmp/lxctest1")) { + fprintf(stderr, "%d: failed writing config file /tmp/lxctest1\n", __LINE__); + goto out; + } + rename(LXCPATH "/" MYNAME "/config", LXCPATH "/" MYNAME "/config.bak"); + if (!c->save_config(c, NULL)) { + fprintf(stderr, "%d: failed writing config file\n", __LINE__); + goto out; + } + + fprintf(stderr, "all lxc_container tests passed for %s\n", c->name); + ret = 0; +out: + lxc_container_put(c); + exit(ret); +} diff --git a/src/tests/shutdowntest.c b/src/tests/shutdowntest.c new file mode 100644 index 000000000..9ad8a6510 --- /dev/null +++ b/src/tests/shutdowntest.c @@ -0,0 +1,93 @@ + +/* liblxcapi + * + * Copyright © 2012 Serge Hallyn . + * Copyright © 2012 Canonical Ltd. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "../lxc/lxccontainer.h" + +#include +#include +#include +#include +#include +#include +#include + +#define MYNAME "lxctest1" + +int main(int argc, char *argv[]) +{ + struct lxc_container *c; + int ret = 1; + + if ((c = lxc_container_new(MYNAME)) == NULL) { + fprintf(stderr, "%d: error opening lxc_container %s\n", __LINE__, MYNAME); + ret = 1; + goto out; + } + + if (c->is_defined(c)) { + fprintf(stderr, "%d: %s thought it was defined\n", __LINE__, MYNAME); + goto out; + } + + if (!c->set_config_item(c, "lxc.network.type", "veth")) { + fprintf(stderr, "%d: failed to set network type\n", __LINE__); + goto out; + } + c->set_config_item(c, "lxc.network.link", "lxcbr0"); + c->set_config_item(c, "lxc.network.flags", "up"); + if (!c->createl(c, "ubuntu", "-r", "lucid", NULL)) { + fprintf(stderr, "%d: failed to create a lucid container\n", __LINE__); + goto out; + } + + if (!c->is_defined(c)) { + fprintf(stderr, "%d: %s thought it was not defined\n", __LINE__, MYNAME); + goto out; + } + + c->load_config(c, NULL); + c->want_daemonize(c); + if (!c->startl(c, 0, NULL)) { + fprintf(stderr, "%d: failed to start %s\n", __LINE__, MYNAME); + goto out; + } + fprintf(stderr, "%d: %s started, you have 60 seconds to test a console\n", __LINE__, MYNAME); + sleep(60); // wait a minute to let user connect to console + + if (!c->shutdown(c, 60)) { + fprintf(stderr, "%d: failed to shut down %s\n", __LINE__, MYNAME); + goto out; + } + + if (!c->destroy(c)) { + fprintf(stderr, "%d: error deleting %s\n", __LINE__, MYNAME); + goto out; + } + + if (c->is_defined(c)) { + fprintf(stderr, "%d: %s thought it was defined\n", __LINE__, MYNAME); + goto out; + } + + fprintf(stderr, "all lxc_container tests passed for %s\n", c->name); + ret = 0; +out: + lxc_container_put(c); + exit(ret); +} diff --git a/src/tests/startone.c b/src/tests/startone.c new file mode 100644 index 000000000..325942e95 --- /dev/null +++ b/src/tests/startone.c @@ -0,0 +1,264 @@ +/* liblxcapi + * + * Copyright © 2012 Serge Hallyn . + * Copyright © 2012 Canonical Ltd. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "../lxc/lxccontainer.h" + +#include +#include +#include +#include +#include +#include +#include + +#define MYNAME "lxctest1" + +static int destroy_ubuntu(void) +{ + int status, ret; + pid_t pid = fork(); + + if (pid < 0) { + perror("fork"); + return -1; + } + if (pid == 0) { + ret = execlp("lxc-destroy", "lxc-destroy", "-f", "-n", MYNAME, NULL); + // Should not return + perror("execl"); + exit(1); + } +again: + ret = waitpid(pid, &status, 0); + if (ret == -1) { + if (errno == -EINTR) + goto again; + perror("waitpid"); + return -1; + } + if (ret != pid) + goto again; + if (!WIFEXITED(status)) { // did not exit normally + fprintf(stderr, "%d: lxc-create exited abnormally\n", __LINE__); + return -1; + } + return WEXITSTATUS(status); +} + +static int create_ubuntu(void) +{ + int status, ret; + pid_t pid = fork(); + + if (pid < 0) { + perror("fork"); + return -1; + } + if (pid == 0) { + ret = execlp("lxc-create", "lxc-create", "-t", "ubuntu", "-f", "/etc/lxc/lxc.conf", "-n", MYNAME, NULL); + // Should not return + perror("execl"); + exit(1); + } +again: + ret = waitpid(pid, &status, 0); + if (ret == -1) { + if (errno == -EINTR) + goto again; + perror("waitpid"); + return -1; + } + if (ret != pid) + goto again; + if (!WIFEXITED(status)) { // did not exit normally + fprintf(stderr, "%d: lxc-create exited abnormally\n", __LINE__); + return -1; + } + return WEXITSTATUS(status); +} + +int main(int argc, char *argv[]) +{ + struct lxc_container *c; + int ret = 0; + const char *s; + bool b; + char buf[201]; + int len; + + ret = 1; + /* test a real container */ + c = lxc_container_new(MYNAME); + if (!c) { + fprintf(stderr, "%d: error creating lxc_container %s\n", __LINE__, MYNAME); + ret = 1; + goto out; + } + + if (c->is_defined(c)) { + fprintf(stderr, "%d: %s thought it was defined\n", __LINE__, MYNAME); + goto out; + } + + ret = create_ubuntu(); + if (ret) { + fprintf(stderr, "%d: failed to create a ubuntu container\n", __LINE__); + goto out; + } + + b = c->is_defined(c); + if (!b) { + fprintf(stderr, "%d: %s thought it was not defined\n", __LINE__, MYNAME); + goto out; + } + + len = c->get_cgroup_item(c, "cpuset.cpus", buf, 200); + if (len >= 0) { + fprintf(stderr, "%d: %s not running but had cgroup settings\n", __LINE__, MYNAME); + goto out; + } + + sprintf(buf, "0"); + b = c->set_cgroup_item(c, "cpuset.cpus", buf); + if (b) { + fprintf(stderr, "%d: %s not running but coudl set cgroup settings\n", __LINE__, MYNAME); + goto out; + } + + s = c->state(c); + if (!s || strcmp(s, "STOPPED")) { + fprintf(stderr, "%d: %s is in state %s, not in STOPPED.\n", __LINE__, c->name, s ? s : "undefined"); + goto out; + } + + b = c->load_config(c, NULL); + if (!b) { + fprintf(stderr, "%d: %s failed to read its config\n", __LINE__, c->name); + goto out; + } + + if (!c->set_config_item(c, "lxc.utsname", "bobo")) { + fprintf(stderr, "%d: failed setting lxc.utsname\n", __LINE__); + goto out; + } + + printf("hit return to start container"); + char mychar; + ret = scanf("%c", &mychar); + if (ret < 0) + goto out; + + if (!lxc_container_get(c)) { + fprintf(stderr, "%d: failed to get extra ref to container\n", __LINE__); + exit(1); + } + pid_t pid = fork(); + if (pid < 0) { + fprintf(stderr, "%d: fork failed\n", __LINE__); + goto out; + } + if (pid == 0) { + b = c->startl(c, 0, NULL); + if (!b) + fprintf(stderr, "%d: %s failed to start\n", __LINE__, c->name); + lxc_container_put(c); + exit(!b); + } + + sleep(3); + s = c->state(c); + if (!s || strcmp(s, "RUNNING")) { + fprintf(stderr, "%d: %s is in state %s, not in RUNNING.\n", __LINE__, c->name, s ? s : "undefined"); + goto out; + } + + len = c->get_cgroup_item(c, "cpuset.cpus", buf, 0); + if (len <= 0) { + fprintf(stderr, "%d: not able to get length of cpuset.cpus (ret %d)\n", __LINE__, len); + goto out; + } + + len = c->get_cgroup_item(c, "cpuset.cpus", buf, 200); + if (len <= 0 || strcmp(buf, "0\n")) { + fprintf(stderr, "%d: not able to get cpuset.cpus (len %d buf %s)\n", __LINE__, len, buf); + goto out; + } + + sprintf(buf, "FROZEN"); + b = c->set_cgroup_item(c, "freezer.state", buf); + if (!b) { + fprintf(stderr, "%d: not able to set freezer.state.\n", __LINE__); + goto out; + } + + sprintf(buf, "XXX"); + len = c->get_cgroup_item(c, "freezer.state", buf, 200); + if (len <= 0 || (strcmp(buf, "FREEZING\n") && strcmp(buf, "FROZEN\n"))) { + fprintf(stderr, "%d: not able to get freezer.state (len %d buf %s)\n", __LINE__, len, buf); + goto out; + } + + c->set_cgroup_item(c, "freezer.state", "THAWED"); + + printf("hit return to finish"); + ret = scanf("%c", &mychar); + if (ret < 0) + goto out; + c->stop(c); + + /* feh - multilib has moved the lxc-init crap */ + goto ok; + + ret = system("mkdir -p " LXCPATH "/lxctest1/rootfs//usr/local/libexec/lxc"); + if (!ret) + ret = system("mkdir -p " LXCPATH "/lxctest1/rootfs/usr/lib/lxc/"); + if (!ret) + ret = system("cp src/lxc/lxc-init " LXCPATH "/lxctest1/rootfs//usr/local/libexec/lxc"); + if (!ret) + ret = system("cp src/lxc/liblxc.so " LXCPATH "/lxctest1/rootfs/usr/lib/lxc"); + if (!ret) + ret = system("cp src/lxc/liblxc.so " LXCPATH "/lxctest1/rootfs/usr/lib/lxc/liblxc.so.0"); + if (!ret) + ret = system("cp src/lxc/liblxc.so " LXCPATH "/lxctest1/rootfs/usr/lib"); + if (!ret) + ret = system("mkdir -p " LXCPATH "/lxctest1/rootfs/dev/shm"); + if (!ret) + ret = system("chroot " LXCPATH "/lxctest1/rootfs apt-get install --no-install-recommends lxc"); + if (ret) { + fprintf(stderr, "%d: failed to installing lxc-init in container\n", __LINE__); + goto out; + } + // next write out the config file; does it match? + if (!c->startl(c, 1, "/bin/hostname", NULL)) { + fprintf(stderr, "%d: failed to lxc-execute /bin/hostname\n", __LINE__); + goto out; + } + // auto-check result? ('bobo' is printed on stdout) + +ok: + fprintf(stderr, "all lxc_container tests passed for %s\n", c->name); + ret = 0; + +out: + if (c) { + c->stop(c); + destroy_ubuntu(); + } + lxc_container_put(c); + exit(ret); +} diff --git a/templates/Makefile.am b/templates/Makefile.am index d6b389289..9a94f321c 100644 --- a/templates/Makefile.am +++ b/templates/Makefile.am @@ -1,3 +1,5 @@ +EXTRA_DIST=lxc-ubuntu + templatesdir=@LXCTEMPLATEDIR@ templates_SCRIPTS = \ @@ -7,6 +9,7 @@ templates_SCRIPTS = \ lxc-ubuntu-cloud \ lxc-opensuse \ lxc-fedora \ + lxc-oracle \ lxc-altlinux \ lxc-busybox \ lxc-sshd \ diff --git a/templates/lxc-altlinux.in b/templates/lxc-altlinux.in index 2d2274a33..fac545cc3 100644 --- a/templates/lxc-altlinux.in +++ b/templates/lxc-altlinux.in @@ -17,7 +17,7 @@ # This library 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 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public @@ -26,7 +26,7 @@ #Configurations arch=$(arch) -cache_base=/var/cache/lxc/altlinux/$arch +cache_base=@LOCALSTATEDIR@/cache/lxc/altlinux/$arch default_path=@LXCPATH@ default_profile=default profile_dir=/etc/lxc/profiles @@ -151,8 +151,8 @@ download_altlinux() INSTALL_ROOT=$cache/partial mkdir -p $INSTALL_ROOT if [ $? -ne 0 ]; then - echo "Failed to create '$INSTALL_ROOT' directory" - return 1 + echo "Failed to create '$INSTALL_ROOT' directory" + return 1 fi # download a mini altlinux into a cache @@ -166,8 +166,8 @@ download_altlinux() $APT_GET install $PKG_LIST if [ $? -ne 0 ]; then - echo "Failed to download the rootfs, aborting." - return 1 + echo "Failed to download the rootfs, aborting." + return 1 fi mv "$INSTALL_ROOT" "$cache/rootfs" @@ -184,7 +184,7 @@ copy_altlinux() #cp -a $cache/rootfs-$arch $rootfs_path || return 1 # i prefer rsync (no reason really) mkdir -p $rootfs_path - rsync -a $cache/rootfs/ $rootfs_path/ + rsync -Ha $cache/rootfs/ $rootfs_path/ return 0 } @@ -196,41 +196,39 @@ update_altlinux() install_altlinux() { - mkdir -p /var/lock/subsys/ + mkdir -p @LOCALSTATEDIR@/lock/subsys/ ( - flock -x 200 - if [ $? -ne 0 ]; then - echo "Cache repository is busy." - return 1 - fi + flock -x 200 + if [ $? -ne 0 ]; then + echo "Cache repository is busy." + return 1 + fi - echo "Checking cache download in $cache/rootfs ... " - if [ ! -e "$cache/rootfs" ]; then - download_altlinux - if [ $? -ne 0 ]; then - echo "Failed to download 'altlinux base'" - return 1 - fi + echo "Checking cache download in $cache/rootfs ... " + if [ ! -e "$cache/rootfs" ]; then + download_altlinux + if [ $? -ne 0 ]; then + echo "Failed to download 'altlinux base'" + return 1 + fi else - echo "Cache found. Updating..." + echo "Cache found. Updating..." update_altlinux - if [ $? -ne 0 ]; then - echo "Failed to update 'altlinux base', continuing with last known good cache" + if [ $? -ne 0 ]; then + echo "Failed to update 'altlinux base', continuing with last known good cache" else echo "Update finished" - fi - fi + fi + fi - echo "Copy $cache/rootfs to $rootfs_path ... " - copy_altlinux - if [ $? -ne 0 ]; then - echo "Failed to copy rootfs" - return 1 - fi - - return 0 - - ) 200>/var/lock/subsys/lxc + echo "Copy $cache/rootfs to $rootfs_path ... " + copy_altlinux + if [ $? -ne 0 ]; then + echo "Failed to copy rootfs" + return 1 + fi + return 0 + ) 200>@LOCALSTATEDIR@/lock/subsys/lxc-altlinux return $? } @@ -239,12 +237,12 @@ copy_configuration() { mkdir -p $config_path + grep -q "^lxc.rootfs" $config_path/config 2>/dev/null || echo "lxc.rootfs = $rootfs_path" >> $config_path/config cat <> $config_path/config lxc.utsname = $name lxc.tty = 4 lxc.pts = 1024 -lxc.rootfs = $rootfs_path -lxc.mount = $config_path/fstab +lxc.mount = $config_path/fstab # When using LXC with apparmor, uncomment the next line to run unconfined: #lxc.aa_profile = unconfined @@ -302,8 +300,8 @@ sysfs $rootfs_path/sys sysfs defaults 0 0 EOF if [ $? -ne 0 ]; then - echo "Failed to add configuration" - return 1 + echo "Failed to add configuration" + return 1 fi return 0 @@ -313,22 +311,21 @@ clean() { if [ ! -e $cache ]; then - exit 0 + exit 0 fi # lock, so we won't purge while someone is creating a repository ( - flock -x 200 - if [ $? != 0 ]; then - echo "Cache repository is busy." - exit 1 - fi + flock -x 200 + if [ $? != 0 ]; then + echo "Cache repository is busy." + exit 1 + fi - echo -n "Purging the download cache for ALTLinux-$release..." - rm --preserve-root --one-file-system -rf $cache && echo "Done." || exit 1 - exit 0 - - ) 200>/var/lock/subsys/lxc + echo -n "Purging the download cache for ALTLinux-$release..." + rm --preserve-root --one-file-system -rf $cache && echo "Done." || exit 1 + exit 0 + ) 200>@LOCALSTATEDIR@/lock/subsys/lxc-altlinux } usage() @@ -345,7 +342,7 @@ usage: Mandatory args: -n,--name container name, used to as an identifier for that container from now on Optional args: - -p,--path path to where the container rootfs will be created, defaults to /var/lib/lxc. The container config will go under /var/lib/lxc in and case + -p,--path path to where the container rootfs will be created, defaults to @LXCPATH@. The container config will go under @LXCPATH@ in that case -c,--clean clean the cache -R,--release ALTLinux release for the new container. if the host is ALTLinux, then it will defaultto the host's release. -4,--ipv4 specify the ipv4 address to assign to the virtualized interface, eg. 192.168.1.123/24 @@ -370,17 +367,17 @@ eval set -- "$options" while true do case "$1" in - -h|--help) usage $0 && exit 0;; - -p|--path) path=$2; shift 2;; - -n|--name) name=$2; shift 2;; - -P|--profile) profile=$2; shift 2;; - -c|--clean) clean=$2; shift 2;; - -R|--release) release=$2; shift 2;; - -4|--ipv4) ipv4=$2; shift 2;; - -6|--ipv6) ipv6=$2; shift 2;; - -g|--gw) gw=$2; shift 2;; - -d|--dns) dns=$2; shift 2;; - --) shift 1; break ;; + -h|--help) usage $0 && exit 0;; + -p|--path) path=$2; shift 2;; + -n|--name) name=$2; shift 2;; + -P|--profile) profile=$2; shift 2;; + -c|--clean) clean=$2; shift 2;; + -R|--release) release=$2; shift 2;; + -4|--ipv4) ipv4=$2; shift 2;; + -6|--ipv6) ipv6=$2; shift 2;; + -g|--gw) gw=$2; shift 2;; + -d|--dns) dns=$2; shift 2;; + --) shift 1; break ;; *) break ;; esac done @@ -433,6 +430,11 @@ if [ -f $config_path/config ]; then exit 1 fi +# check for 'lxc.rootfs' passed in through default config by lxc-create +if grep -q '^lxc.rootfs' $path/config 2>/dev/null ; then + rootfs_path=`grep 'lxc.rootfs =' $path/config | awk -F= '{ print $2 }'` +fi + install_altlinux if [ $? -ne 0 ]; then echo "failed to install altlinux" @@ -456,4 +458,4 @@ if [ ! -z $clean ]; then exit 0 fi echo "container rootfs and config created" -echo "container is configured for lxc.network.type=veth and lxc.network.link=virbr0 (which is default if you have libvirt runnig)" +echo "network configured as $lxc_network_type in the $lxc_network_link" diff --git a/templates/lxc-archlinux.in b/templates/lxc-archlinux.in index 18ec0642d..cf274d4a0 100644 --- a/templates/lxc-archlinux.in +++ b/templates/lxc-archlinux.in @@ -26,10 +26,10 @@ # defaults arch=$(arch) -cache=/var/cache/lxc/arch/${arch} +cache=@LOCALSTATEDIR@/lxc/arch/${arch} lxc_network_type="veth" lxc_network_link="br0" -default_path=/var/lib/lxc +default_path=@LXCPATH@ default_rc_locale="en-US.UTF-8" default_rc_timezone="UTC" host_mirror="http://mirrors.kernel.org/archlinux/\$repo/os/$arch" @@ -218,22 +218,22 @@ EOF # write container configuration files function copy_configuration { mkdir -p "${config_path}" + grep -q "^lxc.rootfs" "${config_path}/config" 2>/dev/null || echo "lxc.rootfs=${rootfs_path}" >> "${config_path}/config" cat > "${config_path}/config" << EOF -lxc.utsname=${name} -lxc.tty=4 -lxc.pts=1024 -lxc.rootfs=${rootfs_path} -lxc.mount=${config_path}/fstab +lxc.utsname = ${name} +lxc.tty = 4 +lxc.pts = 1024 +lxc.mount = ${config_path}/fstab # When using LXC with apparmor, uncomment the next line to run unconfined: #lxc.aa_profile = unconfined #networking -lxc.network.type=${lxc_network_type} -lxc.network.flags=up -lxc.network.link=${lxc_network_link} -lxc.network.name=eth0 -lxc.network.mtu=1500 +lxc.network.type = ${lxc_network_type} +lxc.network.flags = up +lxc.network.link = ${lxc_network_link} +lxc.network.name = eth0 +lxc.network.mtu = 1500 #cgroups lxc.cgroup.devices.deny = a # /dev/null and zero @@ -375,7 +375,7 @@ usage: Mandatory args: -n,--name container name, used to as an identifier for that container from now on Optional args: - -p,--path path to where the container rootfs will be created, defaults to /var/lib/lxc. The container config will go under /var/lib/lxc in that case + -p,--path path to where the container rootfs will be created, defaults to $default_path. The container config will go under $default_path in that case -P,--packages preinstall additional packages, comma-separated list -h,--help print this help EOF @@ -423,6 +423,10 @@ if [ "${EUID}" != "0" ]; then fi rootfs_path="${path}/rootfs" +# check for 'lxc.rootfs' passed in through default config by lxc-create +if grep -q '^lxc.rootfs' $path/config 2>/dev/null ; then + rootfs_path=`grep 'lxc.rootfs =' $path/config | awk -F= '{ print $2 }'` +fi config_path="${default_path}/${name}" revert() diff --git a/templates/lxc-busybox.in b/templates/lxc-busybox.in index 06d2f8916..f2751d8fa 100644 --- a/templates/lxc-busybox.in +++ b/templates/lxc-busybox.in @@ -108,36 +108,34 @@ EOF cat <> $rootfs/usr/share/udhcpc/default.script #!/bin/sh - case "\$1" in - deconfig) - ip addr flush dev \$interface - ;; + deconfig) + ip addr flush dev \$interface + ;; - renew|bound) + renew|bound) + # flush all the routes + if [ -n "\$router" ]; then + ip route del default 2> /dev/null + fi - # flush all the routes - if [ -n "\$router" ]; then - ip route del default 2> /dev/null - fi + # check broadcast + if [ -n "\$broadcast" ]; then + broadcast="broadcast \$broadcast" + fi - # check broadcast - if [ -n "\$broadcast" ]; then - broadcast="broadcast \$broadcast" - fi + # add a new ip address + ip addr add \$ip/\$mask \$broadcast dev \$interface - # add a new ip address - ip addr add \$ip/\$mask \$broadcast dev \$interface + if [ -n "\$router" ]; then + ip route add default via \$router dev \$interface + fi - if [ -n "\$router" ]; then - ip route add default via \$router dev \$interface - fi - - [ -n "\$domain" ] && echo search \$domain > /etc/resolv.conf - for i in \$dns ; do - echo nameserver \$i >> /etc/resolv.conf - done - ;; + [ -n "\$domain" ] && echo search \$domain > /etc/resolv.conf + for i in \$dns ; do + echo nameserver \$i >> /etc/resolv.conf + done + ;; esac exit 0 EOF @@ -151,63 +149,35 @@ configure_busybox() { rootfs=$1 - functions="\ - [ [[ addgroup adduser adjtimex ar arp arping ash awk basename \ - brctl bunzip2 bzcat bzip2 cal cat catv chattr chgrp chmod \ - chown chpasswd chpst chroot chrt chvt cksum clear cmp comm \ - cp cpio crond crontab cryptpw cut date dc dd deallocvt \ - delgroup deluser df dhcprelay diff dirname dmesg dnsd dos2unix \ - du dumpkmap dumpleases echo ed egrep eject env envdir envuidgid \ - ether-wake expand expr fakeidentd false fbset fdformat fdisk \ - fetchmail fgrep find findfs fold free freeramdisk fsck \ - fsck.minix ftpget ftpput fuser getopt getty grep gunzip gzip \ - halt hdparm head hexdump hostid hostname httpd hwclock id \ - ifconfig ifdown ifenslave ifup inetd init insmod install ip \ - ipaddr ipcalc ipcrm ipcs iplink iproute iprule iptunnel \ - kbd_mode kill killall killall5 klogd last length less linux32 \ - linux64 linuxrc ln loadfont loadkmap logger login logname \ - logread losetup lpd lpq lpr ls lsattr lsmod lzmacat makedevs \ - md5sum mdev mesg microcom mkdir mkfifo mkfs.minix mknod mkswap \ - mktemp modprobe more mount mountpoint msh mt mv nameif nc \ - netstat nice nmeter nohup nslookup od openvt passwd patch \ - pgrep pidof ping ping6 pipe_progress pivot_root pkill poweroff \ - printenv printf ps pscan pwd raidautorun rdate readahead \ - readlink readprofile realpath reboot renice reset resize rm \ - rmdir rmmod route rpm rpm2cpio run-parts runlevel runsv \ - runsvdir rx script sed sendmail seq setarch setconsole \ - setkeycodes setlogcons setsid setuidgid sh sha1sum slattach \ - sleep softlimit sort split start-stop-daemon stat strings \ - stty su sulogin sum sv svlogd swapoff swapon switch_root \ - sync sysctl syslogd tac tail tar taskset tcpsvd tee telnet \ - telnetd test tftp tftpd time top touch tr traceroute \ - true tty ttysize udhcpc udhcpd udpsvd umount uname uncompress \ - unexpand uniq unix2dos unlzma unzip uptime usleep uudecode \ - uuencode vconfig vi vlock watch watchdog wc wget which \ - who whoami xargs yes zcat zcip" - type busybox >/dev/null if [ $? -ne 0 ]; then - echo "busybox executable is not accessible" - return 1 + echo "busybox executable is not accessible" + return 1 fi file $(which busybox) | grep -q "statically linked" if [ $? -ne 0 ]; then - echo "warning : busybox is not statically linked." - echo "warning : The template script may not correctly" - echo "warning : setup the container environment." + echo "warning : busybox is not statically linked." + echo "warning : The template script may not correctly" + echo "warning : setup the container environment." fi # copy busybox in the rootfs cp $(which busybox) $rootfs/bin if [ $? -ne 0 ]; then - echo "failed to copy busybox in the rootfs" - return 1 + echo "failed to copy busybox in the rootfs" + return 1 fi - # do hardlink to busybox for the different commands - for i in $functions; do ln $rootfs/bin/busybox $rootfs/bin/$i; done + # symlink busybox for the commands it supports + # it would be nice to just use "chroot $rootfs busybox --install -s /bin" + # but that only works right in a chroot with busybox >= 1.19.0 + pushd $rootfs/bin > /dev/null || return 1 + ./busybox --help | grep 'Currently defined functions:' -A300 | \ + grep -v 'Currently defined functions:' | tr , '\n' | \ + xargs -n1 ln -s busybox + popd > /dev/null # relink /sbin/init ln $rootfs/bin/busybox $rootfs/sbin/init @@ -215,9 +185,8 @@ configure_busybox() # passwd exec must be setuid chmod +s $rootfs/bin/passwd touch $rootfs/etc/shadow - chroot $rootfs /bin/passwd -d root - - echo "No password for 'root', please change !" + echo "setting root passwd to root" + echo "root:root" | chroot $rootfs chpasswd return 0 } @@ -228,16 +197,23 @@ copy_configuration() rootfs=$2 name=$3 +grep -q "^lxc.rootfs" $path/config 2>/dev/null || echo "lxc.rootfs = $rootfs" >> $path/config cat <> $path/config lxc.utsname = $name lxc.tty = 1 lxc.pts = 1 -lxc.rootfs = $rootfs # When using LXC with apparmor, uncomment the next line to run unconfined: #lxc.aa_profile = unconfined EOF +if [ -d "$rootfs/lib" ]; then +cat <> $path/config +lxc.mount.entry = /lib $rootfs/lib none ro,bind 0 0 +lxc.mount.entry = /usr/lib $rootfs/usr/lib none ro,bind 0 0 +EOF +fi + libdirs="\ lib \ usr/lib \ @@ -246,7 +222,7 @@ EOF for dir in $libdirs; do if [ -d "/$dir" ] && [ -d "$rootfs/$dir" ]; then - echo "lxc.mount.entry=/$dir $dir none ro,bind 0 0" >> $path/config + echo "lxc.mount.entry = /$dir $dir none ro,bind 0 0" >> $path/config fi done } @@ -261,8 +237,8 @@ EOF options=$(getopt -o hp:n: -l help,path:,name: -- "$@") if [ $? -ne 0 ]; then - usage $(basename $0) - exit 1 + usage $(basename $0) + exit 1 fi eval set -- "$options" @@ -271,7 +247,7 @@ do case "$1" in -h|--help) usage $0 && exit 0;; -p|--path) path=$2; shift 2;; - -n|--name) name=$2; shift 2;; + -n|--name) name=$2; shift 2;; --) shift 1; break ;; *) break ;; esac @@ -287,7 +263,13 @@ if [ -z "$path" ]; then exit 1 fi -rootfs=$path/rootfs +# detect rootfs +config="$path/config" +if grep -q '^lxc.rootfs' $config 2>/dev/null ; then + rootfs=`grep 'lxc.rootfs =' $config | awk -F= '{ print $2 }'` +else + rootfs=$path/rootfs +fi install_busybox $rootfs $name if [ $? -ne 0 ]; then diff --git a/templates/lxc-debian.in b/templates/lxc-debian.in index 0c4d2c504..7bbc46b94 100644 --- a/templates/lxc-debian.in +++ b/templates/lxc-debian.in @@ -13,7 +13,7 @@ # This library 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 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public @@ -31,9 +31,9 @@ configure_debian() # squeeze only has /dev/tty and /dev/tty0 by default, # therefore creating missing device nodes for tty1-4. for tty in $(seq 1 4); do - if [ ! -e $rootfs/dev/tty$tty ]; then - mknod $rootfs/dev/tty$tty c 4 $tty - fi + if [ ! -e $rootfs/dev/tty$tty ]; then + mknod $rootfs/dev/tty$tty c 4 $tty + fi done # configure the inittab @@ -54,7 +54,7 @@ c1:12345:respawn:/sbin/getty 38400 tty1 linux c2:12345:respawn:/sbin/getty 38400 tty2 linux c3:12345:respawn:/sbin/getty 38400 tty3 linux c4:12345:respawn:/sbin/getty 38400 tty4 linux -p6::ctrlaltdel:/sbin/init 6 +p6::ctrlaltdel:/sbin/init 6 p0::powerfail:/sbin/init 0 EOF @@ -78,11 +78,11 @@ EOF # reconfigure some services if [ -z "$LANG" ]; then - chroot $rootfs locale-gen en_US.UTF-8 UTF-8 - chroot $rootfs update-locale LANG=en_US.UTF-8 + chroot $rootfs locale-gen en_US.UTF-8 UTF-8 + chroot $rootfs update-locale LANG=en_US.UTF-8 else - chroot $rootfs locale-gen $LANG $(echo $LANG | cut -d. -f2) - chroot $rootfs update-locale LANG=$LANG + chroot $rootfs locale-gen $LANG $(echo $LANG | cut -d. -f2) + chroot $rootfs update-locale LANG=$LANG fi # remove pointless services in a container @@ -123,18 +123,18 @@ openssh-server # check the mini debian was not already downloaded mkdir -p "$cache/partial-$SUITE-$arch" if [ $? -ne 0 ]; then - echo "Failed to create '$cache/partial-$SUITE-$arch' directory" - return 1 + echo "Failed to create '$cache/partial-$SUITE-$arch' directory" + return 1 fi # download a mini debian into a cache echo "Downloading debian minimal ..." debootstrap --verbose --variant=minbase --arch=$arch \ - --include=$packages \ - "$SUITE" "$cache/partial-$SUITE-$arch" $MIRROR + --include=$packages \ + "$SUITE" "$cache/partial-$SUITE-$arch" $MIRROR if [ $? -ne 0 ]; then - echo "Failed to download the rootfs, aborting." - return 1 + echo "Failed to download the rootfs, aborting." + return 1 fi mv "$1/partial-$SUITE-$arch" "$1/rootfs-$SUITE-$arch" @@ -156,7 +156,7 @@ copy_debian() # make a local copy of the minidebian echo -n "Copying rootfs to $rootfs..." mkdir -p $rootfs - rsync -a "$cache/rootfs-$SUITE-$arch"/ $rootfs/ || return 1 + rsync -Ha "$cache/rootfs-$SUITE-$arch"/ $rootfs/ || return 1 return 0 } @@ -166,32 +166,32 @@ install_debian() rootfs=$1 mkdir -p @LOCALSTATEDIR@/lock/subsys/ ( - flock -x 200 - if [ $? -ne 0 ]; then - echo "Cache repository is busy." - return 1 - fi + flock -x 200 + if [ $? -ne 0 ]; then + echo "Cache repository is busy." + return 1 + fi - arch=$(dpkg --print-architecture) + arch=$(dpkg --print-architecture) - echo "Checking cache download in $cache/rootfs-$SUITE-$arch ... " - if [ ! -e "$cache/rootfs-$SUITE-$arch" ]; then - download_debian $cache $arch - if [ $? -ne 0 ]; then - echo "Failed to download 'debian base'" - return 1 - fi - fi + echo "Checking cache download in $cache/rootfs-$SUITE-$arch ... " + if [ ! -e "$cache/rootfs-$SUITE-$arch" ]; then + download_debian $cache $arch + if [ $? -ne 0 ]; then + echo "Failed to download 'debian base'" + return 1 + fi + fi - copy_debian $cache $arch $rootfs - if [ $? -ne 0 ]; then - echo "Failed to copy rootfs" - return 1 - fi + copy_debian $cache $arch $rootfs + if [ $? -ne 0 ]; then + echo "Failed to copy rootfs" + return 1 + fi - return 0 + return 0 - ) 200>@LOCALSTATEDIR@/lock/subsys/lxc + ) 200>@LOCALSTATEDIR@/lock/subsys/lxc-debian return $? } @@ -202,10 +202,10 @@ copy_configuration() rootfs=$2 hostname=$3 + grep -q "^lxc.rootfs" $path/config 2>/dev/null || echo "lxc.rootfs = $rootfs" >> $path/config cat <> $path/config lxc.tty = 4 lxc.pts = 1024 -lxc.rootfs = $rootfs lxc.utsname = $hostname # When using LXC with apparmor, uncomment the next line to run unconfined: @@ -229,13 +229,13 @@ lxc.cgroup.devices.allow = c 5:2 rwm lxc.cgroup.devices.allow = c 254:0 rwm # mounts point -lxc.mount.entry=proc proc proc nodev,noexec,nosuid 0 0 -lxc.mount.entry=sysfs sys sysfs defaults 0 0 +lxc.mount.entry = proc proc proc nodev,noexec,nosuid 0 0 +lxc.mount.entry = sysfs sys sysfs defaults 0 0 EOF if [ $? -ne 0 ]; then - echo "Failed to add configuration" - return 1 + echo "Failed to add configuration" + return 1 fi return 0 @@ -246,22 +246,22 @@ clean() cache="@LOCALSTATEDIR@/cache/lxc/debian" if [ ! -e $cache ]; then - exit 0 + exit 0 fi # lock, so we won't purge while someone is creating a repository ( - flock -x 200 - if [ $? != 0 ]; then - echo "Cache repository is busy." - exit 1 - fi + flock -x 200 + if [ $? != 0 ]; then + echo "Cache repository is busy." + exit 1 + fi - echo -n "Purging the download cache..." - rm --preserve-root --one-file-system -rf $cache && echo "Done." || exit 1 - exit 0 + echo -n "Purging the download cache..." + rm --preserve-root --one-file-system -rf $cache && echo "Done." || exit 1 + exit 0 - ) 200>@LOCALSTATEDIR@/lock/subsys/lxc + ) 200>@LOCALSTATEDIR@/lock/subsys/lxc-debian } usage() @@ -275,7 +275,7 @@ EOF options=$(getopt -o hp:n:c -l help,path:,name:,clean -- "$@") if [ $? -ne 0 ]; then usage $(basename $0) - exit 1 + exit 1 fi eval set -- "$options" @@ -284,8 +284,8 @@ do case "$1" in -h|--help) usage $0 && exit 0;; -p|--path) path=$2; shift 2;; - -n|--name) name=$2; shift 2;; - -c|--clean) clean=$2; shift 2;; + -n|--name) name=$2; shift 2;; + -c|--clean) clean=$2; shift 2;; --) shift 1; break ;; *) break ;; esac @@ -312,7 +312,14 @@ if [ "$(id -u)" != "0" ]; then exit 1 fi -rootfs=$path/rootfs +# detect rootfs +config="$path/config" +if grep -q '^lxc.rootfs' $config 2>/dev/null ; then + rootfs=`grep 'lxc.rootfs =' $config | awk -F= '{ print $2 }'` +else + rootfs=$path/rootfs +fi + install_debian $rootfs if [ $? -ne 0 ]; then diff --git a/templates/lxc-fedora.in b/templates/lxc-fedora.in index c2d3143d1..366e776c1 100644 --- a/templates/lxc-fedora.in +++ b/templates/lxc-fedora.in @@ -27,8 +27,8 @@ #Configurations arch=$(arch) -cache_base=/var/cache/lxc/fedora/$arch -default_path=/var/lib/lxc +cache_base=@LOCALSTATEDIR@/cache/lxc/fedora/$arch +default_path=@LXCPATH@ root_password=root # is this fedora? @@ -131,8 +131,8 @@ download_fedora() INSTALL_ROOT=$cache/partial mkdir -p $INSTALL_ROOT if [ $? -ne 0 ]; then - echo "Failed to create '$INSTALL_ROOT' directory" - return 1 + echo "Failed to create '$INSTALL_ROOT' directory" + return 1 fi # download a mini fedora into a cache @@ -170,8 +170,8 @@ download_fedora() $YUM install $PKG_LIST if [ $? -ne 0 ]; then - echo "Failed to download the rootfs, aborting." - return 1 + echo "Failed to download the rootfs, aborting." + return 1 fi mv "$INSTALL_ROOT" "$cache/rootfs" @@ -188,7 +188,7 @@ copy_fedora() #cp -a $cache/rootfs-$arch $rootfs_path || return 1 # i prefer rsync (no reason really) mkdir -p $rootfs_path - rsync -a $cache/rootfs/ $rootfs_path/ + rsync -Ha $cache/rootfs/ $rootfs_path/ return 0 } @@ -200,41 +200,40 @@ update_fedora() install_fedora() { - mkdir -p /var/lock/subsys/ + mkdir -p @LOCALSTATEDIR@/lock/subsys/ ( - flock -x 200 - if [ $? -ne 0 ]; then - echo "Cache repository is busy." - return 1 - fi + flock -x 200 + if [ $? -ne 0 ]; then + echo "Cache repository is busy." + return 1 + fi - echo "Checking cache download in $cache/rootfs ... " - if [ ! -e "$cache/rootfs" ]; then - download_fedora - if [ $? -ne 0 ]; then - echo "Failed to download 'fedora base'" - return 1 - fi + echo "Checking cache download in $cache/rootfs ... " + if [ ! -e "$cache/rootfs" ]; then + download_fedora + if [ $? -ne 0 ]; then + echo "Failed to download 'fedora base'" + return 1 + fi else - echo "Cache found. Updating..." + echo "Cache found. Updating..." update_fedora - if [ $? -ne 0 ]; then - echo "Failed to update 'fedora base', continuing with last known good cache" + if [ $? -ne 0 ]; then + echo "Failed to update 'fedora base', continuing with last known good cache" else echo "Update finished" - fi - fi + fi + fi - echo "Copy $cache/rootfs to $rootfs_path ... " - copy_fedora - if [ $? -ne 0 ]; then - echo "Failed to copy rootfs" - return 1 - fi + echo "Copy $cache/rootfs to $rootfs_path ... " + copy_fedora + if [ $? -ne 0 ]; then + echo "Failed to copy rootfs" + return 1 + fi - return 0 - - ) 200>/var/lock/subsys/lxc + return 0 + ) 200>@LOCALSTATEDIR@/lock/subsys/lxc-fedora return $? } @@ -243,12 +242,12 @@ copy_configuration() { mkdir -p $config_path + grep -q "^lxc.rootfs" $config_path/config 2>/dev/null || echo "lxc.rootfs = $rootfs_path" >> $config_path/config cat <> $config_path/config lxc.utsname = $name lxc.tty = 4 lxc.pts = 1024 -lxc.rootfs = $rootfs_path -lxc.mount = $config_path/fstab +lxc.mount = $config_path/fstab # When using LXC with apparmor, uncomment the next line to run unconfined: #lxc.aa_profile = unconfined @@ -277,8 +276,8 @@ proc proc proc nodev,noexec,nosuid 0 0 sysfs sys sysfs defaults 0 0 EOF if [ $? -ne 0 ]; then - echo "Failed to add configuration" - return 1 + echo "Failed to add configuration" + return 1 fi return 0 @@ -288,22 +287,21 @@ clean() { if [ ! -e $cache ]; then - exit 0 + exit 0 fi # lock, so we won't purge while someone is creating a repository ( - flock -x 200 - if [ $? != 0 ]; then - echo "Cache repository is busy." - exit 1 - fi + flock -x 200 + if [ $? != 0 ]; then + echo "Cache repository is busy." + exit 1 + fi - echo -n "Purging the download cache for Fedora-$release..." - rm --preserve-root --one-file-system -rf $cache && echo "Done." || exit 1 - exit 0 - - ) 200>/var/lock/subsys/lxc + echo -n "Purging the download cache for Fedora-$release..." + rm --preserve-root --one-file-system -rf $cache && echo "Done." || exit 1 + exit 0 + ) 200>@LOCALSTATEDIR@/lock/subsys/lxc-fedora } usage() @@ -316,7 +314,7 @@ usage: Mandatory args: -n,--name container name, used to as an identifier for that container from now on Optional args: - -p,--path path to where the container rootfs will be created, defaults to /var/lib/lxc. The container config will go under /var/lib/lxc in that case + -p,--path path to where the container rootfs will be created, defaults to @LXCPATH@. The container config will go under @LXCPATH@ in that case -c,--clean clean the cache -R,--release Fedora release for the new container. if the host is Fedora, then it will defaultto the host's release. -A,--arch NOT USED YET. Define what arch the container will be [i686,x86_64] @@ -335,12 +333,12 @@ eval set -- "$options" while true do case "$1" in - -h|--help) usage $0 && exit 0;; - -p|--path) path=$2; shift 2;; - -n|--name) name=$2; shift 2;; - -c|--clean) clean=$2; shift 2;; + -h|--help) usage $0 && exit 0;; + -p|--path) path=$2; shift 2;; + -n|--name) name=$2; shift 2;; + -c|--clean) clean=$2; shift 2;; -R|--release) release=$2; shift 2;; - --) shift 1; break ;; + --) shift 1; break ;; *) break ;; esac done diff --git a/templates/lxc-lenny.in b/templates/lxc-lenny.in index de03f01d8..cb938536c 100644 --- a/templates/lxc-lenny.in +++ b/templates/lxc-lenny.in @@ -67,11 +67,11 @@ EOF # reconfigure some services if [ -z "$LANG" ]; then - chroot $rootfs locale-gen en_US.UTF-8 - chroot $rootfs update-locale LANG=en_US.UTF-8 + chroot $rootfs locale-gen en_US.UTF-8 + chroot $rootfs update-locale LANG=en_US.UTF-8 else - chroot $rootfs locale-gen $LANG - chroot $rootfs update-locale LANG=$LANG + chroot $rootfs locale-gen $LANG + chroot $rootfs update-locale LANG=$LANG fi # remove pointless services in a container @@ -104,18 +104,19 @@ openssh-server # check the mini debian was not already downloaded mkdir -p "$cache/partial-$SUITE-$arch" if [ $? -ne 0 ]; then - echo "Failed to create '$cache/partial-$SUITE-$arch' directory" - return 1 + echo "Failed to create '$cache/partial-$SUITE-$arch' directory" + return 1 fi # download a mini debian into a cache echo "Downloading debian minimal ..." debootstrap --verbose --variant=minbase --arch=$arch \ - --include $packages \ - "$SUITE" "$cache/partial-$SUITE-$arch" http://ftp.debian.org/debian + --include $packages \ + "$SUITE" "$cache/partial-$SUITE-$arch" \ + http://ftp.debian.org/debian if [ $? -ne 0 ]; then - echo "Failed to download the rootfs, aborting." - return 1 + echo "Failed to download the rootfs, aborting." + return 1 fi mv "$1/partial-$SUITE-$arch" "$1/rootfs-$SUITE-$arch" @@ -142,32 +143,30 @@ install_debian() rootfs=$1 mkdir -p @LOCALSTATEDIR@/lock/subsys/ ( - flock -x 200 - if [ $? -ne 0 ]; then - echo "Cache repository is busy." - return 1 - fi + flock -x 200 + if [ $? -ne 0 ]; then + echo "Cache repository is busy." + return 1 + fi - arch=$(dpkg --print-architecture) + arch=$(dpkg --print-architecture) - echo "Checking cache download in $cache/rootfs-$SUITE-$arch ... " - if [ ! -e "$cache/rootfs-$SUITE-$arch" ]; then - download_debian $cache $arch - if [ $? -ne 0 ]; then - echo "Failed to download 'debian base'" - return 1 - fi - fi + echo "Checking cache download in $cache/rootfs-$SUITE-$arch ... " + if [ ! -e "$cache/rootfs-$SUITE-$arch" ]; then + download_debian $cache $arch + if [ $? -ne 0 ]; then + echo "Failed to download 'debian base'" + return 1 + fi + fi - copy_debian $cache $arch $rootfs - if [ $? -ne 0 ]; then - echo "Failed to copy rootfs" - return 1 - fi - - return 0 - - ) 200>@LOCALSTATEDIR@/lock/subsys/lxc + copy_debian $cache $arch $rootfs + if [ $? -ne 0 ]; then + echo "Failed to copy rootfs" + return 1 + fi + return 0 + ) 200>@LOCALSTATEDIR@/lock/subsys/lxc-lenny return $? } @@ -178,10 +177,10 @@ copy_configuration() rootfs=$2 name=$3 + grep -q "^lxc.rootfs" $path/config 2>/dev/null || echo "lxc.rootfs = $rootfs" >> $path/config cat <> $path/config lxc.tty = 4 lxc.pts = 1024 -lxc.rootfs = $rootfs lxc.cgroup.devices.deny = a # When using LXC with apparmor, uncomment the next line to run unconfined: @@ -204,13 +203,13 @@ lxc.cgroup.devices.allow = c 5:2 rwm lxc.cgroup.devices.allow = c 254:0 rwm # mounts point -lxc.mount.entry=proc proc proc nodev,noexec,nosuid 0 0 -lxc.mount.entry=sysfs sys sysfs defaults 0 0 +lxc.mount.entry = proc proc proc nodev,noexec,nosuid 0 0 +lxc.mount.entry = sysfs sys sysfs defaults 0 0 EOF if [ $? -ne 0 ]; then - echo "Failed to add configuration" - return 1 + echo "Failed to add configuration" + return 1 fi return 0 @@ -221,22 +220,21 @@ clean() cache="@LOCALSTATEDIR@/cache/lxc/$SUITE" if [ ! -e $cache ]; then - exit 0 + exit 0 fi # lock, so we won't purge while someone is creating a repository ( - flock -x 200 - if [ $? != 0 ]; then - echo "Cache repository is busy." - exit 1 - fi + flock -x 200 + if [ $? != 0 ]; then + echo "Cache repository is busy." + exit 1 + fi - echo -n "Purging the download cache..." - rm --preserve-root --one-file-system -rf $cache && echo "Done." || exit 1 - exit 0 - - ) 200>@LOCALSTATEDIR@/lock/subsys/lxc + echo -n "Purging the download cache..." + rm --preserve-root --one-file-system -rf $cache && echo "Done." || exit 1 + exit 0 + ) 200>@LOCALSTATEDIR@/lock/subsys/lxc-lenny } usage() @@ -249,8 +247,8 @@ EOF options=$(getopt -o hp:n:c -l help,path:,name:,clean -- "$@") if [ $? -ne 0 ]; then - usage $(basename $0) - exit 1 + usage $(basename $0) + exit 1 fi eval set -- "$options" @@ -259,8 +257,8 @@ do case "$1" in -h|--help) usage $0 && exit 0;; -p|--path) path=$2; shift 2;; - -n|--name) name=$2; shift 2;; - -c|--clean) clean=$2; shift 2;; + -n|--name) name=$2; shift 2;; + -c|--clean) clean=$2; shift 2;; --) shift 1; break ;; *) break ;; esac @@ -287,7 +285,13 @@ if [ "$(id -u)" != "0" ]; then exit 1 fi -rootfs=$path/rootfs +# detect rootfs +config="$path/config" +if grep -q '^lxc.rootfs' $config 2>/dev/null ; then + rootfs=`grep 'lxc.rootfs =' $config | awk -F= '{ print $2 }'` +else + rootfs=$path/rootfs +fi install_debian $rootfs if [ $? -ne 0 ]; then diff --git a/templates/lxc-opensuse.in b/templates/lxc-opensuse.in index d728af312..65fb7b0f8 100644 --- a/templates/lxc-opensuse.in +++ b/templates/lxc-opensuse.in @@ -18,7 +18,7 @@ # This library 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 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public @@ -142,8 +142,8 @@ download_opensuse() mkdir -p "$cache/partial-$arch" if [ $? -ne 0 ]; then - echo "Failed to create '$cache/partial-$arch' directory" - return 1 + echo "Failed to create '$cache/partial-$arch' directory" + return 1 fi # download a mini opensuse into a cache @@ -187,8 +187,8 @@ EOF rm -f $cache/partial-$arch/etc/mtab ln -sf /proc/self/mounts $cache/partial-$arch/etc/mtab if [ $? -ne 0 ]; then - echo "Failed to download the rootfs, aborting." - return 1 + echo "Failed to download the rootfs, aborting." + return 1 fi rm -fr "$cache/partial-$arch-packages" @@ -207,43 +207,42 @@ copy_opensuse() # make a local copy of the mini opensuse echo -n "Copying rootfs to $rootfs ..." mkdir -p $rootfs - rsync -a $cache/rootfs-$arch/ $rootfs/ || return 1 + rsync -Ha $cache/rootfs-$arch/ $rootfs/ || return 1 return 0 } install_opensuse() { - cache="/var/cache/lxc/opensuse" + cache="@LOCALSTATEDIR@/cache/lxc/opensuse" rootfs=$1 - mkdir -p /var/lock/subsys/ + mkdir -p @LOCALSTATEDIR@/lock/subsys/ ( - flock -x 200 - if [ $? -ne 0 ]; then - echo "Cache repository is busy." - return 1 - fi + flock -x 200 + if [ $? -ne 0 ]; then + echo "Cache repository is busy." + return 1 + fi - arch=$(arch) + arch=$(arch) - echo "Checking cache download in $cache/rootfs-$arch ... " - if [ ! -e "$cache/rootfs-$arch" ]; then - download_opensuse $cache $arch - if [ $? -ne 0 ]; then - echo "Failed to download 'opensuse base'" - return 1 - fi - fi + echo "Checking cache download in $cache/rootfs-$arch ... " + if [ ! -e "$cache/rootfs-$arch" ]; then + download_opensuse $cache $arch + if [ $? -ne 0 ]; then + echo "Failed to download 'opensuse base'" + return 1 + fi + fi - echo "Copy $cache/rootfs-$arch to $rootfs ... " - copy_opensuse $cache $arch $rootfs - if [ $? -ne 0 ]; then - echo "Failed to copy rootfs" - return 1 - fi + echo "Copy $cache/rootfs-$arch to $rootfs ... " + copy_opensuse $cache $arch $rootfs + if [ $? -ne 0 ]; then + echo "Failed to copy rootfs" + return 1 + fi - return 0 - - ) 200>/var/lock/subsys/lxc + return 0 + ) 200>@LOCALSTATEDIR@/lock/subsys/lxc-opensuse return $? } @@ -254,13 +253,13 @@ copy_configuration() rootfs=$2 name=$3 + grep -q "^lxc.rootfs" $path/config 2>/dev/null || echo "lxc.rootfs = $rootfs" >> $path/config cat <> $path/config lxc.utsname = $name lxc.tty = 4 lxc.pts = 1024 -lxc.rootfs = $rootfs -lxc.mount = $path/fstab +lxc.mount = $path/fstab # When using LXC with apparmor, uncomment the next line to run unconfined: #lxc.aa_profile = unconfined @@ -289,8 +288,8 @@ sysfs sys sysfs defaults 0 0 EOF if [ $? -ne 0 ]; then - echo "Failed to add configuration" - return 1 + echo "Failed to add configuration" + return 1 fi return 0 @@ -298,25 +297,24 @@ EOF clean() { - cache="/var/cache/lxc/opensuse" + cache="@LOCALSTATEDIR@/cache/lxc/opensuse" if [ ! -e $cache ]; then - exit 0 + exit 0 fi # lock, so we won't purge while someone is creating a repository ( - flock -x 200 - if [ $? != 0 ]; then - echo "Cache repository is busy." - exit 1 - fi + flock -x 200 + if [ $? != 0 ]; then + echo "Cache repository is busy." + exit 1 + fi - echo -n "Purging the download cache..." - rm --preserve-root --one-file-system -rf $cache && echo "Done." || exit 1 - exit 0 - - ) 200>/var/lock/subsys/lxc + echo -n "Purging the download cache..." + rm --preserve-root --one-file-system -rf $cache && echo "Done." || exit 1 + exit 0 + ) 200>@LOCALSTATEDIR@/lock/subsys/lxc-opensuse } usage() @@ -337,12 +335,12 @@ eval set -- "$options" while true do case "$1" in - -h|--help) usage $0 && exit 0;; - -p|--path) path=$2; shift 2;; - -n|--name) name=$2; shift 2;; - -c|--clean) clean=$2; shift 2;; - --) shift 1; break ;; - *) break ;; + -h|--help) usage $0 && exit 0;; + -p|--path) path=$2; shift 2;; + -n|--name) name=$2; shift 2;; + -c|--clean) clean=$2; shift 2;; + --) shift 1; break ;; + *) break ;; esac done @@ -367,7 +365,13 @@ if [ "$(id -u)" != "0" ]; then exit 1 fi -rootfs=$path/rootfs +# detect rootfs +config="$path/config" +if grep -q '^lxc.rootfs' $config 2>/dev/null ; then + rootfs=`grep 'lxc.rootfs =' $config | awk -F= '{ print $2 }'` +else + rootfs=$path/rootfs +fi install_opensuse $rootfs if [ $? -ne 0 ]; then diff --git a/templates/lxc-oracle.in b/templates/lxc-oracle.in new file mode 100644 index 000000000..f3252820a --- /dev/null +++ b/templates/lxc-oracle.in @@ -0,0 +1,557 @@ +#!/bin/bash +# +# Template script for generating Oracle Enterprise Linux container for LXC +# based on lxc-fedora, lxc-ubuntu +# +# Copyright © 2011 Wim Coekaerts +# Copyright © 2012 Dwight Engen +# +# Modified for Oracle Linux 5 +# Wim Coekaerts +# +# Modified for Oracle Linux 6, combined OL5,6 into one template script +# Dwight Engen +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# use virbr0 that is setup by default by libvirtd +lxc_network_type=veth +lxc_network_link=virbr0 + +die() +{ + echo "failed: $1" + exit 1 +} + +is_btrfs_subvolume() +{ + if which btrfs >/dev/null 2>&1 && \ + btrfs subvolume list "$1" >/dev/null 2>&1; then + return 0 + fi + return 1 +} + +# fix up the container_rootfs +container_rootfs_configure() +{ + echo "Configuring container for Oracle Linux $container_release_major" + + # "disable" selinux. init in OL 5 honors /etc/selinux/config. note that + # this doesnt actually disable it if it's enabled in the host, since + # libselinux::is_selinux_enabled() in the guest will check + # /proc/filesystems and see selinuxfs, thus reporting that it is on + # (ie. check the output of sestatus in the guest) + mkdir -p $container_rootfs/selinux + echo 0 > $container_rootfs/selinux/enforce + if [ -e $container_rootfs/etc/selinux/config ]; then + sed -i 's|SELINUX=enforcing|SELINUX=disabled|' $container_rootfs/etc/selinux/config + else + echo "SELINUX=disabled" >$container_rootfs/etc/selinux/config + fi + sed -i 's|session[ ]*required[ ]*pam_selinux.so[ ]*close|#session required pam_selinux.so close|' $container_rootfs/etc/pam.d/login + sed -i 's|session[ ]*required[ ]*pam_selinux.so[ ]*open|#session required pam_selinux.so open|' $container_rootfs/etc/pam.d/login + sed -i 's|session[ ]*required[ ]*pam_loginuid.so|#session required pam_loginuid.so|' $container_rootfs/etc/pam.d/login + + + # configure the network to use dhcp. we set DHCP_HOSTNAME so the guest + # will report its name and be resolv'able by the hosts dnsmasq + touch $container_rootfs/etc/resolv.conf + cat < $container_rootfs/etc/sysconfig/network-scripts/ifcfg-eth0 +DEVICE=eth0 +BOOTPROTO=dhcp +ONBOOT=yes +HOSTNAME=$name +DHCP_HOSTNAME=$name +NM_CONTROLLED=no +TYPE=Ethernet +EOF + + # set the hostname + cat < $container_rootfs/etc/sysconfig/network +NETWORKING=yes +NETWORKING_IPV6=no +HOSTNAME=$name +EOF + + # set minimal hosts + echo "127.0.0.1 localhost $name" > $container_rootfs/etc/hosts + + # disable ipv6 + echo "blacklist ipv6" >>$container_rootfs/etc/modprobe.d/blacklist.conf + echo "blacklist net-pf-10" >>$container_rootfs/etc/modprobe.d/blacklist.conf + rm -f $container_rootfs/etc/sysconfig/network-scripts/init.ipv6-global + + # this file has to exist for libvirt/Virtual machine monitor to boot the container + touch $container_rootfs/etc/mtab + + # don't put devpts in here, it will already be mounted for us by lxc/libvirt + cat < $container_rootfs/etc/fstab +proc /proc proc nodev,noexec,nosuid 0 0 +sysfs /sys sysfs defaults 0 0 +EOF + + # remove module stuff for iptables it just shows errors that are not + # relevant in a container + if [ -f "$container_rootfs/etc/sysconfig/iptables-config" ]; then + sed -i 's|IPTABLES_MODULES=".*|IPTABLES_MODULES=""|' $container_rootfs/etc/sysconfig/iptables-config + sed -i 's|IPTABLES_MODULES_UNLOAD=".*|IPTABLES_MODULES_UNLOAD="no"|' $container_rootfs/etc/sysconfig/iptables-config + fi + + # disable readahead in the container + if [ $container_release_major = "6" -a -e $container_rootfs/etc/sysconfig/readahead ]; then + rm -f $container_rootfs/etc/init/readahead-collector.conf + rm -f $container_rootfs/etc/init/readahead-disable-services.conf + sed -i 's|READAHEAD="yes"|READAHEAD="no"|' $container_rootfs/etc/sysconfig/readahead + fi + + # disable udev in the container + sed -i 's|.sbin.start_udev||' $container_rootfs/etc/rc.sysinit + sed -i 's|.sbin.start_udev||' $container_rootfs/etc/rc.d/rc.sysinit + + # disable nash raidautorun in the container since no /dev/md* + if [ $container_release_major = "5" ]; then + sed -i 's|echo "raidautorun /dev/md0"|echo ""|' $container_rootfs/etc/rc.sysinit + sed -i 's|echo "raidautorun /dev/md0"|echo ""|' $container_rootfs/etc/rc.d/rc.sysinit + fi + + # prevent rc.sysinit from attempting to loadkeys + if [ $container_release_major = "5" -a -e $container_rootfs/etc/sysconfig/keyboard ]; then + rm $container_rootfs/etc/sysconfig/keyboard + fi + + # dont try to sync the hwclock at shutdown + sed -i 's|\[ -x /sbin/hwclock|\[ 0 -eq 1|' $container_rootfs/etc/rc.d/init.d/halt + + # dont start lvm + sed -i 's|action $"Setting up Logical Volume Management:"|#action $"Setting up Logical Volume Management:"|' $container_rootfs/etc/rc.sysinit + sed -i 's|action $"Setting up Logical Volume Management:"|/bin/true #action $"Setting up Logical Volume Management:"|' $container_rootfs/etc/rc.d/rc.sysinit + + # fix assumptions that plymouth is available + sed -i 's|\[ "$PROMPT" != no \] && plymouth|[ "$PROMPT" != no ] \&\& [ -n "$PLYMOUTH" ] \&\& plymouth|' $container_rootfs/etc/rc.sysinit + sed -i 's|\[ "$PROMPT" != no \] && plymouth|[ "$PROMPT" != no ] \&\& [ -n "$PLYMOUTH" ] \&\& plymouth|' $container_rootfs/etc/rc.d/rc.sysinit + rm -f $container_rootfs/etc/init/plymouth-shutdown.conf + rm -f $container_rootfs/etc/init/quit-plymouth.conf + rm -f $container_rootfs/etc/init/splash-manager.conf + + # setup console and tty[1-4] for login. note that /dev/console and + # /dev/tty[1-4] will be symlinks to the ptys /dev/lxc/console and + # /dev/lxc/tty[1-4] so that package updates can overwrite the symlinks. + # lxc will maintain these links and bind mount ptys over /dev/lxc/* + # since lxc.devttydir is specified in the config. + + # allow root login on console, tty[1-4], and pts/0 for libvirt + echo "# LXC (Linux Containers)" >>$container_rootfs/etc/securetty + echo "lxc/console" >>$container_rootfs/etc/securetty + echo "lxc/tty1" >>$container_rootfs/etc/securetty + echo "lxc/tty2" >>$container_rootfs/etc/securetty + echo "lxc/tty3" >>$container_rootfs/etc/securetty + echo "lxc/tty4" >>$container_rootfs/etc/securetty + echo "# For libvirt/Virtual Machine Monitor" >>$container_rootfs/etc/securetty + echo "pts/0" >>$container_rootfs/etc/securetty + + # dont try to unmount /dev/lxc devices + sed -i 's|&& $1 !~ /^\\/dev\\/ram/|\&\& $2 !~ /^\\/dev\\/lxc/ \&\& $1 !~ /^\\/dev\\/ram/|' $container_rootfs/etc/init.d/halt + + # start a getty on /dev/console, /dev/tty[1-4] + if [ $container_release_major = "5" ]; then + sed -i '/1:2345:respawn/i cns:2345:respawn:/sbin/mingetty console' $container_rootfs/etc/inittab + sed -i '/5:2345:respawn/d' $container_rootfs/etc/inittab + sed -i '/6:2345:respawn/d' $container_rootfs/etc/inittab + fi + + if [ $container_release_major = "6" ]; then + cat < $container_rootfs/etc/init/console.conf +# console - getty +# +# This service maintains a getty on the console from the point the system is +# started until it is shut down again. + +start on stopped rc RUNLEVEL=[2345] +stop on runlevel [!2345] + +respawn +exec /sbin/mingetty /dev/console +EOF + fi + + # there might be other services that are useless but the below set is a good start + # some of these might not exist in the image, so we silence chkconfig complaining + # about the service file not being found + for service in \ + acpid auditd autofs cpuspeed dund gpm haldaemon hidd \ + ip6tables irqbalance iscsi iscsid isdn kdump kudzu \ + lm_sensors lvm2-monitor mdmonitor microcode_ctl \ + ntpd postfix sendmail udev-post ; + do + chroot $container_rootfs chkconfig 2>/dev/null $service off + done + + for service in rsyslog ; + do + chroot $container_rootfs chkconfig 2>/dev/null $service on + done + + # create required devices. note that /dev/console will be created by lxc + # or libvirt itself to be a symlink to the right pty. + # take care to not nuke /dev in case $container_rootfs isn't set + dev_path="$container_rootfs/dev" + if [ $container_rootfs != "/" -a -d $dev_path ]; then + rm -rf $dev_path + mkdir -p $dev_path + fi + mknod -m 666 $dev_path/null c 1 3 + mknod -m 666 $dev_path/zero c 1 5 + mknod -m 666 $dev_path/random c 1 8 + mknod -m 666 $dev_path/urandom c 1 9 + mkdir -m 755 $dev_path/pts + mkdir -m 1777 $dev_path/shm + mknod -m 666 $dev_path/tty c 5 0 + mknod -m 666 $dev_path/tty0 c 4 0 + mknod -m 666 $dev_path/tty1 c 4 1 + mknod -m 666 $dev_path/tty2 c 4 2 + mknod -m 666 $dev_path/tty3 c 4 3 + mknod -m 666 $dev_path/tty4 c 4 4 + mknod -m 666 $dev_path/full c 1 7 + mknod -m 600 $dev_path/initctl p + + # ensure /dev/ptmx refers to the newinstance devpts of the container, or + # pty's will get crossed up with the hosts (https://lkml.org/lkml/2012/1/23/512) + rm -f $container_rootfs/dev/ptmx + ln -s pts/ptmx $container_rootfs/dev/ptmx + + # start with a clean /var/log/messages + rm -f $container_rootfs/var/log/messages + + # add oracle user, set root password + chroot $container_rootfs useradd --create-home -s /bin/bash oracle + echo "oracle:oracle" | chroot $container_rootfs chpasswd + echo "root:root" | chroot $container_rootfs chpasswd + echo -e "Added container user:\033[1moracle\033[0m password:\033[1moracle\033[0m" + echo -e "Added container user:\033[1mroot\033[0m password:\033[1mroot\033[0m" +} + +# create the container's lxc config file +container_config_create() +{ + echo "Create configuration file $cfg_dir/config" + # generate a hwaddr for the container with a high mac address + # see http://sourceforge.net/tracker/?func=detail&aid=3411497&group_id=163076&atid=826303 + local hwaddr="fe:`dd if=/dev/urandom bs=8 count=1 2>/dev/null |od -t x8 | \ + head -1 |awk '{print $2}' | cut -c1-10 |\ + sed 's/\(..\)/\1:/g; s/.$//'`" + mkdir -p $cfg_dir || die "unable to create config dir $cfg_dir" + cat <> $cfg_dir/config || die "unable to create $cfg_dir/config" +# Container configuration for Oracle Linux $release_major.$release_minor +lxc.arch = $arch +lxc.utsname = $name +lxc.devttydir = lxc +lxc.tty = 4 +lxc.pts = 1024 +lxc.rootfs = $container_rootfs +lxc.mount = $cfg_dir/fstab +# Networking +EOF + + # see if the network settings were already specified + lxc_network_type=`grep '^lxc.network.type' $cfg_dir/config | awk -F'[= \t]+' '{ print $2 }'` + if [ -z "$lxc_network_type" -a \ + \( $host_distribution = "OracleServer" -o \ + $host_distribution = "Fedora" \) ]; then + echo "lxc.network.type = veth" >>$cfg_dir/config + echo "lxc.network.flags = up" >>$cfg_dir/config + echo "lxc.network.link = virbr0" >>$cfg_dir/config + fi + + cat <> $cfg_dir/config || die "unable to create $cfg_dir/config" +lxc.network.name = eth0 +lxc.network.mtu = 1500 +lxc.network.hwaddr = $hwaddr +# Control Group devices: all denied except those whitelisted +lxc.cgroup.devices.deny = a +lxc.cgroup.devices.allow = c 1:3 rwm # /dev/null +lxc.cgroup.devices.allow = c 1:5 rwm # /dev/zero +lxc.cgroup.devices.allow = c 1:7 rwm # /dev/full +lxc.cgroup.devices.allow = c 5:0 rwm # /dev/tty +lxc.cgroup.devices.allow = c 1:8 rwm # /dev/random +lxc.cgroup.devices.allow = c 1:9 rwm # /dev/urandom +lxc.cgroup.devices.allow = c 136:* rwm # /dev/tty[1-4] ptys and lxc console +lxc.cgroup.devices.allow = c 5:2 rwm # /dev/ptmx pty master +lxc.cgroup.devices.allow = c 254:0 rwm # /dev/rtc0 +EOF + + cat < $cfg_dir/fstab || die "unable to create $cfg_dir/fstab" +proc $container_rootfs/proc proc nodev,noexec,nosuid 0 0 +devpts $container_rootfs/dev/pts devpts defaults 0 0 +sysfs $container_rootfs/sys sysfs defaults 0 0 +EOF +} + +container_rootfs_clone() +{ + if is_btrfs_subvolume $template_rootfs; then + # lxc-create already made $container_rootfs a btrfs subvolume, but + # in this case we want to snapshot the original subvolume so we we + # have to delete the one that lxc-create made + btrfs subvolume delete $container_rootfs + btrfs subvolume snapshot $template_rootfs $container_rootfs || die "btrfs clone template" + else + cp -ax $template_rootfs $container_rootfs || die "copy template" + fi +} + +container_rootfs_create() +{ + cmds="rpm wget yum" + if [ $release_major = "5" ]; then + if [ $host_distribution = "Ubuntu" ]; then + db_dump_cmd="db5.1_dump" + db_load_cmd="db4.3_load" + fi + if [ $host_distribution = "OracleServer" -o \ + $host_distribution = "Fedora" ]; then + db_dump_cmd="db_dump" + db_load_cmd="db43_load" + fi + + cmds="$cmds $db_dump_cmd $db_load_cmd file" + fi + for cmd in $cmds; do + which $cmd >/dev/null 2>&1 + if [ $? -ne 0 ]; then + die "The $cmd command is required, please install it" + fi + done + + mkdir -p @LOCALSTATEDIR@/lock/subsys/lxc + ( + flock -x 200 + if [ $? -ne 0 ]; then + die "The template is busy." + fi + + echo "Downloading release $release_major.$release_minor for $basearch" + + # get yum repo file + public_yum_url=http://public-yum.oracle.com + if [ $release_major = "5" ]; then + repofile=public-yum-el5.repo + elif [ $release_major = "6" ]; then + repofile=public-yum-ol6.repo + else + die "Unsupported release $release_major" + fi + mkdir -p $container_rootfs/etc/yum.repos.d + wget -q $public_yum_url/$repofile -O $container_rootfs/etc/yum.repos.d/$repofile + if [ $? -ne 0 ]; then + die "Failed to download repo file $public_yum_url/$repofile" + fi + + # yum will take $basearch from host, so force the arch we want + sed -i "s|\$basearch|$basearch|" $container_rootfs/etc/yum.repos.d/$repofile + + # replace url if they specified one + if [ -n "$repourl" ]; then + sed -i "s|baseurl=http://public-yum.oracle.com/repo|baseurl=$repourl/repo|" $container_rootfs/etc/yum.repos.d/$repofile + sed -i "s|gpgkey=http://public-yum.oracle.com|gpgkey=$repourl|" $container_rootfs/etc/yum.repos.d/$repofile + fi + + # disable all repos, then enable the repo for the version we are installing. + if [ $release_minor = "latest" ]; then + if [ $release_major = "5" ]; then + repo="el"$release_major"_"$release_minor + else + repo="ol"$release_major"_"$release_minor + fi + elif [ $release_minor = "0" ]; then + repo="ol"$release_major"_ga_base" + else + repo="ol"$release_major"_u"$release_minor"_base" + fi + sed -i "s|enabled=1|enabled=0|" $container_rootfs/etc/yum.repos.d/$repofile + sed -i "/\[$repo\]/,/\[/ s/enabled=0/enabled=1/" $container_rootfs/etc/yum.repos.d/$repofile + + # create rpm db, download and yum install minimal packages + mkdir -p $container_rootfs/var/lib/rpm + rpm --root $container_rootfs --initdb + yum_cmd="yum --installroot $container_rootfs --disablerepo=* --enablerepo=$repo -y --nogpgcheck" + min_pkgs="yum initscripts passwd rsyslog vim-minimal openssh-server dhclient chkconfig rootfiles policycoreutils oraclelinux-release" + + $yum_cmd install $min_pkgs + if [ $? -ne 0 ]; then + die "Failed to download and install the rootfs, aborting." + fi + + # rsyslog and pam depend on coreutils for some common commands in + # their POSTIN scriptlets, but coreutils wasn't installed yet. now + # that coreutils is installed, reinstall the packages so their POSTIN + # runs right. similarly, libutempter depends on libselinux.so.1 when + # it runs /usr/sbin/groupadd, so reinstall it too + if [ $release_major = "5" ]; then + rpm --root $container_rootfs --nodeps -e rsyslog pam libutempter + $yum_cmd install rsyslog pam libutempter + if [ $? -ne 0 ]; then + die "Unable to reinstall packages" + fi + fi + + # these distributions put the rpm database in a place the guest is + # not expecting it, so move it + if [ $host_distribution = "Ubuntu" ]; then + mv $container_rootfs/root/.rpmdb/* $container_rootfs/var/lib/rpm + fi + + # if the native rpm created the db with Hash version 9, we need to + # downgrade it to Hash version 8 for use with OL5.x + db_version=`file $container_rootfs/var/lib/rpm/Packages | \ + grep -o 'version [0-9]*' |awk '{print $2}'` + if [ $release_major = "5" -a $db_version != "8" ]; then + echo "Fixing (downgrading) rpm database from version $db_version" + rm -f $container_rootfs/var/lib/rpm/__db* + for db in $container_rootfs/var/lib/rpm/* ; do + $db_dump_cmd $db |$db_load_cmd $db.new + mv $db.new $db + done + fi + + # the host rpm may not be the same as the guest, rebuild the db with + # the guest rpm version + echo "Rebuilding rpm database" + rm -f $container_rootfs/var/lib/rpm/__db* + chroot $container_rootfs rpm --rebuilddb >/dev/null 2>&1 + ) 200>@LOCALSTATEDIR@/lock/subsys/lxc-oracle-$name +} + +usage() +{ + cat < architecture (ie. i386, x86_64) + -R|--release= release to download for the new container + -u|--url= replace yum repo url (ie. local yum mirror) + -t|--templatefs= copy/clone rootfs at path instead of downloading + -h|--help + +Release is of the format "major.minor", for example "5.8", "6.3", or "6.latest" +EOF + return 0 +} + +options=$(getopt -o hp:n:a:R:u:t: -l help,path:,name:,arch:,release:,url:,templatefs: -- "$@") +if [ $? -ne 0 ]; then + usage $(basename $0) + exit 1 +fi + +arch=$(arch) +eval set -- "$options" +while true +do + case "$1" in + -h|--help) usage $0 && exit 0;; + -p|--path) cfg_dir=$2; shift 2;; + -n|--name) name=$2; shift 2;; + -a|--arch) arch=$2; shift 2;; + -R|--release) release_version=$2; shift 2;; + -u|--url) repourl=$2; shift;; + -t|--templatefs) template_rootfs=$2; shift 2;; + --) shift 1; break ;; + *) break ;; + esac +done + +# make sure mandatory args are given and valid +if [ "$(id -u)" != "0" ]; then + echo "This script should be run as 'root'" + exit 1 +fi + +if [ -z $name ]; then + echo "Container name must be given" + usage + exit 1 +fi + +if [ -z $cfg_dir ]; then + echo "Configuration directory must be given, check lxc-create" + usage + exit 1 +fi + +basearch=$arch +if [ "$arch" = "i686" ]; then + basearch="i386" +fi + +if [ "$arch" != "i386" -a "$arch" != "x86_64" ]; then + echo "Bad architecture given, check lxc-create" + usage + exit 1 +fi + +container_rootfs="$cfg_dir/rootfs" + +if [ -n "$template_rootfs" ]; then + release_version=`cat $template_rootfs/etc/oracle-release |awk '/^Oracle/ {print $5}'` +fi +if [ -z "$release_version" ]; then + echo "No release specified with -R, defaulting to 6.3" + release_version="6.3" +fi +release_major=`echo $release_version |awk -F '.' '{print $1}'` +release_minor=`echo $release_version |awk -F '.' '{print $2}'` + +if which lsb_release >/dev/null 2>&1; then + host_distribution=`lsb_release --id |awk '{print $3}'` + host_release_version=`lsb_release --release |awk '{print $2}'` + host_release_major=`echo $host_release_version |awk -F '.' '{print $1}'` + host_release_minor=`echo $host_release_version |awk -F '.' '{print $2}'` +else + if [ -f /etc/fedora-release ]; then + host_distribution="Fedora" + host_release_version=`cat /etc/fedora-release |awk '{print $3}'` + host_release_major=$host_release_version + host_release_minor=0 + elif [ -f /etc/oracle-release ]; then + host_distribution="OracleServer" + host_release_version=`cat /etc/oracle-release |awk '{print $5}'` + host_release_major=`echo $host_release_version |awk -F '.' '{print $1}'` + host_release_minor=`echo $host_release_version |awk -F '.' '{print $2}'` + else + echo "Unable to determine host distribution, ensure lsb_release is installed" + exit 1 + fi +fi +echo "Host is $host_distribution $host_release_version" + +trap cleanup SIGHUP SIGINT SIGTERM + +container_config_create +if [ -n "$template_rootfs" ]; then + container_rootfs_clone +else + container_rootfs_create +fi + +container_release_version=`cat $container_rootfs/etc/oracle-release |awk '/^Oracle/ {print $5}'` +container_release_major=`echo $container_release_version |awk -F '.' '{print $1}'` +container_release_minor=`echo $container_release_version |awk -F '.' '{print $2}'` + +container_rootfs_configure + +echo "Container : $container_rootfs" +echo "Config : $cfg_dir/config" +echo "Network : eth0 ($lxc_network_type) on $lxc_network_link" diff --git a/templates/lxc-sshd.in b/templates/lxc-sshd.in index d456fbbb8..b704723b4 100644 --- a/templates/lxc-sshd.in +++ b/templates/lxc-sshd.in @@ -43,7 +43,7 @@ $rootfs/lib64" mkdir -p $tree if [ $? -ne 0 ]; then - return 1 + return 1 fi return 0 @@ -96,7 +96,7 @@ EOF cp $auth_key "$root_u_path/authorized_keys" chown -R 0:0 "$rootfs/$u_path" chmod 700 "$rootfs/$u_path" - echo "Inserted SSH public key from $auth_key into /home/ubuntu/.ssh/authorized_keys" + echo "Inserted SSH public key from $auth_key into $rootfs/$u_path" fi return 0 @@ -108,22 +108,22 @@ copy_configuration() rootfs=$2 name=$3 + grep -q "^lxc.rootfs" $path/config 2>/dev/null || echo "lxc.rootfs = $rootfs" >> $path/config cat <> $path/config lxc.utsname = $name lxc.pts = 1024 -lxc.rootfs = $rootfs # When using LXC with apparmor, uncomment the next line to run unconfined: #lxc.aa_profile = unconfined -lxc.mount.entry=/dev dev none ro,bind 0 0 -lxc.mount.entry=/lib lib none ro,bind 0 0 -lxc.mount.entry=/bin bin none ro,bind 0 0 -lxc.mount.entry=/usr usr none ro,bind 0 0 -lxc.mount.entry=/sbin sbin none ro,bind 0 0 -lxc.mount.entry=tmpfs var/run/sshd tmpfs mode=0644 0 0 -lxc.mount.entry=@LXCTEMPLATEDIR@/lxc-sshd sbin/init none bind 0 0 -lxc.mount.entry=proc $rootfs/proc proc nodev,noexec,nosuid 0 0 +lxc.mount.entry = /dev dev none ro,bind 0 0 +lxc.mount.entry = /lib lib none ro,bind 0 0 +lxc.mount.entry = /bin bin none ro,bind 0 0 +lxc.mount.entry = /usr usr none ro,bind 0 0 +lxc.mount.entry = /sbin sbin none ro,bind 0 0 +lxc.mount.entry = tmpfs var/run/sshd tmpfs mode=0644 0 0 +lxc.mount.entry = @LXCTEMPLATEDIR@/lxc-sshd sbin/init none bind 0 0 +lxc.mount.entry = proc $rootfs/proc proc nodev,noexec,nosuid 0 0 EOF # if no .ipv4 section in config, then have the container run dhcp @@ -131,7 +131,7 @@ EOF if [ "$(uname -m)" = "x86_64" ]; then cat <> $path/config -lxc.mount.entry=/lib64 lib64 none ro,bind 0 0 +lxc.mount.entry = /lib64 lib64 none ro,bind 0 0 EOF fi } @@ -172,14 +172,14 @@ if [ $0 == "/sbin/init" ]; then type @LXCINITDIR@/lxc-init if [ $? -ne 0 ]; then - echo "'lxc-init is not accessible on the system" - exit 1 + echo "'lxc-init is not accessible on the system" + exit 1 fi type sshd if [ $? -ne 0 ]; then - echo "'sshd' is not accessible on the system " - exit 1 + echo "'sshd' is not accessible on the system " + exit 1 fi # run dhcp? @@ -207,7 +207,13 @@ if [ -z "$path" ]; then exit 1 fi -rootfs=$path/rootfs +# detect rootfs +config="$path/config" +if grep -q '^lxc.rootfs' $config 2>/dev/null ; then + rootfs=`grep 'lxc.rootfs =' $config | awk -F= '{ print $2 }'` +else + rootfs=$path/rootfs +fi install_sshd $rootfs if [ $? -ne 0 ]; then diff --git a/templates/lxc-ubuntu-cloud.in b/templates/lxc-ubuntu-cloud.in index 349830999..de9f3c31d 100644 --- a/templates/lxc-ubuntu-cloud.in +++ b/templates/lxc-ubuntu-cloud.in @@ -46,13 +46,13 @@ lxc.network.hwaddr = 00:16:3e:$(openssl rand -hex 3| sed 's/\(..\)/\1:/g; s/.$// EOF fi + grep -q "^lxc.rootfs" $path/config 2>/dev/null || echo "lxc.rootfs = $rootfs" >> $path/config cat <> $path/config lxc.utsname = $name lxc.tty = 4 lxc.pts = 1024 -lxc.rootfs = $rootfs -lxc.mount = $path/fstab +lxc.mount = $path/fstab lxc.arch = $arch lxc.cap.drop = sys_module mac_admin lxc.pivotdir = lxc_putold @@ -131,7 +131,7 @@ EOF return 0 } -options=$(getopt -o a:hp:r:n:Fi:CLS:T:ds: -l arch:,help,path:,release:,name:,flush-cache,hostid:,auth-key:,cloud,no_locales,tarball:,debug,stream:,userdata: -- "$@") +options=$(getopt -o a:hp:r:n:Fi:CLS:T:ds:u: -l arch:,help,path:,release:,name:,flush-cache,hostid:,auth-key:,cloud,no_locales,tarball:,debug,stream:,userdata: -- "$@") if [ $? -ne 0 ]; then usage $(basename $0) exit 1 @@ -189,7 +189,7 @@ do -u|--userdata) userdata=$2; shift 2;; -C|--cloud) cloud=1; shift 1;; -S|--auth-key) auth_key=$2; shift 2;; - -L|--no_locales) locales=0; shift 2;; + -L|--no_locales) locales=0; shift 1;; -T|--tarball) tarball=$2; shift 2;; -d|--debug) debug=1; shift 1;; -s|--stream) stream=$2; shift 2;; @@ -249,14 +249,20 @@ if [ "$(id -u)" != "0" ]; then exit 1 fi -rootfs=$path/rootfs +# detect rootfs +config="$path/config" +if grep -q '^lxc.rootfs' $config 2>/dev/null ; then + rootfs=`grep 'lxc.rootfs =' $config | awk -F= '{ print $2 }'` +else + rootfs=$path/rootfs +fi type ubuntu-cloudimg-query type wget # determine the url, tarball, and directory names # download if needed -cache="/var/cache/lxc/cloud-$release" +cache="@LOCALSTATEDIR@/cache/lxc/cloud-$release" mkdir -p $cache @@ -312,7 +318,7 @@ build_root_tgz() trap SIGTERM } -mkdir -p /var/lock/subsys/ +mkdir -p @LOCALSTATEDIR@/lock/subsys/ ( flock -x 200 @@ -389,7 +395,7 @@ EOF echo "If you do not have a meta-data service, this container will likely be useless." fi -) 200>/var/lock/subsys/lxc-ubucloud +) 200>@LOCALSTATEDIR@/lock/subsys/lxc-ubuntu-cloud copy_configuration $path $rootfs $name $arch $release diff --git a/templates/lxc-ubuntu.in b/templates/lxc-ubuntu.in index aca211843..0ed8808c7 100644 --- a/templates/lxc-ubuntu.in +++ b/templates/lxc-ubuntu.in @@ -80,6 +80,15 @@ EOF echo "ubuntu:ubuntu" | chroot $rootfs chpasswd fi + # make sure we have the current locale defined in the container + if [ -z "$LANG" ]; then + chroot $rootfs locale-gen en_US.UTF-8 + chroot $rootfs update-locale LANG=en_US.UTF-8 + else + chroot $rootfs locale-gen $LANG + chroot $rootfs update-locale LANG=$LANG + fi + return 0 } @@ -104,13 +113,13 @@ finalize_user() done if [ -n "$auth_key" -a -f "$auth_key" ]; then - u_path="/home/${user}/.ssh" - root_u_path="$rootfs/$u_path" - mkdir -p $root_u_path - cp $auth_key "$root_u_path/authorized_keys" - chroot $rootfs chown -R ${user}: "$u_path" + u_path="/home/${user}/.ssh" + root_u_path="$rootfs/$u_path" + mkdir -p $root_u_path + cp $auth_key "$root_u_path/authorized_keys" + chroot $rootfs chown -R ${user}: "$u_path" - echo "Inserted SSH public key from $auth_key into /home/${user}/.ssh/authorized_keys" + echo "Inserted SSH public key from $auth_key into /home/${user}/.ssh/authorized_keys" fi return 0 } @@ -165,6 +174,18 @@ download_ubuntu() release=$3 packages=vim,ssh + + # Try to guess a list of langpacks to install + langpacks="language-pack-en" + + if which dpkg >/dev/null 2>&1; then + langpacks=`(echo $langpacks && + dpkg -l | grep -E "^ii language-pack-[a-z]* " | + cut -d ' ' -f3) | sort -u` + fi + packages="$packages,$(echo $langpacks | sed 's/ /,/g')" + + echo "installing packages: $packages" trap cleanup EXIT SIGHUP SIGINT SIGTERM @@ -228,7 +249,7 @@ copy_ubuntu() # make a local copy of the miniubuntu echo "Copying rootfs to $rootfs ..." mkdir -p $rootfs - rsync -a $cache/rootfs-$arch/ $rootfs/ || return 1 + rsync -Ha $cache/rootfs-$arch/ $rootfs/ || return 1 return 0 } @@ -237,8 +258,8 @@ install_ubuntu() rootfs=$1 release=$2 flushcache=$3 - cache="/var/cache/lxc/$release" - mkdir -p /var/lock/subsys/ + cache="@LOCALSTATEDIR@/cache/lxc/$release" + mkdir -p @LOCALSTATEDIR@/lock/subsys/ ( flock -x 200 @@ -272,7 +293,7 @@ install_ubuntu() return 0 - ) 200>/var/lock/subsys/lxc + ) 200>@LOCALSTATEDIR@/lock/subsys/lxc-ubuntu return $? } @@ -303,14 +324,14 @@ lxc.network.hwaddr = 00:16:3e:$(openssl rand -hex 3| sed 's/\(..\)/\1:/g; s/.$// EOF fi + grep -q "^lxc.rootfs" $path/config 2>/dev/null || echo "lxc.rootfs = $rootfs" >> $path/config cat <> $path/config lxc.utsname = $name lxc.devttydir =$ttydir lxc.tty = 4 lxc.pts = 1024 -lxc.rootfs = $rootfs -lxc.mount = $path/fstab +lxc.mount = $path/fstab lxc.arch = $arch lxc.cap.drop = sys_module mac_admin mac_override lxc.pivotdir = lxc_putold @@ -431,15 +452,6 @@ EOF # /lib/init/fstab: cleared out for bare-bones lxc EOF - # reconfigure some services - if [ -z "$LANG" ]; then - chroot $rootfs locale-gen en_US.UTF-8 - chroot $rootfs update-locale LANG=en_US.UTF-8 - else - chroot $rootfs locale-gen $LANG - chroot $rootfs update-locale LANG=$LANG - fi - # remove pointless services in a container chroot $rootfs /usr/sbin/update-rc.d -f ondemand remove @@ -670,7 +682,13 @@ if [ "$(id -u)" != "0" ]; then exit 1 fi -rootfs=$path/rootfs +# detect rootfs +config="$path/config" +if grep -q '^lxc.rootfs' $config 2>/dev/null ; then + rootfs=`grep 'lxc.rootfs =' $config | awk -F= '{ print $2 }'` +else + rootfs=$path/rootfs +fi install_ubuntu $rootfs $release $flushcache if [ $? -ne 0 ]; then @@ -702,10 +720,10 @@ fi echo "" echo "##" if [ -n "$bindhome" ]; then - echo "# Log in as user $bindhome" + echo "# Log in as user $bindhome" else - echo "# The default user is 'ubuntu' with password 'ubuntu'!" - echo "# Use the 'sudo' command to run tasks as root in the container." + echo "# The default user is 'ubuntu' with password 'ubuntu'!" + echo "# Use the 'sudo' command to run tasks as root in the container." fi echo "##" echo ""