summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am11
-rw-r--r--src/Makefile.in616
-rw-r--r--src/api.c2320
-rw-r--r--src/config.c496
-rw-r--r--src/daemon/Makefile.am6
-rw-r--r--src/daemon/Makefile.in468
-rw-r--r--src/daemon/cgrulesengd.c793
-rw-r--r--src/daemon/cgrulesengd.h127
-rw-r--r--src/lex.l34
-rw-r--r--src/libcgroup-internal.h105
-rw-r--r--src/libcgroup.map56
-rw-r--r--src/pam/Makefile.am6
-rw-r--r--src/pam/Makefile.in472
-rw-r--r--src/pam/pam_cgroup.c162
-rw-r--r--src/parse.y269
-rw-r--r--src/tools/Makefile.am11
-rw-r--r--src/tools/Makefile.in486
-rw-r--r--src/tools/cgclassify.c219
-rw-r--r--src/tools/cgconfig.c74
-rw-r--r--src/tools/cgexec.c122
-rw-r--r--src/tools/tools-common.c88
-rw-r--r--src/tools/tools-common.h54
-rw-r--r--src/wrapper.c557
23 files changed, 7552 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..c30393a
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,11 @@
+SUBDIRS = . daemon pam tools
+
+# generate parse.h from parse.y
+AM_YFLAGS = -d
+
+INCLUDES = -I$(top_srcdir)/include
+lib_LTLIBRARIES = libcgroup.la
+libcgroup_la_SOURCES = parse.y lex.l api.c config.c libcgroup-internal.h libcgroup.map wrapper.c
+libcgroup_la_LIBADD = -lpthread
+libcgroup_la_LDFLAGS = -Wl,--version-script,libcgroup.map -version-number $(LIBRARY_VERSION)
+
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..01fa4c6
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,616 @@
+# Makefile.in generated by automake 1.10.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in lex.c \
+ parse.c parse.h
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = `echo $$p | sed -e 's|^.*/||'`;
+am__installdirs = "$(DESTDIR)$(libdir)"
+libLTLIBRARIES_INSTALL = $(INSTALL)
+LTLIBRARIES = $(lib_LTLIBRARIES)
+libcgroup_la_DEPENDENCIES =
+am_libcgroup_la_OBJECTS = parse.lo lex.lo api.lo config.lo wrapper.lo
+libcgroup_la_OBJECTS = $(am_libcgroup_la_OBJECTS)
+libcgroup_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(libcgroup_la_LDFLAGS) $(LDFLAGS) -o $@
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
+ $(LDFLAGS) -o $@
+LEXCOMPILE = $(LEX) $(LFLAGS) $(AM_LFLAGS)
+LTLEXCOMPILE = $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=compile $(LEX) $(LFLAGS) $(AM_LFLAGS)
+YLWRAP = $(top_srcdir)/ylwrap
+YACCCOMPILE = $(YACC) $(YFLAGS) $(AM_YFLAGS)
+LTYACCCOMPILE = $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=compile $(YACC) $(YFLAGS) $(AM_YFLAGS)
+SOURCES = $(libcgroup_la_SOURCES)
+DIST_SOURCES = $(libcgroup_la_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
+ html-recursive info-recursive install-data-recursive \
+ install-dvi-recursive install-exec-recursive \
+ install-html-recursive install-info-recursive \
+ install-pdf-recursive install-ps-recursive install-recursive \
+ installcheck-recursive installdirs-recursive pdf-recursive \
+ ps-recursive uninstall-recursive
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO = @ECHO@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+F77 = @F77@
+FFLAGS = @FFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBRARY_VERSION = @LIBRARY_VERSION@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+YACC = @YACC@
+YFLAGS = @YFLAGS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_F77 = @ac_ct_F77@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = . daemon pam tools
+
+# generate parse.h from parse.y
+AM_YFLAGS = -d
+INCLUDES = -I$(top_srcdir)/include
+lib_LTLIBRARIES = libcgroup.la
+libcgroup_la_SOURCES = parse.y lex.l api.c config.c libcgroup-internal.h libcgroup.map wrapper.c
+libcgroup_la_LIBADD = -lpthread
+libcgroup_la_LDFLAGS = -Wl,--version-script,libcgroup.map -version-number $(LIBRARY_VERSION)
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .l .lo .o .obj .y
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \
+ && exit 0; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \
+ cd $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ test -z "$(libdir)" || $(MKDIR_P) "$(DESTDIR)$(libdir)"
+ @list='$(lib_LTLIBRARIES)'; for p in $$list; do \
+ if test -f $$p; then \
+ f=$(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(libLTLIBRARIES_INSTALL) $(INSTALL_STRIP_FLAG) '$$p' '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(libLTLIBRARIES_INSTALL) $(INSTALL_STRIP_FLAG) "$$p" "$(DESTDIR)$(libdir)/$$f"; \
+ else :; fi; \
+ done
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; for p in $$list; do \
+ p=$(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$p'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$p"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; for p in $$list; do \
+ dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \
+ test "$$dir" != "$$p" || dir=.; \
+ echo "rm -f \"$${dir}/so_locations\""; \
+ rm -f "$${dir}/so_locations"; \
+ done
+parse.h: parse.c
+ @if test ! -f $@; then \
+ rm -f parse.c; \
+ $(MAKE) $(AM_MAKEFLAGS) parse.c; \
+ else :; fi
+libcgroup.la: $(libcgroup_la_OBJECTS) $(libcgroup_la_DEPENDENCIES)
+ $(libcgroup_la_LINK) -rpath $(libdir) $(libcgroup_la_OBJECTS) $(libcgroup_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/api.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lex.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/wrapper.Plo@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $<
+
+.l.c:
+ $(am__skiplex) $(SHELL) $(YLWRAP) $< $(LEX_OUTPUT_ROOT).c $@ -- $(LEXCOMPILE)
+
+.y.c:
+ $(am__skipyacc) $(SHELL) $(YLWRAP) $< y.tab.c $@ y.tab.h $*.h y.output $*.output -- $(YACCCOMPILE)
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run `make' without going through this Makefile.
+# To change the values of `make' variables: instead of editing Makefiles,
+# (1) if the variable is set in `config.status', edit `config.status'
+# (which will cause the Makefiles to be regenerated when you run `make');
+# (2) otherwise, pass the desired values on the `make' command line.
+$(RECURSIVE_TARGETS):
+ @failcom='exit 1'; \
+ for f in x $$MAKEFLAGS; do \
+ case $$f in \
+ *=* | --[!k]*);; \
+ *k*) failcom='fail=yes';; \
+ esac; \
+ done; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+$(RECURSIVE_CLEAN_TARGETS):
+ @failcom='exit 1'; \
+ for f in x $$MAKEFLAGS; do \
+ case $$f in \
+ *=* | --[!k]*);; \
+ *k*) failcom='fail=yes';; \
+ esac; \
+ done; \
+ dot_seen=no; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ rev=''; for subdir in $$list; do \
+ if test "$$subdir" = "."; then :; else \
+ rev="$$subdir $$rev"; \
+ fi; \
+ done; \
+ rev="$$rev ."; \
+ target=`echo $@ | sed s/-recursive//`; \
+ for subdir in $$rev; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done && test -z "$$fail"
+tags-recursive:
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \
+ done
+ctags-recursive:
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \
+ done
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonemtpy = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: tags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ tags="$$tags $$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$tags $$unique; \
+ fi
+ctags: CTAGS
+CTAGS: ctags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ test -z "$(CTAGS_ARGS)$$tags$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$tags $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && cd $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) $$here
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
+ fi; \
+ cp -pR $$d/$$file $(distdir)$$dir || exit 1; \
+ else \
+ test -f $(distdir)/$$file \
+ || cp -p $$d/$$file $(distdir)/$$file \
+ || exit 1; \
+ fi; \
+ done
+ list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ distdir=`$(am__cd) $(distdir) && pwd`; \
+ top_distdir=`$(am__cd) $(top_distdir) && pwd`; \
+ (cd $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$top_distdir" \
+ distdir="$$distdir/$$subdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+ -rm -f lex.c
+ -rm -f parse.c
+ -rm -f parse.h
+clean: clean-recursive
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-recursive
+
+install-info: install-info-recursive
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-ps: install-ps-recursive
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-libLTLIBRARIES
+
+.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) install-am \
+ install-strip
+
+.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \
+ all all-am check check-am clean clean-generic \
+ clean-libLTLIBRARIES clean-libtool ctags ctags-recursive \
+ distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-libLTLIBRARIES install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-recursive uninstall uninstall-am \
+ uninstall-libLTLIBRARIES
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/api.c b/src/api.c
new file mode 100644
index 0000000..28365b4
--- /dev/null
+++ b/src/api.c
@@ -0,0 +1,2320 @@
+/*
+ * Copyright IBM Corporation. 2007
+ *
+ * Author: Dhaval Giani <dhaval@linux.vnet.ibm.com>
+ * Author: Balbir Singh <balbir@linux.vnet.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * TODOs:
+ * 1. Convert comments to Docbook style.
+ * 2. Add more APIs for the control groups.
+ * 3. Handle the configuration related APIs.
+ * 4. Error handling.
+ *
+ * Code initiated and designed by Dhaval Giani. All faults are most likely
+ * his mistake.
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <libcgroup.h>
+#include <libcgroup-internal.h>
+#include <mntent.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#include <fts.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <libgen.h>
+#include <assert.h>
+
+#ifndef PACKAGE_VERSION
+#define PACKAGE_VERSION 0.01
+#endif
+
+#define VERSION(ver) #ver
+
+/*
+ * The errno which happend the last time (have to be thread specific)
+ */
+__thread int last_errno;
+
+#define MAXLEN 256
+
+/* the value have to be thread specific */
+__thread char errtext[MAXLEN];
+
+/*
+ * Remember to bump this up for major API changes.
+ */
+const static char cg_version[] = VERSION(PACKAGE_VERSION);
+
+struct cg_mount_table_s cg_mount_table[CG_CONTROLLER_MAX];
+static pthread_rwlock_t cg_mount_table_lock = PTHREAD_RWLOCK_INITIALIZER;
+
+/* Check if cgroup_init has been called or not. */
+static int cgroup_initialized;
+
+/* Check if the rules cache has been loaded or not. */
+static bool cgroup_rules_loaded;
+
+/* List of configuration rules */
+static struct cgroup_rule_list rl;
+
+/* Temporary list of configuration rules (for non-cache apps) */
+static struct cgroup_rule_list trl;
+
+/* Lock for the list of rules (rl) */
+static pthread_rwlock_t rl_lock = PTHREAD_RWLOCK_INITIALIZER;
+
+char *cgroup_strerror_codes[] = {
+ "Cgroup is not compiled in",
+ "Cgroup is not mounted",
+ "Cgroup does not exist",
+ "Cgroup has not been created",
+ "Cgroup one of the needed subsystems is not mounted",
+ "Cgroup, request came in from non owner",
+ "Cgroup controllers controllers are bound to different mount points",
+ "Cgroup, operation not allowed",
+ "Cgroup value set exceeds maximum",
+ "Cgroup controller already exists",
+ "Cgroup value already exists",
+ "Cgroup invalid operation",
+ "Cgroup, creation of controller failed",
+ "Cgroup operation failed",
+ "Cgroup not initialized",
+ "Cgroup trying to set value for control that does not exist",
+ "Cgroup generic error, see errno",
+ "Cgroup values are not equal",
+ "Cgroup controllers are different",
+ "Cgroup parsing failed",
+ "Cgroup, rules file does not exist",
+ "Cgroup mounting failed",
+ "The config file can not be opend",
+ "End of File or iterator",
+};
+
+static int cg_chown_file(FTS *fts, FTSENT *ent, uid_t owner, gid_t group)
+{
+ int ret = 0;
+ const char *filename = fts->fts_path;
+ cgroup_dbg("seeing file %s\n", filename);
+ switch (ent->fts_info) {
+ case FTS_ERR:
+ errno = ent->fts_errno;
+ break;
+ case FTS_D:
+ case FTS_DC:
+ case FTS_NSOK:
+ case FTS_NS:
+ case FTS_DNR:
+ case FTS_DP:
+ ret = chown(filename, owner, group);
+ if (ret)
+ goto fail_chown;
+ ret = chmod(filename, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP |
+ S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH);
+ break;
+ case FTS_F:
+ case FTS_DEFAULT:
+ ret = chown(filename, owner, group);
+ if (ret)
+ goto fail_chown;
+ ret = chmod(filename, S_IRUSR | S_IWUSR | S_IRGRP |
+ S_IWGRP | S_IROTH);
+ break;
+ }
+fail_chown:
+ if (ret < 0) {
+ last_errno = errno;
+ ret = ECGOTHER;
+ }
+ return ret;
+}
+
+/*
+ * TODO: Need to decide a better place to put this function.
+ */
+static int cg_chown_recursive(char **path, uid_t owner, gid_t group)
+{
+ int ret = 0;
+ cgroup_dbg("path is %s\n", *path);
+ FTS *fts = fts_open(path, FTS_PHYSICAL | FTS_NOCHDIR |
+ FTS_NOSTAT, NULL);
+ while (1) {
+ FTSENT *ent;
+ ent = fts_read(fts);
+ if (!ent) {
+ cgroup_dbg("fts_read failed\n");
+ break;
+ }
+ ret = cg_chown_file(fts, ent, owner, group);
+ }
+ fts_close(fts);
+ return ret;
+}
+
+static int cgroup_test_subsys_mounted(const char *name)
+{
+ int i;
+
+ pthread_rwlock_rdlock(&cg_mount_table_lock);
+
+ for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
+ if (strncmp(cg_mount_table[i].name, name,
+ sizeof(cg_mount_table[i].name)) == 0) {
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ return 1;
+ }
+ }
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ return 0;
+}
+
+/**
+ * Free a single cgroup_rule struct.
+ * @param r The rule to free from memory
+ */
+static void cgroup_free_rule(struct cgroup_rule *r)
+{
+ /* Loop variable */
+ int i = 0;
+
+ /* Make sure our rule is not NULL, first. */
+ if (!r) {
+ cgroup_dbg("Warning: Attempted to free NULL rule.\n");
+ return;
+ }
+
+ /* We must free any used controller strings, too. */
+ for(i = 0; i < MAX_MNT_ELEMENTS; i++) {
+ if (r->controllers[i])
+ free(r->controllers[i]);
+ }
+
+ free(r);
+}
+
+/**
+ * Free a list of cgroup_rule structs. If rl is the main list of rules,
+ * the lock must be taken for writing before calling this function!
+ * @param rl Pointer to the list of rules to free from memory
+ */
+static void cgroup_free_rule_list(struct cgroup_rule_list *rl)
+{
+ /* Temporary pointer */
+ struct cgroup_rule *tmp = NULL;
+
+ /* Make sure we're not freeing NULL memory! */
+ if (!(rl->head)) {
+ cgroup_dbg("Warning: Attempted to free NULL list.\n");
+ return;
+ }
+
+ while (rl->head) {
+ tmp = rl->head;
+ rl->head = tmp->next;
+ cgroup_free_rule(tmp);
+ }
+
+ /* Don't leave wild pointers around! */
+ rl->head = NULL;
+ rl->tail = NULL;
+}
+
+/**
+ * Parse the configuration file that maps UID/GIDs to cgroups. If ever the
+ * configuration file is modified, applications should call this function to
+ * load the new configuration rules. The function caller is responsible for
+ * calling free() on each rule in the list.
+ *
+ * The cache parameter alters the behavior of this function. If true, this
+ * function will read the entire configuration file and store the results in
+ * rl (global rules list). If false, this function will only parse until it
+ * finds a rule matching the given UID or GID. It will store this rule in rl,
+ * as well as any children rules (rules that begin with a %) that it has.
+ *
+ * This function is NOT thread safe!
+ * @param cache True to cache rules, else false
+ * @param muid If cache is false, the UID to match against
+ * @param mgid If cache is false, the GID to match against
+ * @return 0 on success, -1 if no cache and match found, > 0 on error.
+ * TODO: Make this function thread safe!
+ */
+static int cgroup_parse_rules(bool cache, uid_t muid, gid_t mgid)
+{
+ /* File descriptor for the configuration file */
+ FILE *fp = NULL;
+
+ /* Buffer to store the line we're working on */
+ char *buff = NULL;
+
+ /* Iterator for the line we're working on */
+ char *itr = NULL;
+
+ /* Pointer to the list that we're using */
+ struct cgroup_rule_list *lst = NULL;
+
+ /* Rule to add to the list */
+ struct cgroup_rule *newrule = NULL;
+
+ /* Structure to get GID from group name */
+ struct group *grp = NULL;
+
+ /* Structure to get UID from user name */
+ struct passwd *pwd = NULL;
+
+ /* Temporary storage for a configuration rule */
+ char user[LOGIN_NAME_MAX] = { '\0' };
+ char controllers[CG_CONTROLLER_MAX] = { '\0' };
+ char destination[FILENAME_MAX] = { '\0' };
+ uid_t uid = CGRULE_INVALID;
+ gid_t gid = CGRULE_INVALID;
+
+ /* The current line number */
+ unsigned int linenum = 0;
+
+ /* Did we skip the previous line? */
+ bool skipped = false;
+
+ /* Have we found a matching rule (non-cache mode)? */
+ bool matched = false;
+
+ /* Return codes */
+ int ret = 0;
+
+ /* Temporary buffer for strtok() */
+ char *stok_buff = NULL;
+
+ /* Loop variable. */
+ int i = 0;
+
+ /* Open the configuration file. */
+ pthread_rwlock_wrlock(&rl_lock);
+ fp = fopen(CGRULES_CONF_FILE, "r");
+ if (!fp) {
+ cgroup_dbg("Failed to open configuration file %s with"
+ " error: %s\n", CGRULES_CONF_FILE,
+ strerror(errno));
+ last_errno = errno;
+ goto finish;
+ }
+
+ buff = calloc(CGROUP_RULE_MAXLINE, sizeof(char));
+ if (!buff) {
+ cgroup_dbg("Out of memory? Error: %s\n", strerror(errno));
+ last_errno = errno;
+ ret = ECGOTHER;
+ goto close_unlock;
+ }
+
+ /* Determine which list we're using. */
+ if (cache)
+ lst = &rl;
+ else
+ lst = &trl;
+
+ /* If our list already exists, clean it. */
+ if (lst->head)
+ cgroup_free_rule_list(lst);
+
+ /* Now, parse the configuration file one line at a time. */
+ cgroup_dbg("Parsing configuration file.\n");
+ while ((itr = fgets(buff, CGROUP_RULE_MAXLINE, fp)) != NULL) {
+ linenum++;
+
+ /* We ignore anything after a # sign as comments. */
+ if ((itr = strchr(buff, '#')))
+ *itr = '\0';
+
+ /* We also need to remove the newline character. */
+ if ((itr = strchr(buff, '\n')))
+ *itr = '\0';
+
+ /* Now, skip any leading tabs and spaces. */
+ itr = buff;
+ while (itr && isblank(*itr))
+ itr++;
+
+ /* If there's nothing left, we can ignore this line. */
+ if (!strlen(itr))
+ continue;
+
+ /*
+ * If we skipped the last rule and this rule is a continuation
+ * of it (begins with %), then we should skip this rule too.
+ */
+ if (skipped && *itr == '%') {
+ cgroup_dbg("Warning: Skipped child of invalid rule,"
+ " line %d.\n", linenum);
+ memset(buff, '\0', CGROUP_RULE_MAXLINE);
+ continue;
+ }
+
+ /*
+ * If there is something left, it should be a rule. Otherwise,
+ * there's an error in the configuration file.
+ */
+ skipped = false;
+ memset(user, '\0', LOGIN_NAME_MAX);
+ memset(controllers, '\0', CG_CONTROLLER_MAX);
+ memset(destination, '\0', FILENAME_MAX);
+ i = sscanf(itr, "%s%s%s", user, controllers, destination);
+ if (i != 3) {
+ cgroup_dbg("Failed to parse configuration file on"
+ " line %d.\n", linenum);
+ goto parsefail;
+ }
+
+ /*
+ * Next, check the user/group. If it's a % sign, then we
+ * are continuing another rule and UID/GID should not be
+ * reset. If it's a @, we're dealing with a GID rule. If
+ * it's a *, then we do not need to do a lookup because the
+ * rule always applies (it's a wildcard). If we're using
+ * non-cache mode and we've found a matching rule, we only
+ * continue to parse if we're looking at a child rule.
+ */
+ if ((!cache) && matched && (strncmp(user, "%", 1) != 0)) {
+ /* If we make it here, we finished (non-cache). */
+ cgroup_dbg("Parsing of configuration file"
+ " complete.\n\n");
+ ret = -1;
+ goto cleanup;
+ }
+ if (strncmp(user, "@", 1) == 0) {
+ /* New GID rule. */
+ itr = &(user[1]);
+ if ((grp = getgrnam(itr))) {
+ uid = CGRULE_INVALID;
+ gid = grp->gr_gid;
+ } else {
+ cgroup_dbg("Warning: Entry for %s not"
+ "found. Skipping rule on line"
+ " %d.\n", itr, linenum);
+ memset(buff, '\0', CGROUP_RULE_MAXLINE);
+ skipped = true;
+ continue;
+ }
+ } else if (strncmp(user, "*", 1) == 0) {
+ /* Special wildcard rule. */
+ uid = CGRULE_WILD;
+ gid = CGRULE_WILD;
+ } else if (*itr != '%') {
+ /* New UID rule. */
+ if ((pwd = getpwnam(user))) {
+ uid = pwd->pw_uid;
+ gid = CGRULE_INVALID;
+ } else {
+ cgroup_dbg("Warning: Entry for %s not"
+ "found. Skipping rule on line"
+ " %d.\n", user, linenum);
+ memset(buff, '\0', CGROUP_RULE_MAXLINE);
+ skipped = true;
+ continue;
+ }
+ } /* Else, we're continuing another rule (UID/GID are okay). */
+
+ /*
+ * If we are not caching rules, then we need to check for a
+ * match before doing anything else. We consider four cases:
+ * The UID matches, the GID matches, the UID is a member of the
+ * GID, or we're looking at the wildcard rule, which always
+ * matches. If none of these are true, we simply continue to
+ * the next line in the file.
+ */
+ if (grp && muid != CGRULE_INVALID) {
+ pwd = getpwuid(muid);
+ for (i = 0; grp->gr_mem[i]; i++) {
+ if (!(strcmp(pwd->pw_name, grp->gr_mem[i])))
+ matched = true;
+ }
+ }
+
+ if (uid == muid || gid == mgid || uid == CGRULE_WILD) {
+ matched = true;
+ }
+
+ if (!cache && !matched)
+ continue;
+
+ /*
+ * Now, we're either caching rules or we found a match. Either
+ * way, copy everything into a new rule and push it into the
+ * list.
+ */
+ newrule = calloc(1, sizeof(struct cgroup_rule));
+ if (!newrule) {
+ cgroup_dbg("Out of memory? Error: %s\n",
+ strerror(errno));
+ last_errno = errno;
+ ret = ECGOTHER;
+ goto cleanup;
+ }
+
+ newrule->uid = uid;
+ newrule->gid = gid;
+ strncpy(newrule->name, user, strlen(user));
+ strncpy(newrule->destination, destination, strlen(destination));
+ newrule->next = NULL;
+
+ /* Parse the controller list, and add that to newrule too. */
+ stok_buff = strtok(controllers, ",");
+ if (!stok_buff) {
+ cgroup_dbg("Failed to parse controllers on line"
+ " %d\n", linenum);
+ goto destroyrule;
+ }
+
+ i = 0;
+ do {
+ if (i >= MAX_MNT_ELEMENTS) {
+ cgroup_dbg("Too many controllers listed"
+ " on line %d\n", linenum);
+ goto destroyrule;
+ }
+
+ newrule->controllers[i] = strndup(stok_buff,
+ strlen(stok_buff) + 1);
+ if (!(newrule->controllers[i])) {
+ cgroup_dbg("Out of memory? Error was: %s\n",
+ strerror(errno));
+ goto destroyrule;
+ }
+ i++;
+ } while ((stok_buff = strtok(NULL, ",")));
+
+ /* Now, push the rule. */
+ if (lst->head == NULL) {
+ lst->head = newrule;
+ lst->tail = newrule;
+ } else {
+ lst->tail->next = newrule;
+ lst->tail = newrule;
+ }
+
+ cgroup_dbg("Added rule %s (UID: %d, GID: %d) -> %s for"
+ " controllers:", lst->tail->name, lst->tail->uid,
+ lst->tail->gid, lst->tail->destination);
+ for (i = 0; lst->tail->controllers[i]; i++) {
+ cgroup_dbg(" %s", lst->tail->controllers[i]);
+ }
+ cgroup_dbg("\n");
+
+ /* Finally, clear the buffer. */
+ memset(buff, '\0', CGROUP_RULE_MAXLINE);
+ grp = NULL;
+ pwd = NULL;
+ }
+
+ /* If we make it here, there were no errors. */
+ cgroup_dbg("Parsing of configuration file complete.\n\n");
+ ret = (matched && !cache) ? -1 : 0;
+ goto cleanup;
+
+destroyrule:
+ cgroup_free_rule(newrule);
+
+parsefail:
+ ret = ECGROUPPARSEFAIL;
+
+cleanup:
+ free(buff);
+
+close_unlock:
+ fclose(fp);
+ pthread_rwlock_unlock(&rl_lock);
+finish:
+ return ret;
+}
+
+/**
+ * cgroup_init(), initializes the MOUNT_POINT.
+ *
+ * This code is theoretically thread safe now. Its not really tested
+ * so it can blow up. If does for you, please let us know with your
+ * test case and we can really make it thread safe.
+ *
+ */
+int cgroup_init()
+{
+ FILE *proc_mount = NULL;
+ struct mntent *ent = NULL;
+ struct mntent *temp_ent = NULL;
+ int found_mnt = 0;
+ int ret = 0;
+ static char *controllers[CG_CONTROLLER_MAX];
+ FILE *proc_cgroup = NULL;
+ char subsys_name[FILENAME_MAX];
+ int hierarchy, num_cgroups, enabled;
+ int i=0;
+ char *mntopt = NULL;
+ int err;
+ char *buf = NULL;
+ char mntent_buffer[4 * FILENAME_MAX];
+ char *strtok_buffer = NULL;
+
+ pthread_rwlock_wrlock(&cg_mount_table_lock);
+
+ proc_cgroup = fopen("/proc/cgroups", "r");
+
+ if (!proc_cgroup) {
+ last_errno = errno;
+ ret = ECGOTHER;
+ goto unlock_exit;
+ }
+
+ /*
+ * The first line of the file has stuff we are not interested in.
+ * So just read it and discard the information.
+ *
+ * XX: fix the size for fgets
+ */
+ buf = malloc(FILENAME_MAX);
+ if (!buf) {
+ last_errno = errno;
+ ret = ECGOTHER;
+ goto unlock_exit;
+ }
+ buf = fgets(buf, FILENAME_MAX, proc_cgroup);
+ if (!buf) {
+ last_errno = errno;
+ ret = ECGOTHER;
+ goto unlock_exit;
+ }
+ free(buf);
+
+ while (!feof(proc_cgroup)) {
+ err = fscanf(proc_cgroup, "%s %d %d %d", subsys_name,
+ &hierarchy, &num_cgroups, &enabled);
+ if (err < 0)
+ break;
+ controllers[i] = strdup(subsys_name);
+ i++;
+ }
+ controllers[i] = NULL;
+
+ proc_mount = fopen("/proc/mounts", "r");
+ if (proc_mount == NULL) {
+ ret = ECGFAIL;
+ goto unlock_exit;
+ }
+
+ temp_ent = (struct mntent *) malloc(sizeof(struct mntent));
+
+ if (!temp_ent) {
+ last_errno = errno;
+ ret = ECGOTHER;
+ goto unlock_exit;
+ }
+
+ while ((ent = getmntent_r(proc_mount, temp_ent,
+ mntent_buffer,
+ sizeof(mntent_buffer))) != NULL) {
+ if (!strcmp(ent->mnt_type, "cgroup")) {
+ for (i = 0; controllers[i] != NULL; i++) {
+ mntopt = hasmntopt(ent, controllers[i]);
+
+ if (!mntopt)
+ continue;
+
+ mntopt = strtok_r(mntopt, ",", &strtok_buffer);
+
+ if (strcmp(mntopt, controllers[i]) == 0) {
+ cgroup_dbg("matched %s:%s\n", mntopt,
+ controllers[i]);
+ strcpy(cg_mount_table[found_mnt].name,
+ controllers[i]);
+ strcpy(cg_mount_table[found_mnt].path,
+ ent->mnt_dir);
+ cgroup_dbg("Found cgroup option %s, "
+ " count %d\n",
+ ent->mnt_opts, found_mnt);
+ found_mnt++;
+ }
+ }
+ }
+ }
+
+ free(temp_ent);
+
+ if (!found_mnt) {
+ cg_mount_table[0].name[0] = '\0';
+ ret = ECGROUPNOTMOUNTED;
+ goto unlock_exit;
+ }
+
+ found_mnt++;
+ cg_mount_table[found_mnt].name[0] = '\0';
+
+ cgroup_initialized = 1;
+
+unlock_exit:
+ if (proc_cgroup)
+ fclose(proc_cgroup);
+
+ if (proc_mount)
+ fclose(proc_mount);
+
+ for (i = 0; controllers[i]; i++) {
+ free(controllers[i]);
+ controllers[i] = NULL;
+ }
+
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+
+ return ret;
+}
+
+static int cg_test_mounted_fs()
+{
+ FILE *proc_mount = NULL;
+ struct mntent *ent = NULL;
+ struct mntent *temp_ent = NULL;
+ char mntent_buff[4 * FILENAME_MAX];
+ int ret = 1;
+
+ proc_mount = fopen("/proc/mounts", "r");
+ if (proc_mount == NULL) {
+ return 0;
+ }
+
+ temp_ent = (struct mntent *) malloc(sizeof(struct mntent));
+ if (!temp_ent) {
+ /* We just fail at the moment. */
+ return 0;
+ }
+
+ ent = getmntent_r(proc_mount, temp_ent, mntent_buff,
+ sizeof(mntent_buff));
+
+ if (!ent)
+ return 0;
+
+ while (strcmp(ent->mnt_type, "cgroup") !=0) {
+ ent = getmntent_r(proc_mount, temp_ent, mntent_buff,
+ sizeof(mntent_buff));
+ if (ent == NULL) {
+ ret = 0;
+ goto done;
+ }
+ }
+done:
+ fclose(proc_mount);
+ free(temp_ent);
+ return ret;
+}
+
+static inline pid_t cg_gettid()
+{
+ return syscall(__NR_gettid);
+}
+
+
+/* Call with cg_mount_table_lock taken */
+static char *cg_build_path_locked(char *name, char *path, char *type)
+{
+ int i;
+ for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
+ /*
+ * XX: Change to snprintf once you figure what n should be
+ */
+ if (strcmp(cg_mount_table[i].name, type) == 0) {
+ sprintf(path, "%s/", cg_mount_table[i].path);
+ if (name) {
+ char *tmp;
+ tmp = strdup(path);
+ sprintf(path, "%s%s/", tmp, name);
+ free(tmp);
+ }
+ return path;
+ }
+ }
+ return NULL;
+}
+
+char *cg_build_path(char *name, char *path, char *type)
+{
+ pthread_rwlock_rdlock(&cg_mount_table_lock);
+ path = cg_build_path_locked(name, path, type);
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+
+ return path;
+}
+
+/** cgroup_attach_task_pid is used to assign tasks to a cgroup.
+ * struct cgroup *cgroup: The cgroup to assign the thread to.
+ * pid_t tid: The thread to be assigned to the cgroup.
+ *
+ * returns 0 on success.
+ * returns ECGROUPNOTOWNER if the caller does not have access to the cgroup.
+ * returns ECGROUPNOTALLOWED for other causes of failure.
+ */
+int cgroup_attach_task_pid(struct cgroup *cgroup, pid_t tid)
+{
+ char path[FILENAME_MAX];
+ FILE *tasks = NULL;
+ int i, ret = 0;
+
+ if (!cgroup_initialized) {
+ cgroup_dbg("libcgroup is not initialized\n");
+ return ECGROUPNOTINITIALIZED;
+ }
+ if(!cgroup)
+ {
+ pthread_rwlock_rdlock(&cg_mount_table_lock);
+ for(i = 0; i < CG_CONTROLLER_MAX &&
+ cg_mount_table[i].name[0]!='\0'; i++) {
+ if (!cg_build_path_locked(NULL, path,
+ cg_mount_table[i].name))
+ continue;
+ strncat(path, "/tasks", sizeof(path) - strlen(path));
+
+ tasks = fopen(path, "w");
+ if (!tasks) {
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ switch (errno) {
+ case EPERM:
+ return ECGROUPNOTOWNER;
+ case ENOENT:
+ return ECGROUPNOTEXIST;
+ default:
+ return ECGROUPNOTALLOWED;
+ }
+ }
+ ret = fprintf(tasks, "%d", tid);
+ if (ret < 0) {
+ cgroup_dbg("Error writing tid %d to %s:%s\n",
+ tid, path, strerror(errno));
+ fclose(tasks);
+ last_errno = errno;
+ return ECGOTHER;
+ }
+
+ ret = fflush(tasks);
+ if (ret) {
+ last_errno = errno;
+ cgroup_dbg("Error writing tid %d to %s:%s\n",
+ tid, path, strerror(errno));
+ fclose(tasks);
+ return ECGOTHER;
+ }
+ fclose(tasks);
+ }
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ } else {
+ for (i = 0; i < cgroup->index; i++) {
+ if (!cgroup_test_subsys_mounted(cgroup->controller[i]->name)) {
+ cgroup_dbg("subsystem %s is not mounted\n",
+ cgroup->controller[i]->name);
+ return ECGROUPSUBSYSNOTMOUNTED;
+ }
+ }
+
+ for (i = 0; i < cgroup->index; i++) {
+ if (!cg_build_path(cgroup->name, path,
+ cgroup->controller[i]->name))
+ continue;
+
+ strncat(path, "/tasks", sizeof(path) - strlen(path));
+
+ tasks = fopen(path, "w");
+ if (!tasks) {
+ cgroup_dbg("fopen failed for %s:%s", path,
+ strerror(errno));
+
+ switch (errno) {
+ case EPERM:
+ return ECGROUPNOTOWNER;
+ case ENOENT:
+ return ECGROUPNOTEXIST;
+ default:
+ return ECGROUPNOTALLOWED;
+ }
+ }
+ ret = fprintf(tasks, "%d", tid);
+ if (ret < 0) {
+ last_errno = errno;
+ cgroup_dbg("Error writing tid %d to %s:%s\n",
+ tid, path, strerror(errno));
+ fclose(tasks);
+ return ECGOTHER;
+ }
+ ret = fflush(tasks);
+ if (ret) {
+ last_errno = errno;
+ cgroup_dbg("Error writing tid %d to %s:%s\n",
+ tid, path, strerror(errno));
+ fclose(tasks);
+ return ECGOTHER;
+ }
+ fclose(tasks);
+ }
+ }
+ return 0;
+}
+
+/** cgroup_attach_task is used to attach the current thread to a cgroup.
+ * struct cgroup *cgroup: The cgroup to assign the current thread to.
+ *
+ * See cg_attach_task_pid for return values.
+ */
+int cgroup_attach_task(struct cgroup *cgroup)
+{
+ pid_t tid = cg_gettid();
+ int error;
+
+ error = cgroup_attach_task_pid(cgroup, tid);
+
+ return error;
+}
+
+/**
+ * cg_mkdir_p, emulate the mkdir -p command (recursively creating paths)
+ * @path: path to create
+ */
+static int cg_mkdir_p(const char *path)
+{
+ char *real_path = NULL;
+ char *wd = NULL;
+ int i = 0, j = 0;
+ char pos;
+ char *str = NULL;
+ int ret = 0;
+ char cwd[FILENAME_MAX];
+ char *buf = NULL;
+
+ buf = getcwd(cwd, FILENAME_MAX);
+
+ if (!buf) {
+ last_errno = errno;
+ return ECGOTHER;
+ }
+
+ real_path = strdup(path);
+ if (!real_path) {
+ last_errno = errno;
+ return ECGOTHER;
+ }
+
+ do {
+ while (real_path[j] != '\0' && real_path[j] != '/')
+ j++;
+ while (real_path[j] != '\0' && real_path[j] == '/')
+ j++;
+ if (i == j)
+ continue;
+ pos = real_path[j];
+ real_path[j] = '\0'; /* Temporarily overwrite "/" */
+ str = &real_path[i];
+ ret = mkdir(str, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+ wd = strdup(str);
+ if (!wd) {
+ last_errno = errno;
+ ret = ECGOTHER;
+ break;
+ }
+ real_path[j] = pos;
+ if (ret) {
+ switch (errno) {
+ case EEXIST:
+ ret = 0; /* Not fatal really */
+ break;
+ case EPERM:
+ ret = ECGROUPNOTOWNER;
+ free(wd);
+ goto done;
+ default:
+ ret = ECGROUPNOTALLOWED;
+ free(wd);
+ goto done;
+ }
+ }
+ i = j;
+ ret = chdir(wd);
+ if (ret) {
+ cgroup_dbg("could not chdir to child directory (%s)\n",
+ wd);
+ break;
+ }
+ free(wd);
+ } while (real_path[i]);
+
+ ret = chdir(buf);
+ if (ret) {
+ last_errno = errno;
+ ret = ECGOTHER;
+ cgroup_dbg("could not go back to old directory (%s)\n", cwd);
+ }
+
+done:
+ free(real_path);
+ return ret;
+}
+
+/*
+ * create_control_group()
+ * This is the basic function used to create the control group. This function
+ * just makes the group. It does not set any permissions, or any control values.
+ * The argument path is the fully qualified path name to make it generic.
+ */
+static int cg_create_control_group(char *path)
+{
+ int error;
+ if (!cg_test_mounted_fs())
+ return ECGROUPNOTMOUNTED;
+ error = cg_mkdir_p(path);
+ return error;
+}
+
+/*
+ * set_control_value()
+ * This is the low level function for putting in a value in a control file.
+ * This function takes in the complete path and sets the value in val in that
+ * file.
+ */
+static int cg_set_control_value(char *path, char *val)
+{
+ FILE *control_file = NULL;
+ if (!cg_test_mounted_fs())
+ return ECGROUPNOTMOUNTED;
+
+ control_file = fopen(path, "r+");
+
+ if (!control_file) {
+ if (errno == EPERM) {
+ /*
+ * We need to set the correct error value, does the
+ * group exist but we don't have the subsystem
+ * mounted at that point, or is it that the group
+ * does not exist. So we check if the tasks file
+ * exist. Before that, we need to extract the path.
+ */
+ int len = strlen(path);
+
+ while (*(path+len) != '/')
+ len--;
+ *(path+len+1) = '\0';
+ strncat(path, "tasks", sizeof(path) - strlen(path));
+ control_file = fopen(path, "r");
+ if (!control_file) {
+ if (errno == ENOENT)
+ return ECGROUPSUBSYSNOTMOUNTED;
+ }
+ fclose(control_file);
+ return ECGROUPNOTALLOWED;
+ }
+ return ECGROUPVALUENOTEXIST;
+ }
+
+ fprintf(control_file, "%s", val);
+ fclose(control_file);
+ return 0;
+}
+
+/** cgroup_modify_cgroup modifies the cgroup control files.
+ * struct cgroup *cgroup: The name will be the cgroup to be modified.
+ * The values will be the values to be modified, those not mentioned
+ * in the structure will not be modified.
+ *
+ * The uids cannot be modified yet.
+ *
+ * returns 0 on success.
+ *
+ */
+
+int cgroup_modify_cgroup(struct cgroup *cgroup)
+{
+ char *path, base[FILENAME_MAX];
+ int i;
+ int error;
+ int ret;
+
+ if (!cgroup_initialized)
+ return ECGROUPNOTINITIALIZED;
+
+ if (!cgroup)
+ return ECGROUPNOTALLOWED;
+
+ for (i = 0; i < cgroup->index; i++) {
+ if (!cgroup_test_subsys_mounted(cgroup->controller[i]->name)) {
+ cgroup_dbg("subsystem %s is not mounted\n",
+ cgroup->controller[i]->name);
+ return ECGROUPSUBSYSNOTMOUNTED;
+ }
+ }
+
+ for (i = 0; i < cgroup->index; i++) {
+ int j;
+ if (!cg_build_path(cgroup->name, base,
+ cgroup->controller[i]->name))
+ continue;
+ for (j = 0; j < cgroup->controller[i]->index; j++) {
+ ret = asprintf(&path, "%s%s", base,
+ cgroup->controller[i]->values[j]->name);
+ if (ret < 0) {
+ last_errno = errno;
+ error = ECGOTHER;
+ goto err;
+ }
+ error = cg_set_control_value(path,
+ cgroup->controller[i]->values[j]->value);
+ free(path);
+ path = NULL;
+ if (error)
+ goto err;
+ }
+ }
+ if (path)
+ free(path);
+ return 0;
+err:
+ if (path)
+ free(path);
+ return error;
+
+}
+
+/**
+ * @dst: Destination controller
+ * @src: Source controller from which values will be copied to dst
+ *
+ * Create a duplicate copy of values under the specified controller
+ */
+int cgroup_copy_controller_values(struct cgroup_controller *dst,
+ struct cgroup_controller *src)
+{
+ int i, ret = 0;
+
+ if (!dst || !src)
+ return ECGFAIL;
+
+ strncpy(dst->name, src->name, FILENAME_MAX);
+ for (i = 0; i < src->index; i++, dst->index++) {
+ struct control_value *src_val = src->values[i];
+ struct control_value *dst_val;
+
+ dst->values[i] = calloc(1, sizeof(struct control_value));
+ if (!dst->values[i]) {
+ ret = ECGFAIL;
+ goto err;
+ }
+
+ dst_val = dst->values[i];
+ strncpy(dst_val->value, src_val->value, CG_VALUE_MAX);
+ strncpy(dst_val->name, src_val->name, FILENAME_MAX);
+ }
+err:
+ return ret;
+}
+
+/**
+ * @dst: Destination control group
+ * @src: Source from which values will be copied to dst
+ *
+ * Create a duplicate copy of src in dst. This will be useful for those who
+ * that intend to create new instances based on an existing control group
+ */
+int cgroup_copy_cgroup(struct cgroup *dst, struct cgroup *src)
+{
+ int ret = 0, i;
+
+ if (!dst || !src)
+ return ECGROUPNOTEXIST;
+
+ /*
+ * Should we just use the restrict keyword instead?
+ */
+ if (dst == src)
+ return ECGFAIL;
+
+ cgroup_free_controllers(dst);
+
+ for (i = 0; i < src->index; i++, dst->index++) {
+ struct cgroup_controller *src_ctlr = src->controller[i];
+ struct cgroup_controller *dst_ctlr;
+
+ dst->controller[i] = calloc(1, sizeof(struct cgroup_controller));
+ if (!dst->controller[i]) {
+ ret = ECGFAIL;
+ goto err;
+ }
+
+ dst_ctlr = dst->controller[i];
+ ret = cgroup_copy_controller_values(dst_ctlr, src_ctlr);
+ if (ret)
+ goto err;
+ }
+err:
+ return ret;
+}
+
+/** cgroup_create_cgroup creates a new control group.
+ * struct cgroup *cgroup: The control group to be created
+ *
+ * returns 0 on success. We recommend calling cg_delete_cgroup
+ * if this routine fails. That should do the cleanup operation.
+ */
+int cgroup_create_cgroup(struct cgroup *cgroup, int ignore_ownership)
+{
+ char *fts_path[2];
+ char *base = NULL;
+ char *path = NULL;
+ int i, j, k;
+ int error = 0;
+ int retval = 0;
+ int ret;
+
+ if (!cgroup_initialized)
+ return ECGROUPNOTINITIALIZED;
+
+ if (!cgroup)
+ return ECGROUPNOTALLOWED;
+
+ for (i = 0; i < cgroup->index; i++) {
+ if (!cgroup_test_subsys_mounted(cgroup->controller[i]->name))
+ return ECGROUPSUBSYSNOTMOUNTED;
+ }
+
+ fts_path[0] = (char *)malloc(FILENAME_MAX);
+ if (!fts_path[0]) {
+ last_errno = errno;
+ return ECGOTHER;
+ }
+ fts_path[1] = NULL;
+ path = fts_path[0];
+
+ /*
+ * XX: One important test to be done is to check, if you have multiple
+ * subsystems mounted at one point, all of them *have* be on the cgroup
+ * data structure. If not, we fail.
+ */
+ for (k = 0; k < cgroup->index; k++) {
+ if (!cg_build_path(cgroup->name, path,
+ cgroup->controller[k]->name))
+ continue;
+
+ error = cg_create_control_group(path);
+ if (error)
+ goto err;
+
+ base = strdup(path);
+
+ if (!base) {
+ last_errno = errno;
+ error = ECGOTHER;
+ goto err;
+ }
+
+ if (!ignore_ownership) {
+ cgroup_dbg("Changing ownership of %s\n", fts_path[0]);
+ error = cg_chown_recursive(fts_path,
+ cgroup->control_uid, cgroup->control_gid);
+ }
+
+ if (error)
+ goto err;
+
+ for (j = 0; j < cgroup->controller[k]->index; j++) {
+ ret = snprintf(path, FILENAME_MAX, "%s%s", base,
+ cgroup->controller[k]->values[j]->name);
+ cgroup_dbg("setting %s to %s, error %d\n", path,
+ cgroup->controller[k]->values[j]->name, ret);
+ if (ret < 0 || ret >= FILENAME_MAX) {
+ last_errno = errno;
+ error = ECGOTHER;
+ goto err;
+ }
+ error = cg_set_control_value(path,
+ cgroup->controller[k]->values[j]->value);
+ /*
+ * Should we undo, what we've done in the loops above?
+ * An error should not be treated as fatal, since we
+ * have several read-only files and several files that
+ * are only conditionally created in the child.
+ *
+ * A middle ground would be to track that there
+ * was an error and return that value.
+ */
+ if (error) {
+ retval = error;
+ continue;
+ }
+ }
+
+ if (!ignore_ownership) {
+ ret = snprintf(path, FILENAME_MAX, "%s/tasks", base);
+ if (ret < 0 || ret >= FILENAME_MAX) {
+ last_errno = errno;
+ error = ECGOTHER;
+ goto err;
+ }
+ error = chown(path, cgroup->tasks_uid,
+ cgroup->tasks_gid);
+ if (error) {
+ last_errno = errno;
+ error = ECGOTHER;
+ goto err;
+ }
+ }
+ free(base);
+ base = NULL;
+ }
+
+err:
+ if (path)
+ free(path);
+ if (base)
+ free(base);
+ if (retval && !error)
+ error = retval;
+ return error;
+}
+
+/**
+ * Find the parent of the specified directory. It returns the parent (the
+ * parent is usually name/.. unless name is a mount point.
+ */
+char *cgroup_find_parent(char *name)
+{
+ char child[FILENAME_MAX];
+ char *parent = NULL;
+ struct stat stat_child, stat_parent;
+ char *type = NULL;
+ char *dir = NULL;
+
+ pthread_rwlock_rdlock(&cg_mount_table_lock);
+ type = cg_mount_table[0].name;
+ if (!cg_build_path_locked(name, child, type)) {
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ return NULL;
+ }
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+
+ cgroup_dbg("path is %s\n", child);
+ dir = dirname(child);
+ cgroup_dbg("directory name is %s\n", dir);
+
+ if (asprintf(&parent, "%s/..", dir) < 0)
+ return NULL;
+
+ cgroup_dbg("parent's name is %s\n", parent);
+
+ if (stat(dir, &stat_child) < 0)
+ goto free_parent;
+
+ if (stat(parent, &stat_parent) < 0)
+ goto free_parent;
+
+ /*
+ * Is the specified "name" a mount point?
+ */
+ if (stat_parent.st_dev != stat_child.st_dev) {
+ cgroup_dbg("parent is a mount point\n");
+ strcpy(parent, ".");
+ } else {
+ dir = strdup(name);
+ if (!dir)
+ goto free_parent;
+ dir = dirname(dir);
+ if (strcmp(dir, ".") == 0)
+ strcpy(parent, "..");
+ else
+ strcpy(parent, dir);
+ }
+
+ return parent;
+
+free_parent:
+ free(parent);
+ return NULL;
+}
+
+/**
+ * @cgroup: cgroup data structure to be filled with parent values and then
+ * passed down for creation
+ * @ignore_ownership: Ignore doing a chown on the newly created cgroup
+ */
+int cgroup_create_cgroup_from_parent(struct cgroup *cgroup,
+ int ignore_ownership)
+{
+ char *parent = NULL;
+ struct cgroup *parent_cgroup = NULL;
+ int ret = ECGFAIL;
+
+ if (!cgroup_initialized)
+ return ECGROUPNOTINITIALIZED;
+
+ parent = cgroup_find_parent(cgroup->name);
+ if (!parent)
+ return ret;
+
+ cgroup_dbg("parent is %s\n", parent);
+ parent_cgroup = cgroup_new_cgroup(parent);
+ if (!parent_cgroup)
+ goto err_nomem;
+
+ if (cgroup_get_cgroup(parent_cgroup))
+ goto err_parent;
+
+ cgroup_dbg("got parent group for %s\n", parent_cgroup->name);
+ ret = cgroup_copy_cgroup(cgroup, parent_cgroup);
+ if (ret)
+ goto err_parent;
+
+ cgroup_dbg("copied parent group %s to %s\n", parent_cgroup->name,
+ cgroup->name);
+ ret = cgroup_create_cgroup(cgroup, ignore_ownership);
+
+err_parent:
+ cgroup_free(&parent_cgroup);
+err_nomem:
+ free(parent);
+ return ret;
+}
+
+/** cgroup_delete cgroup deletes a control group.
+ * struct cgroup *cgroup takes the group which is to be deleted.
+ *
+ * returns 0 on success.
+ */
+int cgroup_delete_cgroup(struct cgroup *cgroup, int ignore_migration)
+{
+ FILE *delete_tasks = NULL, *base_tasks = NULL;
+ int tids;
+ char path[FILENAME_MAX];
+ int error = ECGROUPNOTALLOWED;
+ int i, ret;
+
+ if (!cgroup_initialized)
+ return ECGROUPNOTINITIALIZED;
+
+ if (!cgroup)
+ return ECGROUPNOTALLOWED;
+
+ for (i = 0; i < cgroup->index; i++) {
+ if (!cgroup_test_subsys_mounted(cgroup->controller[i]->name))
+ return ECGROUPSUBSYSNOTMOUNTED;
+ }
+
+ for (i = 0; i < cgroup->index; i++) {
+ if (!cg_build_path(cgroup->name, path,
+ cgroup->controller[i]->name))
+ continue;
+ strncat(path, "../tasks", sizeof(path) - strlen(path));
+
+ base_tasks = fopen(path, "w");
+ if (!base_tasks)
+ goto open_err;
+
+ if (!cg_build_path(cgroup->name, path,
+ cgroup->controller[i]->name)) {
+ fclose(base_tasks);
+ continue;
+ }
+
+ strncat(path, "tasks", sizeof(path) - strlen(path));
+
+ delete_tasks = fopen(path, "r");
+ if (!delete_tasks) {
+ fclose(base_tasks);
+ goto open_err;
+ }
+
+ while (!feof(delete_tasks)) {
+ ret = fscanf(delete_tasks, "%d", &tids);
+ if (ret == EOF || ret < 1)
+ break;
+ fprintf(base_tasks, "%d", tids);
+ }
+
+ fclose(delete_tasks);
+ fclose(base_tasks);
+
+ if (!cg_build_path(cgroup->name, path,
+ cgroup->controller[i]->name))
+ continue;
+ error = rmdir(path);
+ last_errno = errno;
+ }
+open_err:
+ if (ignore_migration) {
+ for (i = 0; i < cgroup->index; i++) {
+ if (!cg_build_path(cgroup->name, path,
+ cgroup->controller[i]->name))
+ continue;
+ error = rmdir(path);
+ if (error < 0 && errno == ENOENT) {
+ last_errno = errno;
+ error = 0;
+ }
+ }
+ }
+ if (error)
+ return ECGOTHER;
+
+ return error;
+}
+
+/*
+ * This function should really have more checks, but this version
+ * will assume that the callers have taken care of everything.
+ * Including the locking.
+ */
+static int cg_rd_ctrl_file(char *subsys, char *cgroup, char *file, char **value)
+{
+ char path[FILENAME_MAX];
+ FILE *ctrl_file = NULL;
+ int ret;
+
+ if (!cg_build_path_locked(cgroup, path, subsys))
+ return ECGFAIL;
+
+ strncat(path, file, sizeof(path) - strlen(path));
+ ctrl_file = fopen(path, "r");
+ if (!ctrl_file)
+ return ECGROUPVALUENOTEXIST;
+
+ *value = malloc(CG_VALUE_MAX);
+ if (!*value) {
+ last_errno = errno;
+ return ECGOTHER;
+ }
+
+ /*
+ * using %as crashes when we try to read from files like
+ * memory.stat
+ */
+ ret = fscanf(ctrl_file, "%s", *value);
+ if (ret == 0 || ret == EOF) {
+ free(*value);
+ *value = NULL;
+ }
+
+ fclose(ctrl_file);
+
+ return 0;
+}
+
+/*
+ * Call this function with required locks taken.
+ */
+static int cgroup_fill_cgc(struct dirent *ctrl_dir, struct cgroup *cgroup,
+ struct cgroup_controller *cgc, int index)
+{
+ char *ctrl_name = NULL;
+ char *ctrl_file = NULL;
+ char *ctrl_value = NULL;
+ char *d_name = NULL;
+ char path[FILENAME_MAX+1];
+ char *buffer = NULL;
+ int error = 0;
+ struct stat stat_buffer;
+
+ d_name = strdup(ctrl_dir->d_name);
+
+ if (!strcmp(d_name, ".") || !strcmp(d_name, "..")) {
+ error = ECGINVAL;
+ goto fill_error;
+ }
+
+
+ /*
+ * This part really needs to be optimized out. Probably use
+ * some sort of a flag, but this is fine for now.
+ */
+
+ cg_build_path_locked(cgroup->name, path, cg_mount_table[index].name);
+ strncat(path, d_name, sizeof(path) - strlen(path));
+
+ error = stat(path, &stat_buffer);
+
+ if (error) {
+ error = ECGFAIL;
+ goto fill_error;
+ }
+
+ cgroup->control_uid = stat_buffer.st_uid;
+ cgroup->control_gid = stat_buffer.st_gid;
+
+ ctrl_name = strtok_r(d_name, ".", &buffer);
+
+ if (!ctrl_name) {
+ error = ECGFAIL;
+ goto fill_error;
+ }
+
+ ctrl_file = strtok_r(NULL, ".", &buffer);
+
+ if (!ctrl_file) {
+ error = ECGINVAL;
+ goto fill_error;
+ }
+
+ if (strcmp(ctrl_name, cg_mount_table[index].name) == 0) {
+ error = cg_rd_ctrl_file(cg_mount_table[index].name,
+ cgroup->name, ctrl_dir->d_name, &ctrl_value);
+ if (error || !ctrl_value)
+ goto fill_error;
+
+ if (cgroup_add_value_string(cgc, ctrl_dir->d_name,
+ ctrl_value)) {
+ error = ECGFAIL;
+ goto fill_error;
+ }
+ }
+fill_error:
+ if (ctrl_value)
+ free(ctrl_value);
+ free(d_name);
+ return error;
+}
+
+/*
+ * cgroup_get_cgroup reads the cgroup data from the filesystem.
+ * struct cgroup has the name of the group to be populated
+ *
+ * return 0 on success.
+ */
+int cgroup_get_cgroup(struct cgroup *cgroup)
+{
+ int i;
+ char path[FILENAME_MAX];
+ DIR *dir = NULL;
+ struct dirent *ctrl_dir = NULL;
+ char *control_path = NULL;
+ int error;
+ int ret;
+
+ if (!cgroup_initialized) {
+ /* ECGROUPNOTINITIALIZED */
+ return ECGROUPNOTINITIALIZED;
+ }
+
+ if (!cgroup) {
+ /* ECGROUPNOTALLOWED */
+ return ECGROUPNOTALLOWED;
+ }
+
+ pthread_rwlock_rdlock(&cg_mount_table_lock);
+ for (i = 0; i < CG_CONTROLLER_MAX &&
+ cg_mount_table[i].name[0] != '\0'; i++) {
+ /*
+ * cgc will not leak, since it has to be freed using
+ * cgroup_free_cgroup
+ */
+ struct cgroup_controller *cgc;
+ struct stat stat_buffer;
+ int path_len;
+
+ if (!cg_build_path_locked(NULL, path,
+ cg_mount_table[i].name))
+ continue;
+
+ path_len = strlen(path);
+ strncat(path, cgroup->name, FILENAME_MAX - path_len - 1);
+
+ if (access(path, F_OK))
+ continue;
+
+ if (!cg_build_path_locked(cgroup->name, path,
+ cg_mount_table[i].name)) {
+ /*
+ * This fails when the cgroup does not exist
+ * for that controller.
+ */
+ continue;
+ }
+
+ /*
+ * Get the uid and gid information
+ */
+
+ ret = asprintf(&control_path, "%s/tasks", path);
+
+ if (ret < 0) {
+ last_errno = errno;
+ error = ECGOTHER;
+ goto unlock_error;
+ }
+
+ if (stat(control_path, &stat_buffer)) {
+ last_errno = errno;
+ free(control_path);
+ error = ECGOTHER;
+ goto unlock_error;
+ }
+
+ cgroup->tasks_uid = stat_buffer.st_uid;
+ cgroup->tasks_gid = stat_buffer.st_gid;
+
+ free(control_path);
+
+ cgc = cgroup_add_controller(cgroup,
+ cg_mount_table[i].name);
+ if (!cgc) {
+ error = ECGINVAL;
+ goto unlock_error;
+ }
+
+ dir = opendir(path);
+ if (!dir) {
+ last_errno = errno;
+ error = ECGOTHER;
+ goto unlock_error;
+ }
+
+ while ((ctrl_dir = readdir(dir)) != NULL) {
+ /*
+ * Skip over non regular files
+ */
+ if (ctrl_dir->d_type != DT_REG)
+ continue;
+
+ error = cgroup_fill_cgc(ctrl_dir, cgroup, cgc, i);
+ if (error == ECGFAIL) {
+ closedir(dir);
+ goto unlock_error;
+ }
+ }
+ closedir(dir);
+ }
+
+ /* Check if the group really exists or not */
+ if (!cgroup->index) {
+ error = ECGROUPNOTEXIST;
+ goto unlock_error;
+ }
+
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ return 0;
+
+unlock_error:
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ /*
+ * XX: Need to figure out how to cleanup? Cleanup just the stuff
+ * we added, or the whole structure.
+ */
+ cgroup = NULL;
+ return error;
+}
+
+/** cg_prepare_cgroup
+ * Process the selected rule. Prepare the cgroup structure which can be
+ * used to add the task to destination cgroup.
+ *
+ *
+ * returns 0 on success.
+ */
+static int cg_prepare_cgroup(struct cgroup *cgroup, pid_t pid,
+ const char *dest,
+ char *controllers[])
+{
+ int ret = 0, i;
+ char *controller = NULL;
+ struct cgroup_controller *cptr = NULL;
+
+ /* Fill in cgroup details. */
+ cgroup_dbg("Will move pid %d to cgroup '%s'\n", pid, dest);
+
+ strcpy(cgroup->name, dest);
+
+ /* Scan all the controllers */
+ for (i = 0; i < CG_CONTROLLER_MAX; i++) {
+ if (!controllers[i])
+ return 0;
+ controller = controllers[i];
+
+ /* If first string is "*" that means all the mounted
+ * controllers. */
+ if (strcmp(controller, "*") == 0) {
+ pthread_rwlock_rdlock(&cg_mount_table_lock);
+ for (i = 0; i < CG_CONTROLLER_MAX &&
+ cg_mount_table[i].name[0] != '\0'; i++) {
+ cgroup_dbg("Adding controller %s\n",
+ cg_mount_table[i].name);
+ cptr = cgroup_add_controller(cgroup,
+ cg_mount_table[i].name);
+ if (!cptr) {
+ cgroup_dbg("Adding controller '%s'"
+ " failed\n",
+ cg_mount_table[i].name);
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ return ECGROUPNOTALLOWED;
+ }
+ }
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ return ret;
+ }
+
+ /* it is individual controller names and not "*" */
+ cgroup_dbg("Adding controller %s\n", controller);
+ cptr = cgroup_add_controller(cgroup, controller);
+ if (!cptr) {
+ cgroup_dbg("Adding controller '%s' failed\n",
+ controller);
+ return ECGROUPNOTALLOWED;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * Finds the first rule in the cached list that matches the given UID or GID,
+ * and returns a pointer to that rule. This function uses rl_lock.
+ *
+ * This function may NOT be thread safe.
+ * @param uid The UID to match
+ * @param gid The GID to match
+ * @return Pointer to the first matching rule, or NULL if no match
+ * TODO: Determine thread-safeness and fix if not safe.
+ */
+static struct cgroup_rule *cgroup_find_matching_rule_uid_gid(const uid_t uid,
+ const gid_t gid)
+{
+ /* Return value */
+ struct cgroup_rule *ret = rl.head;
+
+ /* Temporary user data */
+ struct passwd *usr = NULL;
+
+ /* Temporary group data */
+ struct group *grp = NULL;
+
+ /* Temporary string pointer */
+ char *sp = NULL;
+
+ /* Loop variable */
+ int i = 0;
+
+ pthread_rwlock_wrlock(&rl_lock);
+ while (ret) {
+ /* The wildcard rule always matches. */
+ if ((ret->uid == CGRULE_WILD) && (ret->gid == CGRULE_WILD)) {
+ goto finished;
+ }
+
+ /* This is the simple case of the UID matching. */
+ if (ret->uid == uid) {
+ goto finished;
+ }
+
+ /* This is the simple case of the GID matching. */
+ if (ret->gid == gid) {
+ goto finished;
+ }
+
+ /* If this is a group rule, the UID might be a member. */
+ if (ret->name[0] == '@') {
+ /* Get the group data. */
+ sp = &(ret->name[1]);
+ grp = getgrnam(sp);
+ if (!grp) {
+ continue;
+ }
+
+ /* Get the data for UID. */
+ usr = getpwuid(uid);
+ if (!usr) {
+ continue;
+ }
+
+ /* If UID is a member of group, we matched. */
+ for (i = 0; grp->gr_mem[i]; i++) {
+ if (!(strcmp(usr->pw_name, grp->gr_mem[i])))
+ goto finished;
+ }
+ }
+
+ /* If we haven't matched, try the next rule. */
+ ret = ret->next;
+ }
+
+ /* If we get here, no rules matched. */
+ ret = NULL;
+
+finished:
+ pthread_rwlock_unlock(&rl_lock);
+ return ret;
+}
+
+/**
+ * Changes the cgroup of a program based on the rules in the config file. If a
+ * rule exists for the given UID or GID, then the given PID is placed into the
+ * correct group. By default, this function parses the configuration file each
+ * time it is called.
+ *
+ * The flags can alter the behavior of this function:
+ * CGFLAG_USECACHE: Use cached rules instead of parsing the config file
+ *
+ * This function may NOT be thread safe.
+ * @param uid The UID to match
+ * @param gid The GID to match
+ * @param pid The PID of the process to move
+ * @param flags Bit flags to change the behavior, as defined above
+ * @return 0 on success, > 0 on error
+ * TODO: Determine thread-safeness and fix of not safe.
+ */
+int cgroup_change_cgroup_uid_gid_flags(const uid_t uid, const gid_t gid,
+ const pid_t pid, const int flags)
+{
+ /* Temporary pointer to a rule */
+ struct cgroup_rule *tmp = NULL;
+
+ /* Return codes */
+ int ret = 0;
+
+ /* We need to check this before doing anything else! */
+ if (!cgroup_initialized) {
+ cgroup_dbg("libcgroup is not initialized\n");
+ ret = ECGROUPNOTINITIALIZED;
+ goto finished;
+ }
+
+ /*
+ * If the user did not ask for cached rules, we must parse the
+ * configuration to find a matching rule (if one exists). Else, we'll
+ * find the first match in the cached list (rl).
+ */
+ if (!(flags & CGFLAG_USECACHE)) {
+ cgroup_dbg("Not using cached rules for PID %d.\n", pid);
+ ret = cgroup_parse_rules(false, uid, gid);
+
+ /* The configuration file has an error! We must exit now. */
+ if (ret != -1 && ret != 0) {
+ cgroup_dbg("Failed to parse the configuration"
+ " rules.\n");
+ goto finished;
+ }
+
+ /* We did not find a matching rule, so we're done. */
+ if (ret == 0) {
+ cgroup_dbg("No rule found to match PID: %d, UID: %d, "
+ "GID: %d\n", pid, uid, gid);
+ goto finished;
+ }
+
+ /* Otherwise, we did match a rule and it's in trl. */
+ tmp = trl.head;
+ } else {
+ /* Find the first matching rule in the cached list. */
+ tmp = cgroup_find_matching_rule_uid_gid(uid, gid);
+ if (!tmp) {
+ cgroup_dbg("No rule found to match PID: %d, UID: %d, "
+ "GID: %d\n", pid, uid, gid);
+ ret = 0;
+ goto finished;
+ }
+ }
+ cgroup_dbg("Found matching rule %s for PID: %d, UID: %d, GID: %d\n",
+ tmp->name, pid, uid, gid);
+
+ /* If we are here, then we found a matching rule, so execute it. */
+ do {
+ cgroup_dbg("Executing rule %s for PID %d... ", tmp->name, pid);
+ ret = cgroup_change_cgroup_path(tmp->destination,
+ pid, tmp->controllers);
+ if (ret) {
+ cgroup_dbg("FAILED! (Error Code: %d)\n", ret);
+ goto finished;
+ }
+ cgroup_dbg("OK!\n");
+
+ /* Now, check for multi-line rules. As long as the "next"
+ * rule starts with '%', it's actually part of the rule that
+ * we just executed.
+ */
+ tmp = tmp->next;
+ } while (tmp && (tmp->name[0] == '%'));
+
+finished:
+ return ret;
+}
+
+/**
+ * Provides backwards-compatibility with older versions of the API. This
+ * function is deprecated, and cgroup_change_cgroup_uid_gid_flags() should be
+ * used instead. In fact, this function simply calls the newer one with flags
+ * set to 0 (none).
+ * @param uid The UID to match
+ * @param gid The GID to match
+ * @param pid The PID of the process to move
+ * @return 0 on success, > 0 on error
+ *
+ */
+int cgroup_change_cgroup_uid_gid(uid_t uid, gid_t gid, pid_t pid)
+{
+ return cgroup_change_cgroup_uid_gid_flags(uid, gid, pid, 0);
+}
+
+/**
+ * Changes the cgroup of a program based on the path provided. In this case,
+ * the user must already know into which cgroup the task should be placed and
+ * no rules will be parsed.
+ *
+ * returns 0 on success.
+ */
+int cgroup_change_cgroup_path(char *dest, pid_t pid, char *controllers[])
+{
+ int ret;
+ struct cgroup cgroup;
+
+ if (!cgroup_initialized) {
+ cgroup_dbg("libcgroup is not initialized\n");
+ return ECGROUPNOTINITIALIZED;
+ }
+ memset(&cgroup, 0, sizeof(struct cgroup));
+
+ ret = cg_prepare_cgroup(&cgroup, pid, dest, controllers);
+ if (ret)
+ return ret;
+ /* Add task to cgroup */
+ ret = cgroup_attach_task_pid(&cgroup, pid);
+ if (ret) {
+ cgroup_dbg("cgroup_attach_task_pid failed:%d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+/**
+ * Print the cached rules table. This function should be called only after
+ * first calling cgroup_parse_config(), but it will work with an empty rule
+ * list.
+ * @param fp The file stream to print to
+ */
+void cgroup_print_rules_config(FILE *fp)
+{
+ /* Iterator */
+ struct cgroup_rule *itr = NULL;
+
+ /* Loop variable */
+ int i = 0;
+
+ pthread_rwlock_rdlock(&rl_lock);
+
+ if (!(rl.head)) {
+ fprintf(fp, "The rules table is empty.\n\n");
+ pthread_rwlock_unlock(&rl_lock);
+ return;
+ }
+
+ itr = rl.head;
+ while (itr) {
+ fprintf(fp, "Rule: %s\n", itr->name);
+
+ if (itr->uid == CGRULE_WILD)
+ fprintf(fp, " UID: any\n");
+ else if (itr->uid == CGRULE_INVALID)
+ fprintf(fp, " UID: N/A\n");
+ else
+ fprintf(fp, " UID: %d\n", itr->uid);
+
+ if (itr->gid == CGRULE_WILD)
+ fprintf(fp, " GID: any\n");
+ else if (itr->gid == CGRULE_INVALID)
+ fprintf(fp, " GID: N/A\n");
+ else
+ fprintf(fp, " GID: %d\n", itr->gid);
+
+ fprintf(fp, " DEST: %s\n", itr->destination);
+
+ fprintf(fp, " CONTROLLERS:\n");
+ for (i = 0; i < MAX_MNT_ELEMENTS; i++) {
+ if (itr->controllers[i]) {
+ fprintf(fp, " %s\n", itr->controllers[i]);
+ }
+ }
+ fprintf(fp, "\n");
+ itr = itr->next;
+ }
+ pthread_rwlock_unlock(&rl_lock);
+}
+
+/**
+ * Reloads the rules list, using the given configuration file. This function
+ * is probably NOT thread safe (calls cgroup_parse_rules()).
+ * @return 0 on success, > 0 on failure
+ */
+int cgroup_reload_cached_rules()
+{
+ /* Return codes */
+ int ret = 0;
+
+ cgroup_dbg("Reloading cached rules from %s.\n", CGRULES_CONF_FILE);
+ if ((ret = cgroup_parse_rules(true, CGRULE_INVALID, CGRULE_INVALID))) {
+ cgroup_dbg("Error parsing configuration file \"%s\": %d.\n",
+ CGRULES_CONF_FILE, ret);
+ ret = ECGROUPPARSEFAIL;
+ goto finished;
+ }
+
+ #ifdef CGROUP_DEBUG
+ cgroup_print_rules_config(stdout);
+ #endif
+
+finished:
+ return ret;
+}
+
+/**
+ * Initializes the rules cache.
+ * @return 0 on success, > 0 on error
+ */
+int cgroup_init_rules_cache()
+{
+ /* Return codes */
+ int ret = 0;
+
+ /* Attempt to read the configuration file and cache the rules. */
+ ret = cgroup_parse_rules(true, CGRULE_INVALID, CGRULE_INVALID);
+ if (ret) {
+ cgroup_dbg("Could not initialize rule cache, error was: %d\n",
+ ret);
+ cgroup_rules_loaded = false;
+ } else {
+ cgroup_rules_loaded = true;
+ }
+
+ return ret;
+}
+
+/**
+ * cgroup_get_current_controller_path
+ * @pid: pid of the current process for which the path is to be determined
+ * @controller: name of the controller for which to determine current path
+ * @current_path: a pointer that is filled with the value of the current
+ * path as seen in /proc/<pid>/cgroup
+ */
+int cgroup_get_current_controller_path(pid_t pid, const char *controller,
+ char **current_path)
+{
+ char *path = NULL;
+ int ret;
+ FILE *pid_cgroup_fd = NULL;
+
+ if (!controller)
+ return ECGOTHER;
+
+ if (!cgroup_initialized) {
+ cgroup_dbg("libcgroup is not initialized\n");
+ return ECGROUPNOTINITIALIZED;
+ }
+
+ ret = asprintf(&path, "/proc/%d/cgroup", pid);
+ if (ret <= 0) {
+ cgroup_dbg("cannot allocate memory (/proc/pid/cgroup) ret %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = ECGROUPNOTEXIST;
+ pid_cgroup_fd = fopen(path, "r");
+ if (!pid_cgroup_fd)
+ goto cleanup_path;
+
+ /*
+ * Why do we grab the cg_mount_table_lock?, the reason is that
+ * the cgroup of a pid can change via the cgroup_attach_task_pid()
+ * call. To make sure, we return consitent and safe results,
+ * we acquire the lock upfront. We can optimize by acquiring
+ * and releasing the lock in the while loop, but that
+ * will be more expensive.
+ */
+ pthread_rwlock_rdlock(&cg_mount_table_lock);
+ while (!feof(pid_cgroup_fd)) {
+ char controllers[FILENAME_MAX];
+ char cgroup_path[FILENAME_MAX];
+ int num;
+ char *savedptr;
+ char *token;
+
+ ret = fscanf(pid_cgroup_fd, "%d:%[^:]:%s\n", &num, controllers,
+ cgroup_path);
+ /*
+ * Magic numbers like "3" seem to be integrating into
+ * my daily life, I need some magic to help make them
+ * disappear :)
+ */
+ if (ret != 3 || ret == EOF) {
+ cgroup_dbg("read failed for pid_cgroup_fd ret %d\n",
+ ret);
+ last_errno = errno;
+ ret = ECGOTHER;
+ goto done;
+ }
+
+ token = strtok_r(controllers, ",", &savedptr);
+ do {
+ if (strncmp(controller, token, strlen(controller) + 1)
+ == 0) {
+ *current_path = strdup(cgroup_path);
+ if (!*current_path) {
+ last_errno = errno;
+ ret = ECGOTHER;
+ goto done;
+ }
+ ret = 0;
+ goto done;
+ }
+ token = strtok_r(NULL, ",", &savedptr);
+ } while (token);
+ }
+
+done:
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ fclose(pid_cgroup_fd);
+cleanup_path:
+ free(path);
+ return ret;
+}
+
+char *cgroup_strerror(int code)
+{
+ assert((code >= ECGROUPNOTCOMPILED) && (code < ECGSENTINEL));
+ if (code == ECGOTHER) {
+ snprintf(errtext, MAXLEN, "%s: error message: %s",
+ cgroup_strerror_codes[code % ECGROUPNOTCOMPILED],
+ strerror(cgroup_get_last_errno()));
+ return errtext;
+ }
+ return cgroup_strerror_codes[code % ECGROUPNOTCOMPILED];
+}
+
+/**
+ * Return last errno, which caused ECGOTHER error.
+ */
+int cgroup_get_last_errno()
+{
+ return last_errno;
+}
+
+
+static int cg_walk_node(FTS *fts, FTSENT *ent, const int depth,
+ struct cgroup_file_info *info)
+{
+ int ret = 0;
+
+ if (!cgroup_initialized)
+ return ECGROUPNOTINITIALIZED;
+
+ cgroup_dbg("seeing file %s\n", ent->fts_path);
+
+ info->path = ent->fts_name;
+ info->parent = ent->fts_parent->fts_name;
+ info->full_path = ent->fts_path;
+ info->depth = ent->fts_level;
+ info->type = CGROUP_FILE_TYPE_OTHER;
+
+ if (depth && (info->depth > depth))
+ return 0;
+
+ switch (ent->fts_info) {
+ case FTS_DNR:
+ case FTS_ERR:
+ errno = ent->fts_errno;
+ break;
+ case FTS_D:
+ info->type = CGROUP_FILE_TYPE_DIR;
+ break;
+ case FTS_DC:
+ case FTS_NSOK:
+ case FTS_NS:
+ case FTS_DP:
+ break;
+ case FTS_F:
+ info->type = CGROUP_FILE_TYPE_FILE;
+ break;
+ case FTS_DEFAULT:
+ break;
+ }
+ return ret;
+}
+
+int cgroup_walk_tree_next(const int depth, void **handle,
+ struct cgroup_file_info *info, int base_level)
+{
+ int ret = 0;
+ FTS *fts = *(FTS **)handle;
+ FTSENT *ent;
+
+ if (!cgroup_initialized)
+ return ECGROUPNOTINITIALIZED;
+
+ if (!handle)
+ return ECGINVAL;
+ ent = fts_read(fts);
+ if (!ent)
+ return ECGEOF;
+ if (!base_level && depth)
+ base_level = ent->fts_level + depth;
+ ret = cg_walk_node(fts, ent, base_level, info);
+ *handle = fts;
+ return ret;
+}
+
+int cgroup_walk_tree_end(void **handle)
+{
+ FTS *fts = *(FTS **)handle;
+
+ if (!cgroup_initialized)
+ return ECGROUPNOTINITIALIZED;
+
+ if (!handle)
+ return ECGINVAL;
+ fts_close(fts);
+ return 0;
+}
+
+/*
+ * TODO: Need to decide a better place to put this function.
+ */
+int cgroup_walk_tree_begin(char *controller, char *base_path, const int depth,
+ void **handle, struct cgroup_file_info *info,
+ int *base_level)
+{
+ int ret = 0;
+ cgroup_dbg("path is %s\n", base_path);
+ char *cg_path[2];
+ char full_path[FILENAME_MAX];
+ FTSENT *ent;
+ FTS *fts;
+
+ if (!cgroup_initialized)
+ return ECGROUPNOTINITIALIZED;
+
+ if (!cg_build_path(base_path, full_path, controller))
+ return ECGOTHER;
+
+ *base_level = 0;
+ cg_path[0] = full_path;
+ cg_path[1] = NULL;
+
+ fts = fts_open(cg_path, FTS_LOGICAL | FTS_NOCHDIR |
+ FTS_NOSTAT, NULL);
+ ent = fts_read(fts);
+ if (!ent) {
+ cgroup_dbg("fts_read failed\n");
+ return ECGINVAL;
+ }
+ if (!*base_level && depth)
+ *base_level = ent->fts_level + depth;
+ ret = cg_walk_node(fts, ent, *base_level, info);
+ *handle = fts;
+ return ret;
+}
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..b188985
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,496 @@
+/*
+ * Copyright IBM Corporation. 2007
+ *
+ * Authors: Balbir Singh <balbir@linux.vnet.ibm.com>
+ * Dhaval Giani <dhaval@linux.vnet.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * TODOs:
+ * 1. Implement our own hashing scheme
+ * 2. Add namespace support
+ * 3. Add support for parsing cgroup filesystem and creating a
+ * config out of it.
+ *
+ * Code initiated and designed by Balbir Singh. All faults are most likely
+ * his mistake.
+ *
+ * Cleanup and changes to use the "official" structures and functions made
+ * by Dhaval Giani. All faults will still be Balbir's mistake :)
+ */
+
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <grp.h>
+#include <libcgroup.h>
+#include <libcgroup-internal.h>
+#include <limits.h>
+#include <pwd.h>
+#include <pthread.h>
+#include <search.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#define MAX_CGROUPS 1024
+
+extern FILE *yyin;
+extern int yyparse(void);
+
+/*
+ * The basic global data structures.
+ *
+ * config_mount_table -> Where what controller is mounted
+ * table_index -> Where in the table are we.
+ * config_table_lock -> Serializing access to config_mount_table.
+ * cgroup_table -> Which cgroups have to be created.
+ * cgroup_table_index -> Where in the cgroup_table we are.
+ */
+static struct cg_mount_table_s config_mount_table[CG_CONTROLLER_MAX];
+static int config_table_index;
+static pthread_rwlock_t config_table_lock = PTHREAD_RWLOCK_INITIALIZER;
+static struct cgroup config_cgroup_table[MAX_CGROUPS];
+int cgroup_table_index;
+
+/*
+ * Needed for the type while mounting cgroupfs.
+ */
+static const char cgroup_filesystem[] = "cgroup";
+
+/*
+ * NOTE: All these functions return 1 on success
+ * and not 0 as is the library convention
+ */
+
+/*
+ * This call just sets the name of the cgroup. It will
+ * always be called in the end, because the parser will
+ * work bottom up.
+ */
+int cgroup_config_insert_cgroup(char *cg_name)
+{
+ struct cgroup *config_cgroup =
+ &config_cgroup_table[cgroup_table_index];
+
+ strncpy(config_cgroup->name, cg_name, FILENAME_MAX);
+ /*
+ * Since this will be the last part to be parsed.
+ */
+ cgroup_table_index++;
+ free(cg_name);
+ return 1;
+}
+
+/*
+ * This function sets the various controller's control
+ * files. It will always append values for cgroup_table_index
+ * entry in the cgroup_table. The index is incremented in
+ * cgroup_config_insert_cgroup
+ */
+int cgroup_config_parse_controller_options(char *controller, char *name_value)
+{
+ char *buffer = NULL;
+ char *name, *value;
+ struct cgroup_controller *cgc;
+ int error;
+ struct cgroup *config_cgroup =
+ &config_cgroup_table[cgroup_table_index];
+ char *nm_pairs, *nv_buf;
+
+ cgroup_dbg("Adding controller %s, value %s\n", controller, name_value);
+ cgc = cgroup_add_controller(config_cgroup, controller);
+
+ if (!cgc)
+ goto parse_error;
+
+ /*
+ * Did we just specify the controller to create the correct
+ * set of directories, without setting any values?
+ */
+ if (!name_value)
+ goto done;
+
+ nm_pairs = strtok_r(name_value, ":", &nv_buf);
+ cgroup_dbg("[1] name value pair being processed is %s\n", nm_pairs);
+
+ name = strtok_r(nm_pairs, " ", &buffer);
+
+ if (!name)
+ goto parse_error;
+
+ value = strtok_r(NULL, " ", &buffer);
+
+ if (!value)
+ goto parse_error;
+
+ cgroup_dbg("name is %s, value is %s\n", name, value);
+ error = cgroup_add_value_string(cgc, name, value);
+
+ if (error)
+ goto parse_error;
+
+ while ((nm_pairs = strtok_r(NULL, ":", &nv_buf))) {
+ cgroup_dbg("[2] name value pair being processed is %s\n",
+ nm_pairs);
+ name = strtok_r(nm_pairs, " ", &buffer);
+
+ if (!name)
+ goto parse_error;
+
+ value = strtok_r(NULL, " ", &buffer);
+
+ if (!value)
+ goto parse_error;
+
+ cgroup_dbg("name is %s, value is %s\n", name, value);
+ error = cgroup_add_value_string(cgc, name, value);
+
+ if (error)
+ goto parse_error;
+ }
+
+done:
+ free(controller);
+ free(name_value);
+ return 1;
+
+parse_error:
+ free(controller);
+ free(name_value);
+ cgroup_delete_cgroup(config_cgroup, 1);
+ cgroup_table_index--;
+ return 0;
+}
+
+/*
+ * Sets the tasks file's uid and gid
+ */
+int cgroup_config_group_task_perm(char *perm_type, char *value)
+{
+ struct passwd *pw, *pw_buffer;
+ struct group *group, *group_buffer;
+ int error;
+ long val = atoi(value);
+ char buffer[CGROUP_BUFFER_LEN];
+ struct cgroup *config_cgroup =
+ &config_cgroup_table[cgroup_table_index];
+
+ if (!strcmp(perm_type, "uid")) {
+ if (!val) {
+ pw = (struct passwd *) malloc(sizeof(struct passwd));
+
+ if (!pw)
+ goto group_task_error;
+
+ error = getpwnam_r(value, pw, buffer, CGROUP_BUFFER_LEN,
+ &pw_buffer);
+ if (pw_buffer == NULL) {
+ free(pw);
+ goto group_task_error;
+ }
+
+ val = pw->pw_uid;
+ free(pw);
+ }
+ config_cgroup->tasks_uid = val;
+ }
+
+ if (!strcmp(perm_type, "gid")) {
+ if (!val) {
+ group = (struct group *) malloc(sizeof(struct group));
+
+ if (!group)
+ goto group_task_error;
+
+ error = getgrnam_r(value, group, buffer,
+ CGROUP_BUFFER_LEN, &group_buffer);
+
+ if (group_buffer == NULL) {
+ free(group);
+ goto group_task_error;
+ }
+
+ val = group->gr_gid;
+ free(group);
+ }
+ config_cgroup->tasks_gid = val;
+ }
+
+ free(perm_type);
+ free(value);
+ return 1;
+
+group_task_error:
+ free(perm_type);
+ free(value);
+ cgroup_delete_cgroup(config_cgroup, 1);
+ cgroup_table_index--;
+ return 0;
+}
+
+/*
+ * Set the control file's uid/gid
+ */
+int cgroup_config_group_admin_perm(char *perm_type, char *value)
+{
+ struct passwd *pw, *pw_buffer;
+ struct group *group, *group_buffer;
+ int error;
+ struct cgroup *config_cgroup =
+ &config_cgroup_table[cgroup_table_index];
+ long val = atoi(value);
+ char buffer[CGROUP_BUFFER_LEN];
+
+ if (!strcmp(perm_type, "uid")) {
+ if (!val) {
+ pw = (struct passwd *) malloc(sizeof(struct passwd));
+
+ if (!pw)
+ goto admin_error;
+
+ error = getpwnam_r(value, pw, buffer, CGROUP_BUFFER_LEN,
+ &pw_buffer);
+ if (pw_buffer == NULL) {
+ free(pw);
+ goto admin_error;
+ }
+
+ val = pw->pw_uid;
+ free(pw);
+ }
+ config_cgroup->control_uid = val;
+ }
+
+ if (!strcmp(perm_type, "gid")) {
+ if (!val) {
+ group = (struct group *) malloc(sizeof(struct group));
+
+ if (!group)
+ goto admin_error;
+
+ error = getgrnam_r(value, group, buffer,
+ CGROUP_BUFFER_LEN, &group_buffer);
+
+ if (group_buffer == NULL) {
+ free(group);
+ goto admin_error;
+ }
+
+ val = group->gr_gid;
+ free(group);
+ }
+ config_cgroup->control_gid = val;
+ }
+
+ free(perm_type);
+ free(value);
+ return 1;
+
+admin_error:
+ free(perm_type);
+ free(value);
+ cgroup_delete_cgroup(config_cgroup, 1);
+ cgroup_table_index--;
+ return 0;
+}
+
+/*
+ * The moment we have found the controller's information
+ * insert it into the config_mount_table.
+ */
+int cgroup_config_insert_into_mount_table(char *name, char *mount_point)
+{
+ int i;
+
+ if (config_table_index >= CG_CONTROLLER_MAX)
+ return 0;
+
+ pthread_rwlock_wrlock(&config_table_lock);
+
+ /*
+ * Merge controller names with the same mount point
+ */
+ for (i = 0; i < config_table_index; i++) {
+ if (strcmp(config_mount_table[i].path, mount_point) == 0) {
+ char *cname = config_mount_table[i].name;
+ strncat(cname, ",", FILENAME_MAX - strlen(cname) - 1);
+ strncat(cname, name,
+ FILENAME_MAX - strlen(cname) - 1);
+ goto done;
+ }
+ }
+
+ strcpy(config_mount_table[config_table_index].name, name);
+ strcpy(config_mount_table[config_table_index].path, mount_point);
+ config_table_index++;
+done:
+ pthread_rwlock_unlock(&config_table_lock);
+ free(name);
+ free(mount_point);
+ return 1;
+}
+
+/*
+ * Cleanup all the data from the config_mount_table
+ */
+void cgroup_config_cleanup_mount_table(void)
+{
+ memset(&config_mount_table, 0,
+ sizeof(struct cg_mount_table_s) * CG_CONTROLLER_MAX);
+}
+
+/*
+ * Start mounting the mount table.
+ */
+int cgroup_config_mount_fs()
+{
+ int ret;
+ struct stat buff;
+ int i;
+
+ for (i = 0; i < config_table_index; i++) {
+ struct cg_mount_table_s *curr = &(config_mount_table[i]);
+
+ ret = stat(curr->path, &buff);
+
+ if (ret < 0 && errno != ENOENT) {
+ last_errno = errno;
+ return ECGOTHER;
+ }
+
+ if (errno == ENOENT) {
+ ret = mkdir(curr->path,
+ S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+ if (ret < 0) {
+ last_errno = errno;
+ return ECGOTHER;
+ }
+ } else if (!S_ISDIR(buff.st_mode)) {
+ errno = ENOTDIR;
+ last_errno = errno;
+ return ECGOTHER;
+ }
+
+ ret = mount(cgroup_filesystem, curr->path, cgroup_filesystem,
+ 0, curr->name);
+
+ if (ret < 0)
+ return ECGMOUNTFAIL;
+ }
+ return 0;
+}
+
+/*
+ * Actually create the groups once the parsing has been finished.
+ */
+int cgroup_config_create_groups()
+{
+ int error = 0;
+ int i;
+
+ for (i = 0; i < cgroup_table_index; i++) {
+ struct cgroup *cgroup = &config_cgroup_table[i];
+ error = cgroup_create_cgroup(cgroup, 0);
+ cgroup_dbg("creating group %s, error %d\n", cgroup->name,
+ error);
+ if (error)
+ return error;
+ }
+ return error;
+}
+
+/*
+ * Destroy the cgroups
+ */
+int cgroup_config_destroy_groups(void)
+{
+ int error = 0;
+ int i;
+
+ for (i = 0; i < cgroup_table_index; i++) {
+ struct cgroup *cgroup = &config_cgroup_table[i];
+ error = cgroup_delete_cgroup(cgroup, 0);
+ if (error)
+ return error;
+ }
+ return error;
+}
+
+/*
+ * Unmount the controllers
+ */
+int cgroup_config_unmount_controllers(void)
+{
+ int i;
+ int error;
+
+ for (i = 0; i < config_table_index; i++) {
+ /*
+ * We ignore failures and ensure that all mounted
+ * containers are unmounted
+ */
+ error = umount(config_mount_table[i].path);
+ if (error < 0)
+ cgroup_dbg("Unmount failed\n");
+ error = rmdir(config_mount_table[i].path);
+ if (error < 0)
+ cgroup_dbg("rmdir failed\n");
+ }
+
+ return 0;
+}
+
+/*
+ * The main function which does all the setup of the data structures
+ * and finally creates the cgroups
+ */
+int cgroup_config_load_config(const char *pathname)
+{
+ int error;
+ yyin = fopen(pathname, "r");
+
+ if (!yyin) {
+ cgroup_dbg("Failed to open file %s\n", pathname);
+ last_errno = errno;
+ return ECGOTHER;
+ }
+
+ if (yyparse() != 0) {
+ cgroup_dbg("Failed to parse file %s\n", pathname);
+ return ECGROUPPARSEFAIL;
+ }
+
+ error = cgroup_config_mount_fs();
+ if (error)
+ goto err_mnt;
+
+ error = cgroup_init();
+ if (error)
+ goto err_mnt;
+
+ error = cgroup_config_create_groups();
+ cgroup_dbg("creating all cgroups now, error=%d\n", error);
+ if (error)
+ goto err_grp;
+
+ fclose(yyin);
+ return 0;
+err_grp:
+ cgroup_config_destroy_groups();
+err_mnt:
+ cgroup_config_unmount_controllers();
+ fclose(yyin);
+ return error;
+}
diff --git a/src/daemon/Makefile.am b/src/daemon/Makefile.am
new file mode 100644
index 0000000..17d31d9
--- /dev/null
+++ b/src/daemon/Makefile.am
@@ -0,0 +1,6 @@
+INCLUDES = -I $(top_srcdir)/include
+sbin_PROGRAMS = cgrulesengd
+
+cgrulesengd_SOURCES = cgrulesengd.c cgrulesengd.h
+cgrulesengd_LDADD = $(top_srcdir)/src/.libs/libcgroup.la
+cgrulesengd_LDFLAGS = -L$(top_srcdir)/src/.libs
diff --git a/src/daemon/Makefile.in b/src/daemon/Makefile.in
new file mode 100644
index 0000000..279d7a4
--- /dev/null
+++ b/src/daemon/Makefile.in
@@ -0,0 +1,468 @@
+# Makefile.in generated by automake 1.10.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+sbin_PROGRAMS = cgrulesengd$(EXEEXT)
+subdir = src/daemon
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+am__installdirs = "$(DESTDIR)$(sbindir)"
+sbinPROGRAMS_INSTALL = $(INSTALL_PROGRAM)
+PROGRAMS = $(sbin_PROGRAMS)
+am_cgrulesengd_OBJECTS = cgrulesengd.$(OBJEXT)
+cgrulesengd_OBJECTS = $(am_cgrulesengd_OBJECTS)
+cgrulesengd_DEPENDENCIES = $(top_srcdir)/src/.libs/libcgroup.la
+cgrulesengd_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(cgrulesengd_LDFLAGS) $(LDFLAGS) -o $@
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
+ $(LDFLAGS) -o $@
+SOURCES = $(cgrulesengd_SOURCES)
+DIST_SOURCES = $(cgrulesengd_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO = @ECHO@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+F77 = @F77@
+FFLAGS = @FFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBRARY_VERSION = @LIBRARY_VERSION@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+YACC = @YACC@
+YFLAGS = @YFLAGS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_F77 = @ac_ct_F77@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = -I $(top_srcdir)/include
+cgrulesengd_SOURCES = cgrulesengd.c cgrulesengd.h
+cgrulesengd_LDADD = $(top_srcdir)/src/.libs/libcgroup.la
+cgrulesengd_LDFLAGS = -L$(top_srcdir)/src/.libs
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \
+ && exit 0; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/daemon/Makefile'; \
+ cd $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/daemon/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+install-sbinPROGRAMS: $(sbin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ test -z "$(sbindir)" || $(MKDIR_P) "$(DESTDIR)$(sbindir)"
+ @list='$(sbin_PROGRAMS)'; for p in $$list; do \
+ p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \
+ if test -f $$p \
+ || test -f $$p1 \
+ ; then \
+ f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(sbinPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(sbindir)/$$f'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(sbinPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(sbindir)/$$f" || exit 1; \
+ else :; fi; \
+ done
+
+uninstall-sbinPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sbin_PROGRAMS)'; for p in $$list; do \
+ f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \
+ echo " rm -f '$(DESTDIR)$(sbindir)/$$f'"; \
+ rm -f "$(DESTDIR)$(sbindir)/$$f"; \
+ done
+
+clean-sbinPROGRAMS:
+ @list='$(sbin_PROGRAMS)'; for p in $$list; do \
+ f=`echo $$p|sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f $$p $$f"; \
+ rm -f $$p $$f ; \
+ done
+cgrulesengd$(EXEEXT): $(cgrulesengd_OBJECTS) $(cgrulesengd_DEPENDENCIES)
+ @rm -f cgrulesengd$(EXEEXT)
+ $(cgrulesengd_LINK) $(cgrulesengd_OBJECTS) $(cgrulesengd_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cgrulesengd.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonemtpy = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$tags $$unique; \
+ fi
+ctags: CTAGS
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ test -z "$(CTAGS_ARGS)$$tags$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$tags $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && cd $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) $$here
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
+ fi; \
+ cp -pR $$d/$$file $(distdir)$$dir || exit 1; \
+ else \
+ test -f $(distdir)/$$file \
+ || cp -p $$d/$$file $(distdir)/$$file \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+ for dir in "$(DESTDIR)$(sbindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-sbinPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-exec-am: install-sbinPROGRAMS
+
+install-html: install-html-am
+
+install-info: install-info-am
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-ps: install-ps-am
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-sbinPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+ clean-libtool clean-sbinPROGRAMS ctags distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-sbinPROGRAMS install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags uninstall uninstall-am uninstall-sbinPROGRAMS
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/daemon/cgrulesengd.c b/src/daemon/cgrulesengd.c
new file mode 100644
index 0000000..8efdce1
--- /dev/null
+++ b/src/daemon/cgrulesengd.c
@@ -0,0 +1,793 @@
+/*
+ * Copyright Red Hat Inc. 2008
+ *
+ * Author: Steve Olivieri <sjo@redhat.com>
+ * Author: Vivek Goyal <vgoyal@redhat.com>
+ *
+ * Some part of the programs have been derived from Dhaval Giani's posting
+ * for daemon to place the task in right container. Original copyright notice
+ * follows.
+ *
+ * Copyright IBM Corporation, 2007
+ * Author: Dhaval Giani <dhaval <at> linux.vnet.ibm.com>
+ * Derived from test_cn_proc.c by Matt Helsley
+ * Original copyright notice follows
+ *
+ * Copyright (C) Matt Helsley, IBM Corp. 2005
+ * Derived from fcctl.c by Guillaume Thouvenin
+ * Original copyright notice follows:
+ *
+ * Copyright (C) 2005 BULL SA.
+ * Written by Guillaume Thouvenin <guillaume.thouvenin <at> bull.net>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * TODO Stop using netlink for communication (or at least rewrite that part).
+ */
+
+#include "libcgroup.h"
+#include "cgrulesengd.h"
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/syslog.h>
+#include <string.h>
+#include <linux/netlink.h>
+#include <signal.h>
+#include <time.h>
+#include <syslog.h>
+#include <getopt.h>
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <linux/connector.h>
+#include <linux/cn_proc.h>
+
+/* Log file, NULL if logging to file is disabled */
+FILE* logfile;
+
+/* Log facility, 0 if logging to syslog is disabled */
+int logfacility;
+
+/* Current log level */
+int loglevel;
+
+/**
+ * Prints the usage information for this program and, optionally, an error
+ * message. This function uses vfprintf.
+ * @param fd The file stream to print to
+ * @param msg The error message to print (printf style)
+ * @param ... Any args to msg (printf style)
+ */
+void usage(FILE* fd, const char* msg, ...)
+{
+ /* List of args to msg */
+ va_list ap;
+
+ /* Put all args after msg into the list. */
+ va_start(ap, msg);
+
+ if (msg)
+ vfprintf(fd, msg, ap);
+ fprintf(fd, "\n");
+ fprintf(fd, "cgrulesengd -- a daemon for the cgroups rules engine\n\n");
+ fprintf(fd, "Usage : cgrulesengd [options]\n\n");
+ fprintf(fd, " options :\n");
+ fprintf(fd, " -q | --quiet quiet mode\n"
+ " -v | --verbose verbose mode\n"
+ " -f <path> | --logfile=<path> write log to file\n"
+ " -s[facility] | --syslog=[facility] write log to syslog\n"
+ " -n | --nodaemom don't fork daemon\n"
+ " -d | --debug same as -v -v -n -f -\n"
+ " -Q | --nolog disable logging\n"
+ " -h | --help show this help\n\n"
+ );
+ va_end(ap);
+}
+
+/**
+ * Prints a formatted message (like printf()) to all log destinations.
+ * Flushes the file stream's buffer so that the message is immediately
+ * readable.
+ * @param level The log level (LOG_EMERG ... LOG_DEBUG)
+ * @param format The format for the message (printf style)
+ * @param ... Any args to format (printf style)
+ */
+void flog(int level, const char *format, ...)
+{
+ /* List of args to format */
+ va_list ap;
+
+ /* Check the log level */
+ if (level > loglevel)
+ return;
+
+ if (logfile) {
+ /* Print the message to the given stream. */
+ va_start(ap, format);
+ vfprintf(logfile, format, ap);
+ va_end(ap);
+ fprintf(logfile, "\n");
+
+ /*
+ * Flush the stream's buffer, so the data is readable
+ * immediately.
+ */
+ fflush(logfile);
+ }
+
+ if (logfacility) {
+ va_start(ap, format);
+ vsyslog(LOG_MAKEPRI(logfacility, level), format, ap);
+ va_end(ap);
+ }
+}
+
+/**
+ * Process an event from the kernel, and determine the correct UID/GID/PID to
+ * pass to libcgroup. Then, libcgroup will decide the cgroup to move the PID
+ * to, if any.
+ * @param ev The event to process
+ * @param type The type of event to process (part of ev)
+ * @return 0 on success, > 0 on failure
+ */
+int cgre_process_event(const struct proc_event *ev, const int type)
+{
+ /* Handle for the /proc/PID/status file */
+ FILE *f;
+
+ /* Path for /proc/PID/status file */
+ char path[FILENAME_MAX];
+
+ /* Temporary buffer */
+ char *buf = NULL;
+
+ /* UID data */
+ uid_t ruid, euid, suid, fsuid, log_uid = 0;
+
+ /* GID data */
+ gid_t rgid, egid, sgid, fsgid, log_gid = 0;
+
+ /* PID, just for logging */
+ pid_t log_pid = 0;
+
+ /* Return codes */
+ int ret = 0;
+
+ /*
+ * First, we need to open the /proc/PID/status file so that we can
+ * get the effective UID and GID for the process that we're working
+ * on. This process is probably not us, so we can't just call
+ * geteuid() or getegid().
+ */
+ sprintf(path, "/proc/%d/status", ev->event_data.id.process_pid);
+ f = fopen(path, "r");
+ if (!f) {
+ flog(LOG_WARNING, "Failed to open %s", path);
+ goto finished;
+ }
+
+ /* Now, we need to find either the eUID or the eGID of the process. */
+ buf = calloc(4096, sizeof(char));
+ if (!buf) {
+ flog(LOG_WARNING, "Failed to process event, out of"
+ "memory? Error: %s",
+ strerror(errno));
+ ret = errno;
+ fclose(f);
+ goto finished;
+ }
+ switch (type) {
+ case PROC_EVENT_UID:
+ /* Have the eUID, need to find the eGID. */
+ while (fgets(buf, 4096, f)) {
+ if (!strncmp(buf, "Gid:", 4)) {
+ sscanf((buf + 5), "%d%d%d%d", &rgid, &egid,
+ &sgid, &fsgid);
+ break;
+ }
+ memset(buf, '\0', 4096);
+ }
+ break;
+ case PROC_EVENT_GID:
+ /* Have the eGID, need to find the eUID. */
+ while (fgets(buf, 4096, f)) {
+ if (!strncmp(buf, "Uid:", 4)) {
+ sscanf((buf + 5), "%d%d%d%d", &ruid, &euid,
+ &suid, &fsuid);
+ break;
+ }
+ memset(buf, '\0', 4096);
+ }
+ break;
+ default:
+ flog(LOG_WARNING, "For some reason, we're processing a"
+ " non-UID/GID event. Something is wrong!");
+ break;
+ }
+ free(buf);
+ fclose(f);
+
+ /*
+ * Now that we have the UID, the GID, and the PID, we can make a call
+ * to libcgroup to change the cgroup for this PID.
+ */
+ switch (type) {
+ case PROC_EVENT_UID:
+ log_uid = ev->event_data.id.e.euid;
+ log_gid = egid;
+ log_pid = ev->event_data.id.process_pid;
+ ret = cgroup_change_cgroup_uid_gid_flags(
+ ev->event_data.id.e.euid,
+ egid, ev->event_data.id.process_pid,
+ CGFLAG_USECACHE);
+ break;
+ case PROC_EVENT_GID:
+ log_uid = euid;
+ log_gid = ev->event_data.id.e.egid;
+ log_pid = ev->event_data.id.process_pid;
+ ret = cgroup_change_cgroup_uid_gid_flags(euid,
+ ev->event_data.id.e.egid,
+ ev->event_data.id.process_pid,
+ CGFLAG_USECACHE);
+ break;
+ default:
+ break;
+ }
+
+ if (ret) {
+ /*
+ * TODO: add some supression, do not spam log when every group
+ * change fails
+ */
+ flog(LOG_WARNING, "Cgroup change for PID: %d, UID: %d, GID: %d"
+ " FAILED! (Error Code: %d)", log_pid, log_uid, log_gid,
+ ret);
+ } else {
+ flog(LOG_INFO, "Cgroup change for PID: %d, UID: %d, GID: %d OK",
+ log_pid, log_uid, log_gid);
+ }
+
+finished:
+ return ret;
+}
+
+/**
+ * Handle a netlink message. In the event of PROC_EVENT_UID or PROC_EVENT_GID,
+ * we pass the event along to cgre_process_event for further processing. All
+ * other events are ignored.
+ * @param cn_hdr The netlink message
+ * @return 0 on success, > 0 on error
+ */
+int cgre_handle_msg(struct cn_msg *cn_hdr)
+{
+ /* The event to consider */
+ struct proc_event *ev;
+
+ /* Return codes */
+ int ret = 0;
+
+ /* Get the event data. We only care about two event types. */
+ ev = (struct proc_event*)cn_hdr->data;
+ switch (ev->what) {
+ case PROC_EVENT_UID:
+ flog(LOG_DEBUG, "UID Event: PID = %d, tGID = %d, rUID = %d,"
+ " eUID = %d", ev->event_data.id.process_pid,
+ ev->event_data.id.process_tgid,
+ ev->event_data.id.r.ruid,
+ ev->event_data.id.e.euid);
+ ret = cgre_process_event(ev, PROC_EVENT_UID);
+ break;
+ case PROC_EVENT_GID:
+ flog(LOG_DEBUG, "GID Event: PID = %d, tGID = %d, rGID = %d,"
+ " eGID = %d", ev->event_data.id.process_pid,
+ ev->event_data.id.process_tgid,
+ ev->event_data.id.r.rgid,
+ ev->event_data.id.e.egid);
+ ret = cgre_process_event(ev, PROC_EVENT_GID);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+int cgre_create_netlink_socket_process_msg()
+{
+ int sk_nl;
+ int err;
+ struct sockaddr_nl my_nla, kern_nla, from_nla;
+ socklen_t from_nla_len;
+ char buff[BUFF_SIZE];
+ int rc = -1;
+ struct nlmsghdr *nl_hdr;
+ struct cn_msg *cn_hdr;
+ enum proc_cn_mcast_op *mcop_msg;
+ size_t recv_len = 0;
+
+ /*
+ * Create an endpoint for communication. Use the kernel user
+ * interface device (PF_NETLINK) which is a datagram oriented
+ * service (SOCK_DGRAM). The protocol used is the connector
+ * protocol (NETLINK_CONNECTOR)
+ */
+ sk_nl = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
+ if (sk_nl == -1) {
+ cgroup_dbg("socket sk_nl error");
+ return rc;
+ }
+
+ my_nla.nl_family = AF_NETLINK;
+ my_nla.nl_groups = CN_IDX_PROC;
+ my_nla.nl_pid = getpid();
+ my_nla.nl_pad = 0;
+
+ kern_nla.nl_family = AF_NETLINK;
+ kern_nla.nl_groups = CN_IDX_PROC;
+ kern_nla.nl_pid = 1;
+ kern_nla.nl_pad = 0;
+
+ err = bind(sk_nl, (struct sockaddr *)&my_nla, sizeof(my_nla));
+ if (err == -1) {
+ cgroup_dbg("binding sk_nl error");
+ goto close_and_exit;
+ }
+
+ nl_hdr = (struct nlmsghdr *)buff;
+ cn_hdr = (struct cn_msg *)NLMSG_DATA(nl_hdr);
+ mcop_msg = (enum proc_cn_mcast_op*)&cn_hdr->data[0];
+ cgroup_dbg("sending proc connector: PROC_CN_MCAST_LISTEN... ");
+ memset(buff, 0, sizeof(buff));
+ *mcop_msg = PROC_CN_MCAST_LISTEN;
+
+ /* fill the netlink header */
+ nl_hdr->nlmsg_len = SEND_MESSAGE_LEN;
+ nl_hdr->nlmsg_type = NLMSG_DONE;
+ nl_hdr->nlmsg_flags = 0;
+ nl_hdr->nlmsg_seq = 0;
+ nl_hdr->nlmsg_pid = getpid();
+
+ /* fill the connector header */
+ cn_hdr->id.idx = CN_IDX_PROC;
+ cn_hdr->id.val = CN_VAL_PROC;
+ cn_hdr->seq = 0;
+ cn_hdr->ack = 0;
+ cn_hdr->len = sizeof(enum proc_cn_mcast_op);
+ cgroup_dbg("sending netlink message len=%d, cn_msg len=%d\n",
+ nl_hdr->nlmsg_len, (int) sizeof(struct cn_msg));
+ if (send(sk_nl, nl_hdr, nl_hdr->nlmsg_len, 0) != nl_hdr->nlmsg_len) {
+ cgroup_dbg("failed to send proc connector mcast ctl op!\n");
+ goto close_and_exit;
+ }
+ cgroup_dbg("sent\n");
+
+ for(memset(buff, 0, sizeof(buff)), from_nla_len = sizeof(from_nla);
+ ; memset(buff, 0, sizeof(buff)), from_nla_len = sizeof(from_nla)) {
+ struct nlmsghdr *nlh = (struct nlmsghdr*)buff;
+ memcpy(&from_nla, &kern_nla, sizeof(from_nla));
+ recv_len = recvfrom(sk_nl, buff, BUFF_SIZE, 0,
+ (struct sockaddr*)&from_nla, &from_nla_len);
+ if (recv_len == ENOBUFS) {
+ flog(LOG_ERR, "ERROR: NETLINK BUFFER FULL, MESSAGE "
+ "DROPPED!");
+ continue;
+ }
+ if (recv_len < 1)
+ continue;
+ while (NLMSG_OK(nlh, recv_len)) {
+ cn_hdr = NLMSG_DATA(nlh);
+ if (nlh->nlmsg_type == NLMSG_NOOP)
+ continue;
+ if ((nlh->nlmsg_type == NLMSG_ERROR) ||
+ (nlh->nlmsg_type == NLMSG_OVERRUN))
+ break;
+ if(cgre_handle_msg(cn_hdr) < 0) {
+ goto close_and_exit;
+ }
+ if (nlh->nlmsg_type == NLMSG_DONE)
+ break;
+ nlh = NLMSG_NEXT(nlh, recv_len);
+ }
+ }
+
+close_and_exit:
+ close(sk_nl);
+ return rc;
+}
+
+/**
+ * Start logging. Opens syslog and/or log file and sets log level.
+ * @param logp Path of the log file, NULL if no log file was specified
+ * @param logf Syslog facility, NULL if no facility was specified
+ * @param logv Log verbosity, 2 is the default, 0 = no logging, 4 = everything
+ */
+static void cgre_start_log(const char *logp, int logf, int logv)
+{
+ /* Current system time */
+ time_t tm;
+
+ /* Log levels */
+ int loglevels[] = {
+ LOG_EMERG, /* -qq */
+ LOG_ERR, /* -q */
+ LOG_NOTICE, /* default */
+ LOG_INFO, /* -v */
+ LOG_DEBUG /* -vv */
+ };
+
+ /* Set default logging destination if nothing was specified */
+ if (!logp && !logf)
+ logf = LOG_DAEMON;
+
+ /* Open log file */
+ if (logp) {
+ if (strcmp("-", logp) == 0) {
+ logfile = stdout;
+ } else {
+ logfile = fopen(logp, "a");
+ if (!logfile) {
+ fprintf(stderr, "Failed to open log file %s,"
+ " error: %s. Continuing anyway.\n",
+ logp, strerror(errno));
+ logfile = stdout;
+ }
+ }
+ } else
+ logfile = NULL;
+
+ /* Open syslog */
+ if (logf) {
+ openlog("CGRE", LOG_CONS | LOG_PID, logf);
+ logfacility = logf;
+ } else
+ logfacility = 0;
+
+ /* Set the log level */
+ if (logv < 0)
+ logv = 0;
+ if (logv >= sizeof(loglevels)/sizeof(int))
+ logv = sizeof(loglevels)/sizeof(int)-1;
+
+ loglevel = loglevels[logv];
+
+ flog(LOG_DEBUG, "CGroup Rules Engine Daemon log started");
+ tm = time(0);
+ flog(LOG_DEBUG, "Current time: %s", ctime(&tm));
+ flog(LOG_DEBUG, "Opened log file: %s, log facility: %d, log level: %d",
+ logp, logfacility, loglevel);
+}
+
+
+/**
+ * Turns this program into a daemon. In doing so, we fork() and kill the
+ * parent process. Note too that stdout, stdin, and stderr are closed in
+ * daemon mode, and a file descriptor for a log file is opened.
+ * @param logp Path of the log file, NULL if no log file was specified
+ * @param logf Syslog facility, 0 if no facility was specified
+ * @param daemon False to turn off daemon mode (no fork, leave FDs open)
+ * @param logv Log verbosity, 2 is the default, 0 = no logging, 5 = everything
+ * @return 0 on success, > 0 on error
+ */
+int cgre_start_daemon(const char *logp, const int logf,
+ const unsigned char daemon, const int logv)
+{
+ /* PID returned from the fork() */
+ pid_t pid;
+
+ /* Fork and die. */
+ if (daemon) {
+ pid = fork();
+ if (pid < 0) {
+ openlog("CGRE", LOG_CONS, LOG_DAEMON|LOG_WARNING);
+ syslog(LOG_DAEMON|LOG_WARNING, "Failed to fork,"
+ " error: %s", strerror(errno));
+ closelog();
+ fprintf(stderr, "Failed to fork(), %s\n",
+ strerror(errno));
+ return 1;
+ } else if (pid > 0) {
+ exit(EXIT_SUCCESS);
+ }
+
+ /* Change the file mode mask. */
+ umask(0);
+ } else {
+ cgroup_dbg("Not using daemon mode.\n");
+ pid = getpid();
+ }
+
+ cgre_start_log(logp, logf, logv);
+
+ if (!daemon) {
+ /* We can skip the rest, since we're not becoming a daemon. */
+ flog(LOG_INFO, "Proceeding with PID %d", getpid());
+ return 0;
+ } else {
+ /* Get a new SID for the child. */
+ if (setsid() < 0) {
+ flog(LOG_ERR, "Failed to get a new SID, error: %s",
+ strerror(errno));
+ return 2;
+ }
+
+ /* Change to the root directory. */
+ if (chdir("/") < 0) {
+ flog(LOG_ERR, "Failed to chdir to /, error: %s",
+ strerror(errno));
+ return 3;
+ }
+
+ /* Close standard file descriptors. */
+ close(STDIN_FILENO);
+ if (logfile != stdout)
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ }
+
+ /* If we make it this far, we're a real daemon! Or we chose not to. */
+ flog(LOG_INFO, "Proceeding with PID %d", getpid());
+ return 0;
+}
+
+/**
+ * Catch the SIGUSR2 signal and reload the rules configuration. This function
+ * makes use of the logfile and flog() to print the new rules.
+ * @param signum The signal that we caught (always SIGUSR2)
+ */
+void cgre_flash_rules(int signum)
+{
+ /* Current time */
+ time_t tm = time(0);
+
+ flog(LOG_NOTICE, "Reloading rules configuration.");
+ flog(LOG_DEBUG, "Current time: %s", ctime(&tm));
+
+ /* Ask libcgroup to reload the rules table. */
+ cgroup_reload_cached_rules();
+
+ /* Print the results of the new table to our log file. */
+ if (logfile && loglevel >= LOG_INFO) {
+ cgroup_print_rules_config(logfile);
+ fprintf(logfile, "\n");
+ }
+}
+
+/**
+ * Catch the SIGTERM and SIGINT signals so that we can exit gracefully. Before
+ * exiting, this function makes use of the logfile and flog().
+ * @param signum The signal that we caught (SIGTERM, SIGINT)
+ */
+void cgre_catch_term(int signum)
+{
+ /* Current time */
+ time_t tm = time(0);
+
+ flog(LOG_NOTICE, "Stopped CGroup Rules Engine Daemon at %s",
+ ctime(&tm));
+
+ /* Close the log file, if we opened one */
+ if (logfile && logfile != stdout)
+ fclose(logfile);
+
+ /* Close syslog */
+ if (logfacility)
+ closelog();
+
+ exit(EXIT_SUCCESS);
+}
+
+/**
+ * Parse the syslog facility as received on command line.
+ * @param arg Command line argument with the syslog facility
+ * @return the syslog facility (e.g. LOG_DAEMON) or 0 on error
+ */
+static int cgre_parse_syslog_facility(const char *arg)
+{
+ if (arg == NULL)
+ return 0;
+
+ if (strlen(arg) > 1)
+ return 0;
+
+ switch (arg[0]) {
+ case '0':
+ return LOG_LOCAL0;
+ case '1':
+ return LOG_LOCAL1;
+ case '2':
+ return LOG_LOCAL2;
+ case '3':
+ return LOG_LOCAL3;
+ case '4':
+ return LOG_LOCAL4;
+ case '5':
+ return LOG_LOCAL5;
+ case '6':
+ return LOG_LOCAL6;
+ case '7':
+ return LOG_LOCAL7;
+ case 'D':
+ return LOG_DAEMON;
+ default:
+ return 0;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ /* Patch to the log file */
+ const char *logp = NULL;
+
+ /* Syslog facility */
+ int facility = 0;
+
+ /* Verbose level */
+ int verbosity = 2;
+
+ /* For catching signals */
+ struct sigaction sa;
+
+ /* Should we daemonize? */
+ unsigned char daemon = 1;
+
+ /* Return codes */
+ int ret = 0;
+
+ /* Command line arguments */
+ const char *short_options = "hvqf:s::ndQ";
+ struct option long_options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"verbose", no_argument, NULL, 'v'},
+ {"quiet", no_argument, NULL, 'q'},
+ {"logfile", required_argument, NULL, 'f'},
+ {"syslog", optional_argument, NULL, 's'},
+ {"nodaemon", no_argument, NULL, 'n'},
+ {"debug", no_argument, NULL, 'd'},
+ {"nolog", no_argument, NULL, 'Q'},
+ {NULL, 0, NULL, 0}
+ };
+
+ /* Make sure the user is root. */
+ if (getuid() != 0) {
+ fprintf(stderr, "Error: Only root can start/stop the control"
+ " group rules engine daemon\n");
+ ret = 1;
+ goto finished;
+ }
+
+ while (1) {
+ int c;
+
+ c = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h': /* --help */
+ usage(stdout, "Help:\n");
+ ret = 0;
+ goto finished;
+
+ case 'v': /* --verbose */
+ verbosity++;
+ break;
+
+ case 'q': /* --quiet */
+ verbosity--;
+ break;
+
+ case 'Q': /* --nolog */
+ verbosity = 0;
+ break;
+
+ case 'f': /* --logfile=<filename> */
+ logp = optarg;
+ break;
+
+ case 's': /* --syslog=[facility] */
+ if (optarg) {
+ facility = cgre_parse_syslog_facility(optarg);
+ if (facility == 0) {
+ fprintf(stderr,
+ "Unknown syslog facility: %s\n",
+ optarg);
+ ret = 2;
+ goto finished;
+ }
+ } else {
+ facility = LOG_DAEMON;
+ }
+ break;
+
+ case 'n': /* --no-fork */
+ daemon = 0;
+ break;
+
+ case 'd': /* --debug */
+ /* same as -vvn */
+ daemon = 0;
+ verbosity = 4;
+ logp = "-";
+ break;
+
+ default:
+ usage(stderr, "");
+ ret = 2;
+ goto finished;
+ }
+ }
+
+ /* Initialize libcgroup. */
+ if ((ret = cgroup_init()) != 0) {
+ fprintf(stderr, "Error: libcgroup initialization failed, %d\n",
+ ret);
+ goto finished;
+ }
+
+ /* Ask libcgroup to load the configuration rules. */
+ if ((ret = cgroup_init_rules_cache()) != 0) {
+ fprintf(stderr, "Error: libcgroup failed to initialize rules"
+ "cache, %d\n", ret);
+ goto finished;
+ }
+
+ /* Now, start the daemon. */
+ ret = cgre_start_daemon(logp, facility, daemon, verbosity);
+ if (ret < 0) {
+ fprintf(stderr, "Error: Failed to launch the daemon, %d\n",
+ ret);
+ goto finished;
+ }
+
+ /*
+ * Set up the signal handler to reload the cached rules upon reception
+ * of a SIGUSR2 signal.
+ */
+ sa.sa_handler = &cgre_flash_rules;
+ sa.sa_flags = 0;
+ sa.sa_restorer = NULL;
+ sigemptyset(&sa.sa_mask);
+ if ((ret = sigaction(SIGUSR2, &sa, NULL))) {
+ flog(LOG_ERR, "Failed to set up signal handler for SIGUSR2."
+ " Error: %s", strerror(errno));
+ goto finished;
+ }
+
+ /*
+ * Set up the signal handler to catch SIGINT and SIGTERM so that we
+ * can exit gracefully.
+ */
+ sa.sa_handler = &cgre_catch_term;
+ ret = sigaction(SIGINT, &sa, NULL);
+ ret |= sigaction(SIGTERM, &sa, NULL);
+ if (ret) {
+ flog(LOG_ERR, "Failed to set up the signal handler. Error:"
+ " %s", strerror(errno));
+ goto finished;
+ }
+
+ /* Print the configuration to the log file, or stdout. */
+ if (logfile && loglevel >= LOG_INFO)
+ cgroup_print_rules_config(logfile);
+
+ flog(LOG_NOTICE, "Started the CGroup Rules Engine Daemon.");
+
+ /* We loop endlesly in this function, unless we encounter an error. */
+ ret = cgre_create_netlink_socket_process_msg();
+
+finished:
+ if (logfile && logfile != stdout)
+ fclose(logfile);
+
+ return ret;
+}
diff --git a/src/daemon/cgrulesengd.h b/src/daemon/cgrulesengd.h
new file mode 100644
index 0000000..1840143
--- /dev/null
+++ b/src/daemon/cgrulesengd.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright Red Hat Inc. 2008
+ *
+ * Author: Steve Olivieri <sjo@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#ifndef _CGRULESENGD_H
+#define _CGRULESENGD_H
+
+#include <features.h>
+
+__BEGIN_DECLS
+
+#include "config.h"
+#include "libcgroup.h"
+#include <linux/connector.h>
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#ifndef __USE_GNU
+#define __USE_GNU
+#endif
+
+/* A simple macro for printing messages only when CGROUP_DEBUG is defined. */
+#ifdef CGROUP_DEBUG
+ #define cgroup_dbg(a...) printf(a)
+#else
+ #define cgroup_dbg(a...) do {} while (0)
+#endif /* CGROUP_DEBUG */
+
+/* The following ten macros are all for the Netlink code. */
+#define SEND_MESSAGE_LEN (NLMSG_LENGTH(sizeof(struct cn_msg) + \
+ sizeof(enum proc_cn_mcast_op)))
+#define RECV_MESSAGE_LEN (NLMSG_LENGTH(sizeof(struct cn_msg) + \
+ sizeof(struct proc_event)))
+
+#define SEND_MESSAGE_SIZE (NLMSG_SPACE(SEND_MESSAGE_LEN))
+#define RECV_MESSAGE_SIZE (NLMSG_SPACE(RECV_MESSAGE_LEN))
+
+#define max(x,y) ((y)<(x)?(x):(y))
+#define min(x,y) ((y)>(x)?(x):(y))
+
+#define BUFF_SIZE (max(max(SEND_MESSAGE_SIZE, RECV_MESSAGE_SIZE), 1024))
+#define MIN_RECV_SIZE (min(SEND_MESSAGE_SIZE, RECV_MESSAGE_SIZE))
+
+#define PROC_CN_MCAST_LISTEN (1)
+#define PROC_CN_MCAST_IGNORE (2)
+
+/**
+ * Prints the usage information for this program and, optionally, an error
+ * message. This function uses vfprintf.
+ * @param fd The file stream to print to
+ * @param msg The error message to print (printf style)
+ * @param ... Any args to msg (printf style)
+ */
+void cgre_usage(FILE *fd, const char *msg, ...);
+
+/**
+ * Prints a formatted message (like printf()) to all log destinations.
+ * Flushes the file stream's buffer so that the message is immediately
+ * readable.
+ * @param level The log level (LOG_EMERG ... LOG_DEBUG)
+ * @param format The format for the message (printf style)
+ * @param ... Any args to format (printf style)
+ */
+void flog(int level, const char *msg, ...);
+
+/**
+ * Process an event from the kernel, and determine the correct UID/GID/PID to
+ * pass to libcgroup. Then, libcgroup will decide the cgroup to move the PID
+ * to, if any.
+ * @param ev The event to process
+ * @param type The type of event to process (part of ev)
+ * @return 0 on success, > 0 on failure
+ */
+int cgre_process_event(const struct proc_event *ev, const int type);
+
+/**
+ * Handle a netlink message. In the event of PROC_EVENT_UID or PROC_EVENT_GID,
+ * we pass the event along to cgre_process_event for further processing. All
+ * other events are ignored.
+ * @param cn_hdr The netlink message
+ * @return 0 on success, > 0 on error
+ */
+int cgre_handle_message(struct cn_msg *cn_hdr);
+
+/**
+ * Turns this program into a daemon. In doing so, we fork() and kill the
+ * parent process. Note too that stdout, stdin, and stderr are closed in
+ * daemon mode, and a file descriptor for a log file is opened.
+ * @param logp Path of the log file, NULL if no log file was specified
+ * @param logf Syslog facility, NULL if no facility was specified
+ * @param daemon False to turn off daemon mode (no fork, leave FDs open)
+ * @param logv Log verbosity, 2 is the default, 0 = no logging, 5 = everything
+ * @return 0 on success, > 0 on error
+ */
+int cgre_start_daemon(const char *logp, const int logf,
+ const unsigned char daemon, const int logv);
+
+/**
+ * Catch the SIGUSR2 signal and reload the rules configuration. This function
+ * makes use of the logfile and flog() to print the new rules.
+ * @param signum The signal that we caught (always SIGUSR2)
+ */
+void cgre_flash_rules(int signum);
+
+/**
+ * Catch the SIGTERM and SIGINT signal so that we can exit gracefully. Before
+ * exiting, this function makes use of the logfile and flog().
+ * @param signum The signal that we caught (SIGTERM, SIGINT)
+ */
+void cgre_catch_term(int signum);
+
+__END_DECLS
+
+#endif /* _CGRULESENGD_H */
+
diff --git a/src/lex.l b/src/lex.l
new file mode 100644
index 0000000..817b6b6
--- /dev/null
+++ b/src/lex.l
@@ -0,0 +1,34 @@
+/*
+ * Copyright IBM Corporation. 2007
+ *
+ * Authors: Balbir Singh <balbir@linux.vnet.ibm.com>
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+%{
+#include <string.h>
+#include "parse.h"
+int line_no = 1;
+
+%}
+
+%%
+\n {line_no++;}
+[ \t] {/* DO NOTHING */}
+^#.*[ \t]* {/* Comments */}
+^\*.*[ \t]* {/* Comments */}
+"mount" {return MOUNT;}
+"task" {return TASK;}
+"admin" {return ADMIN;}
+"perm" {return PERM;}
+"group" {return GROUP;}
+[a-zA-Z0-9_\-\/\.]+ {yylval.name = strdup(yytext); return ID;}
+. {return yytext[0];}
+%%
+
diff --git a/src/libcgroup-internal.h b/src/libcgroup-internal.h
new file mode 100644
index 0000000..001da1a
--- /dev/null
+++ b/src/libcgroup-internal.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright IBM Corporation. 2008
+ *
+ * Author: Dhaval Giani <dhaval@linux.vnet.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+#ifndef __LIBCG_INTERNAL
+
+#define __LIBCG_INTERNAL
+
+__BEGIN_DECLS
+
+#include "config.h"
+#include <libcgroup.h>
+#include <limits.h>
+
+#define CGRULES_CONF_FILE "/etc/cgrules.conf"
+#define CGRULES_MAX_FIELDS_PER_LINE 3
+
+#define CGROUP_BUFFER_LEN (5 * FILENAME_MAX)
+
+#ifdef CGROUP_DEBUG
+#define cgroup_dbg(x...) printf(x)
+#else
+#define cgroup_dbg(x...) do {} while (0)
+#endif
+
+struct control_value {
+ char name[FILENAME_MAX];
+ char value[CG_VALUE_MAX];
+};
+
+struct cgroup_controller {
+ char name[FILENAME_MAX];
+ struct control_value *values[CG_NV_MAX];
+ int index;
+};
+
+struct cgroup {
+ char name[FILENAME_MAX];
+ struct cgroup_controller *controller[CG_CONTROLLER_MAX];
+ int index;
+ uid_t tasks_uid;
+ gid_t tasks_gid;
+ uid_t control_uid;
+ gid_t control_gid;
+};
+
+
+struct cg_mount_table_s {
+ char name[FILENAME_MAX];
+ char path[FILENAME_MAX];
+ int index;
+};
+
+struct cgroup_rules_data {
+ pid_t pid; /* pid of the process which needs to change group */
+
+ /* Details of user under consideration for destination cgroup */
+ struct passwd *pw;
+ /* Gid of the process */
+ gid_t gid;
+};
+
+/* A rule that maps UID/GID to a cgroup */
+struct cgroup_rule {
+ uid_t uid;
+ gid_t gid;
+ char name[LOGIN_NAME_MAX];
+ char destination[FILENAME_MAX];
+ char *controllers[MAX_MNT_ELEMENTS];
+ struct cgroup_rule *next;
+};
+
+/* Container for a list of rules */
+struct cgroup_rule_list {
+ struct cgroup_rule *head;
+ struct cgroup_rule *tail;
+ int len;
+};
+
+
+/* Internal API */
+char *cg_build_path(char *name, char *path, char *type);
+
+/*
+ * config related API
+ */
+int cgroup_config_insert_cgroup(char *cg_name);
+int cgroup_config_parse_controller_options(char *controller, char *name_value);
+int cgroup_config_group_task_perm(char *perm_type, char *value);
+int cgroup_config_group_admin_perm(char *perm_type, char *value);
+int cgroup_config_insert_into_mount_table(char *name, char *mount_point);
+void cgroup_config_cleanup_mount_table(void);
+__END_DECLS
+
+#endif
diff --git a/src/libcgroup.map b/src/libcgroup.map
new file mode 100644
index 0000000..1989f90
--- /dev/null
+++ b/src/libcgroup.map
@@ -0,0 +1,56 @@
+CGROUP_0.32 {
+global:
+ cgroup_init;
+ cgroup_attach_task;
+ cgroup_modify_cgroup;
+ cgroup_create_cgroup;
+ cgroup_delete_cgroup;
+ cgroup_attach_task_pid;
+ cgroup_get_cgroup;
+ cgroup_create_cgroup_from_parent;
+ cgroup_copy_cgroup;
+ cgroup_change_cgroup_uid_gid;
+ cgroup_change_cgroup_path;
+ cgroup_new_cgroup;
+ cgroup_add_controller;
+ cgroup_free;
+ cgroup_free_controllers;
+ cgroup_add_value_string;
+ cgroup_add_value_int64;
+ cgroup_add_value_uint64;
+ cgroup_add_value_bool;
+ cgroup_compare_cgroup;
+ cgroup_compare_controllers;
+ cgroup_set_uid_gid;
+ cgroup_get_uid_gid;
+ cgroup_get_value_string;
+ cgroup_set_value_string;
+ cgroup_get_value_int64;
+ cgroup_set_value_int64;
+ cgroup_get_value_uint64;
+ cgroup_set_value_uint64;
+ cgroup_get_value_bool;
+ cgroup_set_value_bool;
+ cgroup_change_cgroup_uid_gid_flags;
+ cgroup_print_rules_config;
+ cgroup_reload_cached_rules;
+ cgroup_init_rules_cache;
+ cgroup_get_current_controller_path;
+ cgroup_config_load_config;
+local:
+ *;
+};
+
+CGROUP_0.32.1 {
+global:
+ cgroup_strerror;
+} CGROUP_0.32;
+
+CGROUP_0.33 {
+global:
+ cgroup_get_last_errno;
+ cgroup_walk_tree_begin;
+ cgroup_walk_tree_next;
+ cgroup_walk_tree_end;
+} CGROUP_0.32.1;
+
diff --git a/src/pam/Makefile.am b/src/pam/Makefile.am
new file mode 100644
index 0000000..1a1e782
--- /dev/null
+++ b/src/pam/Makefile.am
@@ -0,0 +1,6 @@
+INCLUDES = -I $(top_srcdir)/include
+lib_LTLIBRARIES = pam_cgroup.la
+
+pam_cgroup_la_SOURCES = pam_cgroup.c
+pam_cgroup_la_LDFLAGS = -module
+pam_cgroup_la_LIBADD = $(top_srcdir)/src/.libs/libcgroup.la -lpam
diff --git a/src/pam/Makefile.in b/src/pam/Makefile.in
new file mode 100644
index 0000000..205e1bc
--- /dev/null
+++ b/src/pam/Makefile.in
@@ -0,0 +1,472 @@
+# Makefile.in generated by automake 1.10.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/pam
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = `echo $$p | sed -e 's|^.*/||'`;
+am__installdirs = "$(DESTDIR)$(libdir)"
+libLTLIBRARIES_INSTALL = $(INSTALL)
+LTLIBRARIES = $(lib_LTLIBRARIES)
+pam_cgroup_la_DEPENDENCIES = $(top_srcdir)/src/.libs/libcgroup.la
+am_pam_cgroup_la_OBJECTS = pam_cgroup.lo
+pam_cgroup_la_OBJECTS = $(am_pam_cgroup_la_OBJECTS)
+pam_cgroup_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(pam_cgroup_la_LDFLAGS) $(LDFLAGS) -o $@
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
+ $(LDFLAGS) -o $@
+SOURCES = $(pam_cgroup_la_SOURCES)
+DIST_SOURCES = $(pam_cgroup_la_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO = @ECHO@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+F77 = @F77@
+FFLAGS = @FFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBRARY_VERSION = @LIBRARY_VERSION@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+YACC = @YACC@
+YFLAGS = @YFLAGS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_F77 = @ac_ct_F77@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = -I $(top_srcdir)/include
+lib_LTLIBRARIES = pam_cgroup.la
+pam_cgroup_la_SOURCES = pam_cgroup.c
+pam_cgroup_la_LDFLAGS = -module
+pam_cgroup_la_LIBADD = $(top_srcdir)/src/.libs/libcgroup.la -lpam
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \
+ && exit 0; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/pam/Makefile'; \
+ cd $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/pam/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ test -z "$(libdir)" || $(MKDIR_P) "$(DESTDIR)$(libdir)"
+ @list='$(lib_LTLIBRARIES)'; for p in $$list; do \
+ if test -f $$p; then \
+ f=$(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(libLTLIBRARIES_INSTALL) $(INSTALL_STRIP_FLAG) '$$p' '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(libLTLIBRARIES_INSTALL) $(INSTALL_STRIP_FLAG) "$$p" "$(DESTDIR)$(libdir)/$$f"; \
+ else :; fi; \
+ done
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; for p in $$list; do \
+ p=$(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$p'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$p"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; for p in $$list; do \
+ dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \
+ test "$$dir" != "$$p" || dir=.; \
+ echo "rm -f \"$${dir}/so_locations\""; \
+ rm -f "$${dir}/so_locations"; \
+ done
+pam_cgroup.la: $(pam_cgroup_la_OBJECTS) $(pam_cgroup_la_DEPENDENCIES)
+ $(pam_cgroup_la_LINK) -rpath $(libdir) $(pam_cgroup_la_OBJECTS) $(pam_cgroup_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pam_cgroup.Plo@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonemtpy = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$tags $$unique; \
+ fi
+ctags: CTAGS
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ test -z "$(CTAGS_ARGS)$$tags$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$tags $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && cd $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) $$here
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
+ fi; \
+ cp -pR $$d/$$file $(distdir)$$dir || exit 1; \
+ else \
+ test -f $(distdir)/$$file \
+ || cp -p $$d/$$file $(distdir)/$$file \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+ for dir in "$(DESTDIR)$(libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-am
+
+install-info: install-info-am
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-ps: install-ps-am
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-libLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+ clean-libLTLIBRARIES clean-libtool ctags distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am \
+ install-libLTLIBRARIES install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags uninstall uninstall-am uninstall-libLTLIBRARIES
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/pam/pam_cgroup.c b/src/pam/pam_cgroup.c
new file mode 100644
index 0000000..c4ce633
--- /dev/null
+++ b/src/pam/pam_cgroup.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright RedHat Inc. 2008
+ *
+ * Author: Vivek Goyal <vgoyal@redhat.com>
+ *
+ * Derived from pam_limits.c. Original Copyright notice follows.
+ *
+ * Copyright (c) Cristian Gafton, 1996-1997, <gafton@redhat.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * ALTERNATIVELY, this product may be distributed under the terms of
+ * the GNU Public License, in which case the provisions of the GPL are
+ * required INSTEAD OF the above restrictions. (This clause is
+ * necessary due to a potential bad interaction between the GPL and
+ * the restrictions contained in a BSD-style copyright.)
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * End of original copyright notice.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <syslog.h>
+#include <libcgroup.h>
+#include <pwd.h>
+
+/*
+ * Module defines
+ */
+
+#define PAM_SM_SESSION
+
+#include <security/pam_modules.h>
+#include <security/_pam_macros.h>
+#include <security/pam_modutil.h>
+#include <security/pam_ext.h>
+
+/* argument parsing */
+
+#define PAM_DEBUG_ARG 0x0001
+
+static int _pam_parse(const pam_handle_t *pamh, int argc, const char **argv)
+{
+ int ctrl = 0;
+
+ /* step through arguments */
+ for (ctrl = 0; argc-- > 0; ++argv) {
+ if (!strcmp(*argv, "debug"))
+ ctrl |= PAM_DEBUG_ARG;
+ else
+ pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv);
+ }
+
+ return ctrl;
+}
+
+/* now the session stuff */
+PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags,
+ int argc, const char **argv)
+{
+ pid_t pid;
+ int ctrl, ret;
+ char *user_name;
+ struct passwd *pwd;
+
+ D(("called."));
+
+ ctrl = _pam_parse(pamh, argc, argv);
+
+ ret = pam_get_item(pamh, PAM_USER, (void *) &user_name);
+ if (user_name == NULL || ret != PAM_SUCCESS) {
+ pam_syslog(pamh, LOG_ERR, "open_session - error recovering"
+ "username");
+ return PAM_SESSION_ERR;
+ }
+
+ pwd = pam_modutil_getpwnam(pamh, user_name);
+ if (!pwd) {
+ if (ctrl & PAM_DEBUG_ARG)
+ pam_syslog(pamh, LOG_ERR, "open_session username"
+ " '%s' does not exist", user_name);
+ return PAM_SESSION_ERR;
+ }
+
+ D(("user name is %s", user_name));
+
+ /* Initialize libcg */
+ ret = cgroup_init();
+ if (ret) {
+ if (ctrl & PAM_DEBUG_ARG)
+ pam_syslog(pamh, LOG_ERR, "libcgroup initialization"
+ " failed");
+ return PAM_SESSION_ERR;
+ }
+
+ D(("Initialized libcgroup successfuly."));
+
+ /* Determine the pid of the task */
+ pid = getpid();
+
+ /* Note: We are using default gid here. Is there a way to determine
+ * under what egid service will be provided?
+ */
+ ret = cgroup_change_cgroup_uid_gid(pwd->pw_uid, pwd->pw_gid, pid);
+ if (ret) {
+ if (ctrl & PAM_DEBUG_ARG)
+ pam_syslog(pamh, LOG_ERR, "Change of cgroup for process"
+ " with username %s failed.\n", user_name);
+ return PAM_SESSION_ERR;
+ }
+
+ if (ctrl & PAM_DEBUG_ARG)
+ pam_syslog(pamh, LOG_DEBUG, "Changed cgroup for process %d"
+ " with username %s.\n", pid, user_name);
+
+ return PAM_SUCCESS;
+}
+
+PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc,
+ const char **argv)
+{
+ D(("called pam_cgroup close session"));
+
+ /* nothing to do yet */
+ return PAM_SUCCESS;
+}
diff --git a/src/parse.y b/src/parse.y
new file mode 100644
index 0000000..8c7ae24
--- /dev/null
+++ b/src/parse.y
@@ -0,0 +1,269 @@
+/*
+ * Copyright IBM Corporation. 2007
+ *
+ * Authors: Balbir Singh <balbir@linux.vnet.ibm.com>
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * NOTE: The grammar has been modified, not to be the most efficient, but
+ * to allow easy updation of internal data structures.
+ */
+%{
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <libcgroup.h>
+#include <libcgroup-internal.h>
+
+int yylex(void);
+extern int line_no;
+extern char *yytext;
+
+void yyerror(char *s)
+{
+ fprintf(stderr, "error at line number %d at %c:%s", line_no, *yytext,
+ s);
+}
+
+int yywrap(void)
+{
+ return 1;
+}
+
+%}
+
+%token ID MOUNT GROUP PERM TASK ADMIN
+
+%union {
+ char *name;
+ char chr;
+ int val;
+}
+%type <name> ID namevalue_conf
+%type <val> mountvalue_conf mount task_namevalue_conf admin_namevalue_conf
+%type <val> admin_conf task_conf task_or_admin group_conf group start
+%start start
+%%
+
+start : start group
+ {
+ $$ = $1;
+ }
+ | start mount
+ {
+ $$ = $1;
+ }
+ |
+ {
+ $$ = 1;
+ }
+ ;
+
+group : GROUP ID '{' group_conf '}'
+ {
+ $$ = $4;
+ if ($$)
+ cgroup_config_insert_cgroup($2);
+ else {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ ;
+
+group_conf
+ : ID '{' namevalue_conf '}'
+ {
+ $$ = cgroup_config_parse_controller_options($1, $3);
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ | group_conf ID '{' namevalue_conf '}'
+ {
+ $$ = cgroup_config_parse_controller_options($2, $4);
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ | PERM '{' task_or_admin '}'
+ {
+ $$ = $3;
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ ;
+
+namevalue_conf
+ : ID '=' ID ';'
+ {
+ $1 = realloc($1, strlen($1) + strlen($3) + 2);
+ $1 = strncat($1, " ", strlen(" "));
+ $$ = strncat($1, $3, strlen($3));
+ free($3);
+ }
+ | namevalue_conf ID '=' ID ';'
+ {
+ int len = 0;
+ if ($1)
+ len = strlen($1);
+ $2 = realloc($2, len + strlen($2) + strlen($4) + 3);
+ $2 = strncat($2, " ", strlen(" "));
+ $$ = strncat($2, $4, strlen($4));
+ if ($1) {
+ $2 = strncat($2, ":", strlen(":"));
+ $$ = strncat($2, $1, strlen($1));
+ }
+ free($4);
+ }
+ |
+ {
+ $$ = NULL;
+ }
+ ;
+
+task_namevalue_conf
+ : ID '=' ID ';'
+ {
+ $$ = cgroup_config_group_task_perm($1, $3);
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ | task_namevalue_conf ID '=' ID ';'
+ {
+ $$ = $1 && cgroup_config_group_task_perm($2, $4);
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ ;
+
+admin_namevalue_conf
+ : ID '=' ID ';'
+ {
+ $$ = cgroup_config_group_admin_perm($1, $3);
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ | admin_namevalue_conf ID '=' ID ';'
+ {
+ $$ = $1 && cgroup_config_group_admin_perm($2, $4);
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ ;
+
+task_or_admin
+ : TASK '{' task_namevalue_conf '}' admin_conf
+ {
+ $$ = $3 && $5;
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ | ADMIN '{' admin_namevalue_conf '}' task_conf
+ {
+ $$ = $3 && $5;
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ ;
+
+admin_conf: ADMIN '{' admin_namevalue_conf '}'
+ {
+ $$ = $3;
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ ;
+
+task_conf: TASK '{' task_namevalue_conf '}'
+ {
+ $$ = $3;
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ ;
+
+mountvalue_conf
+ : ID '=' ID ';'
+ {
+ if (!cgroup_config_insert_into_mount_table($1, $3)) {
+ cgroup_config_cleanup_mount_table();
+ $$ = 0;
+ return $$;
+ }
+ $$ = 1;
+ }
+ | mountvalue_conf ID '=' ID ';'
+ {
+ if (!cgroup_config_insert_into_mount_table($2, $4)) {
+ cgroup_config_cleanup_mount_table();
+ $$ = 0;
+ return $$;
+ }
+ $$ = 1;
+ }
+ ;
+
+mount : MOUNT '{' mountvalue_conf '}'
+ {
+ $$ = $3;
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ ;
+
+
+%%
diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am
new file mode 100644
index 0000000..b237744
--- /dev/null
+++ b/src/tools/Makefile.am
@@ -0,0 +1,11 @@
+INCLUDES = -I$(top_srcdir)/src -I$(top_srcdir)/include
+LDADD = $(top_srcdir)/src/.libs/libcgroup.la
+
+bin_PROGRAMS = cgexec cgclassify cgconfigparser
+
+cgexec_SOURCES = cgexec.c tools-common.c tools-common.h
+
+cgclassify_SOURCES = cgclassify.c tools-common.c tools-common.h
+
+cgconfigparser_SOURCES = cgconfig.c
+
diff --git a/src/tools/Makefile.in b/src/tools/Makefile.in
new file mode 100644
index 0000000..e850034
--- /dev/null
+++ b/src/tools/Makefile.in
@@ -0,0 +1,486 @@
+# Makefile.in generated by automake 1.10.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+bin_PROGRAMS = cgexec$(EXEEXT) cgclassify$(EXEEXT) \
+ cgconfigparser$(EXEEXT)
+subdir = src/tools
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+binPROGRAMS_INSTALL = $(INSTALL_PROGRAM)
+PROGRAMS = $(bin_PROGRAMS)
+am_cgclassify_OBJECTS = cgclassify.$(OBJEXT) tools-common.$(OBJEXT)
+cgclassify_OBJECTS = $(am_cgclassify_OBJECTS)
+cgclassify_LDADD = $(LDADD)
+cgclassify_DEPENDENCIES = $(top_srcdir)/src/.libs/libcgroup.la
+am_cgconfigparser_OBJECTS = cgconfig.$(OBJEXT)
+cgconfigparser_OBJECTS = $(am_cgconfigparser_OBJECTS)
+cgconfigparser_LDADD = $(LDADD)
+cgconfigparser_DEPENDENCIES = $(top_srcdir)/src/.libs/libcgroup.la
+am_cgexec_OBJECTS = cgexec.$(OBJEXT) tools-common.$(OBJEXT)
+cgexec_OBJECTS = $(am_cgexec_OBJECTS)
+cgexec_LDADD = $(LDADD)
+cgexec_DEPENDENCIES = $(top_srcdir)/src/.libs/libcgroup.la
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
+ $(LDFLAGS) -o $@
+SOURCES = $(cgclassify_SOURCES) $(cgconfigparser_SOURCES) \
+ $(cgexec_SOURCES)
+DIST_SOURCES = $(cgclassify_SOURCES) $(cgconfigparser_SOURCES) \
+ $(cgexec_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO = @ECHO@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+F77 = @F77@
+FFLAGS = @FFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBRARY_VERSION = @LIBRARY_VERSION@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+YACC = @YACC@
+YFLAGS = @YFLAGS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_F77 = @ac_ct_F77@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = -I$(top_srcdir)/src -I$(top_srcdir)/include
+LDADD = $(top_srcdir)/src/.libs/libcgroup.la
+cgexec_SOURCES = cgexec.c tools-common.c tools-common.h
+cgclassify_SOURCES = cgclassify.c tools-common.c tools-common.h
+cgconfigparser_SOURCES = cgconfig.c
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \
+ && exit 0; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/tools/Makefile'; \
+ cd $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/tools/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)"
+ @list='$(bin_PROGRAMS)'; for p in $$list; do \
+ p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \
+ if test -f $$p \
+ || test -f $$p1 \
+ ; then \
+ f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(binPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(bindir)/$$f'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(binPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(bindir)/$$f" || exit 1; \
+ else :; fi; \
+ done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; for p in $$list; do \
+ f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \
+ echo " rm -f '$(DESTDIR)$(bindir)/$$f'"; \
+ rm -f "$(DESTDIR)$(bindir)/$$f"; \
+ done
+
+clean-binPROGRAMS:
+ @list='$(bin_PROGRAMS)'; for p in $$list; do \
+ f=`echo $$p|sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f $$p $$f"; \
+ rm -f $$p $$f ; \
+ done
+cgclassify$(EXEEXT): $(cgclassify_OBJECTS) $(cgclassify_DEPENDENCIES)
+ @rm -f cgclassify$(EXEEXT)
+ $(LINK) $(cgclassify_OBJECTS) $(cgclassify_LDADD) $(LIBS)
+cgconfigparser$(EXEEXT): $(cgconfigparser_OBJECTS) $(cgconfigparser_DEPENDENCIES)
+ @rm -f cgconfigparser$(EXEEXT)
+ $(LINK) $(cgconfigparser_OBJECTS) $(cgconfigparser_LDADD) $(LIBS)
+cgexec$(EXEEXT): $(cgexec_OBJECTS) $(cgexec_DEPENDENCIES)
+ @rm -f cgexec$(EXEEXT)
+ $(LINK) $(cgexec_OBJECTS) $(cgexec_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cgclassify.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cgconfig.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cgexec.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tools-common.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonemtpy = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$tags $$unique; \
+ fi
+ctags: CTAGS
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ test -z "$(CTAGS_ARGS)$$tags$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$tags $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && cd $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) $$here
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
+ fi; \
+ cp -pR $$d/$$file $(distdir)$$dir || exit 1; \
+ else \
+ test -f $(distdir)/$$file \
+ || cp -p $$d/$$file $(distdir)/$$file \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-info: install-info-am
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-ps: install-ps-am
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \
+ clean-generic clean-libtool ctags distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-binPROGRAMS install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags uninstall uninstall-am \
+ uninstall-binPROGRAMS
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/tools/cgclassify.c b/src/tools/cgclassify.c
new file mode 100644
index 0000000..c044608
--- /dev/null
+++ b/src/tools/cgclassify.c
@@ -0,0 +1,219 @@
+/*
+ * Copyright RedHat Inc. 2008
+ *
+ * Authors: Vivek Goyal <vgoyal@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <libcgroup.h>
+#include <limits.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "tools-common.h"
+
+#define TEMP_BUF 81
+
+/*
+ * Go through /proc/<pid>/status file to determine the euid of the
+ * process.
+ * It returns 0 on success and negative values on failure.
+ */
+
+int euid_of_pid(pid_t pid)
+{
+ FILE *fp;
+ char path[FILENAME_MAX];
+ char buf[TEMP_BUF];
+ uid_t ruid, euid, suid, fsuid;
+
+ sprintf(path, "/proc/%d/status", pid);
+ fp = fopen(path, "r");
+ if (!fp) {
+ cgroup_dbg("Error in opening file %s:%s\n", path,
+ strerror(errno));
+ return -1;
+ }
+
+ while (fgets(buf, TEMP_BUF, fp)) {
+ if (!strncmp(buf, "Uid:", 4)) {
+ sscanf((buf + 5), "%d%d%d%d", (int *)&ruid,
+ (int *)&euid, (int *)&suid, (int *)&fsuid);
+ cgroup_dbg("Scanned proc values are %d %d %d %d\n",
+ ruid, euid, suid, fsuid);
+ return euid;
+ }
+ }
+
+ /* If we are here, we could not find euid. Return error. */
+ return -1;
+}
+
+/*
+ * Go through /proc/<pid>/status file to determine the egid of the
+ * process.
+ * It returns 0 on success and negative values on failure.
+ */
+
+int egid_of_pid(pid_t pid)
+{
+ FILE *fp;
+ char path[FILENAME_MAX];
+ char buf[TEMP_BUF];
+ gid_t rgid, egid, sgid, fsgid;
+
+ sprintf(path, "/proc/%d/status", pid);
+ fp = fopen(path, "r");
+ if (!fp) {
+ cgroup_dbg("Error in opening file %s:%s\n", path,
+ strerror(errno));
+ return -1;
+ }
+
+ while (fgets(buf, TEMP_BUF, fp)) {
+ if (!strncmp(buf, "Gid:", 4)) {
+ sscanf((buf + 5), "%d%d%d%d", (int *)&rgid,
+ (int *)&egid, (int *)&sgid, (int *)&fsgid);
+ cgroup_dbg("Scanned proc values are %d %d %d %d\n",
+ rgid, egid, sgid, fsgid);
+ return egid;
+ }
+ }
+
+ /* If we are here, we could not find egid. Return error. */
+ return -1;
+}
+
+/*
+ * Change process group as specified on command line.
+ */
+int change_group_path(pid_t pid, struct cgroup_group_spec *cgroup_list[])
+{
+ int i;
+ int ret = 0;
+
+ for (i = 0; i < CG_HIER_MAX; i++) {
+ if (!cgroup_list[i])
+ break;
+
+ ret = cgroup_change_cgroup_path(cgroup_list[i]->path, pid,
+ cgroup_list[i]->controllers);
+ if (ret)
+ fprintf(stderr, "Error changing group of pid %d: %s\n",
+ pid, cgroup_strerror(ret));
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Change process group as specified in cgrules.conf.
+ */
+int change_group_uid_gid(pid_t pid)
+{
+ uid_t euid;
+ gid_t egid;
+ int ret;
+
+ /* Put pid into right cgroup as per rules in /etc/cgrules.conf */
+ euid = euid_of_pid(pid);
+ if (euid == -1) {
+ fprintf(stderr, "Error in determining euid of"
+ " pid %d\n", pid);
+ return -1;
+ }
+
+ egid = egid_of_pid(pid);
+ if (egid == -1) {
+ fprintf(stderr, "Error in determining egid of"
+ " pid %d\n", pid);
+ return -1;
+ }
+
+ /* Change the cgroup by determining the rules based on uid */
+ ret = cgroup_change_cgroup_uid_gid(euid, egid, pid);
+ if (ret) {
+ fprintf(stderr, "Error: change of cgroup failed for"
+ " pid %d: %s\n",
+ pid, cgroup_strerror(ret));
+ return -1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int ret = 0, i, exit_code = 0;
+ pid_t pid;
+ int cg_specified = 0;
+ struct cgroup_group_spec *cgroup_list[CG_HIER_MAX];
+ int c;
+
+
+ if (argc < 2) {
+ fprintf(stderr, "usage is %s "
+ "[-g <list of controllers>:<relative path to cgroup>] "
+ "<list of pids> \n",
+ argv[0]);
+ exit(2);
+ }
+
+ memset(cgroup_list, 0, sizeof(cgroup_list));
+ while ((c = getopt(argc, argv, "+g:")) > 0) {
+ switch (c) {
+ case 'g':
+ if (parse_cgroup_spec(cgroup_list, optarg)) {
+ fprintf(stderr, "cgroup controller and path"
+ "parsing failed\n");
+ return -1;
+ }
+ cg_specified = 1;
+ break;
+ default:
+ fprintf(stderr, "Invalid command line option\n");
+ exit(2);
+ break;
+ }
+ }
+
+
+ /* Initialize libcg */
+ ret = cgroup_init();
+ if (ret) {
+ fprintf(stderr, "libcgroup initialization failed:%d\n", ret);
+ return ret;
+ }
+
+ for (i = optind; i < argc; i++) {
+ pid = (uid_t) atoi(argv[i]);
+
+ if (cg_specified)
+ ret = change_group_path(pid, cgroup_list);
+ else
+ ret = change_group_uid_gid(pid);
+
+ /* if any group change fails */
+ if (ret)
+ exit_code = 1;
+ }
+ return exit_code;
+
+}
diff --git a/src/tools/cgconfig.c b/src/tools/cgconfig.c
new file mode 100644
index 0000000..cc33ad9
--- /dev/null
+++ b/src/tools/cgconfig.c
@@ -0,0 +1,74 @@
+
+/*
+ * Copyright IBM Corporation. 2007
+ *
+ * Authors: Dhaval Giani <dhaval@linux.vnet.ibm.com>
+ * Balbir Singh <balbir@linux.vnet.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * Code initiated and designed by Dhaval Giani. All faults are most likely
+ * his mistake.
+ */
+
+#include <libcgroup.h>
+#include <libcgroup-internal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+
+
+static void usage(char *progname)
+{
+ printf("Usage: %s [OPTION] [FILE]\n", basename(progname));
+ printf("Parse and load the specified cgroups configuration file\n");
+ printf("\n");
+ printf(" -h, --help Display this help\n");
+ printf(" -l, --load=FILE Parse and load the cgroups configuration file\n");
+ exit(2);
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+ char filename[PATH_MAX];
+ int ret;
+ static struct option options[] = {
+ {"help", 0, 0, 'h'},
+ {"load", 1, 0, 'l'},
+ {0, 0, 0, 0}
+ };
+
+ if (argc < 2)
+ usage(argv[0]); /* usage() exits */
+
+ while ((c = getopt_long(argc, argv, "hl:", options, NULL)) > 0) {
+ switch (c) {
+ case 'h':
+ usage(argv[0]);
+ break;
+ case 'l':
+ strncpy(filename, optarg, PATH_MAX);
+ ret = cgroup_config_load_config(filename);
+ if (ret) {
+ printf("Loading configuration file %s "
+ "failed\n%s\n", filename,
+ cgroup_strerror(ret));
+ exit(3);
+ }
+ return 0;
+ default:
+ usage(argv[0]);
+ break;
+ }
+ }
+ return 0;
+}
diff --git a/src/tools/cgexec.c b/src/tools/cgexec.c
new file mode 100644
index 0000000..167d873
--- /dev/null
+++ b/src/tools/cgexec.c
@@ -0,0 +1,122 @@
+/*
+ * Copyright RedHat Inc. 2008
+ *
+ * Authors: Vivek Goyal <vgoyal@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+#include <errno.h>
+#include <grp.h>
+#include <libcgroup.h>
+#include <limits.h>
+#include <pwd.h>
+#include <search.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "tools-common.h"
+
+int main(int argc, char *argv[])
+{
+ int ret = 0, i;
+ int cg_specified = 0;
+ uid_t euid;
+ pid_t pid;
+ gid_t egid;
+ char c;
+ struct cgroup_group_spec *cgroup_list[CG_HIER_MAX];
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage is %s"
+ " [-g <list of controllers>:<relative path to cgroup>]"
+ " command [arguments] \n",
+ argv[0]);
+ exit(2);
+ }
+
+ memset(cgroup_list, 0, sizeof(cgroup_list));
+
+ while ((c = getopt(argc, argv, "+g:")) > 0) {
+ switch (c) {
+ case 'g':
+ if (parse_cgroup_spec(cgroup_list, optarg)) {
+ fprintf(stderr, "cgroup controller and path"
+ "parsing failed\n");
+ return -1;
+ }
+ cg_specified = 1;
+ break;
+ default:
+ fprintf(stderr, "Invalid command line option\n");
+ exit(1);
+ break;
+ }
+ }
+
+ /* Executable name */
+ if (!argv[optind]) {
+ fprintf(stderr, "No command specified\n");
+ exit(1);
+ }
+
+ /* Initialize libcg */
+ ret = cgroup_init();
+ if (ret) {
+ fprintf(stderr, "libcgroup initialization failed:%d", ret);
+ return ret;
+ }
+
+ euid = geteuid();
+ egid = getegid();
+ pid = getpid();
+
+ if (cg_specified) {
+ /*
+ * User has specified the list of control group and
+ * controllers
+ * */
+ for (i = 0; i < CG_HIER_MAX; i++) {
+ if (!cgroup_list[i])
+ break;
+
+ ret = cgroup_change_cgroup_path(cgroup_list[i]->path,
+ pid,
+ cgroup_list[i]->controllers);
+ if (ret) {
+ fprintf(stderr,
+ "cgroup change of group failed\n");
+ return ret;
+ }
+ }
+ } else {
+
+ /* Change the cgroup by determining the rules based on euid */
+ ret = cgroup_change_cgroup_uid_gid(euid, egid, pid);
+ if (ret) {
+ fprintf(stderr, "cgroup change of group failed\n");
+ return ret;
+ }
+ }
+
+ /* Now exec the new process */
+ ret = execvp(argv[optind], &argv[optind]);
+ if (ret == -1) {
+ fprintf(stderr, "%s", strerror(errno));
+ return -1;
+ }
+ return 0;
+}
diff --git a/src/tools/tools-common.c b/src/tools/tools-common.c
new file mode 100644
index 0000000..0bb666f
--- /dev/null
+++ b/src/tools/tools-common.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright Red Hat, Inc. 2009
+ *
+ * Author: Vivek Goyal <vgoyal@redhat.com>
+ * Jan Safranek <jsafrane@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <libcgroup.h>
+#include "tools-common.h"
+
+int parse_cgroup_spec(struct cgroup_group_spec *cdptr[], char *optarg)
+{
+ struct cgroup_group_spec *ptr;
+ int i, j;
+ char *cptr, *pathptr, *temp;
+
+ ptr = *cdptr;
+
+ /* Find first free entry inside the cgroup data array */
+ for (i = 0; i < CG_HIER_MAX; i++, ptr++) {
+ if (!cdptr[i])
+ break;
+ }
+
+ if (i == CG_HIER_MAX) {
+ /* No free slot found */
+ fprintf(stderr, "Max allowed hierarchies %d reached\n",
+ CG_HIER_MAX);
+ return -1;
+ }
+
+ /* Extract list of controllers */
+ cptr = strtok(optarg, ":");
+ cgroup_dbg("list of controllers is %s\n", cptr);
+ if (!cptr)
+ return -1;
+
+ /* Extract cgroup path */
+ pathptr = strtok(NULL, ":");
+ cgroup_dbg("cgroup path is %s\n", pathptr);
+ if (!pathptr)
+ return -1;
+
+ /* instanciate cgroup_data. */
+ cdptr[i] = malloc(sizeof(struct cgroup_group_spec));
+ if (!cdptr[i]) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ return -1;
+ }
+ /* Convert list of controllers into an array of strings. */
+ j = 0;
+ do {
+ if (j == 0)
+ temp = strtok(cptr, ",");
+ else
+ temp = strtok(NULL, ",");
+
+ if (temp) {
+ cdptr[i]->controllers[j] = strdup(temp);
+ if (!cdptr[i]->controllers[j]) {
+ free(cdptr[i]);
+ fprintf(stderr, "%s\n", strerror(errno));
+ return -1;
+ }
+ }
+ j++;
+ } while (temp);
+
+ /* Store path to the cgroup */
+ strncpy(cdptr[i]->path, pathptr, FILENAME_MAX);
+ cdptr[i]->path[FILENAME_MAX-1] = '\0';
+
+ return 0;
+}
diff --git a/src/tools/tools-common.h b/src/tools/tools-common.h
new file mode 100644
index 0000000..3437973
--- /dev/null
+++ b/src/tools/tools-common.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright Red Hat, Inc. 2009
+ *
+ * Author: Vivek Goyal <vgoyal@redhat.com>
+ * Jan Safranek <jsafrane@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+#ifndef __TOOLS_COMMON
+
+#define __TOOLS_COMMON
+
+#include "config.h"
+#include <libcgroup.h>
+
+#ifdef CGROUP_DEBUG
+#define cgroup_dbg(x...) printf(x)
+#else
+#define cgroup_dbg(x...) do {} while (0)
+#endif
+
+/**
+ * Auxiliary specifier of group, used to store parsed command line options.
+ */
+struct cgroup_group_spec {
+ char path[FILENAME_MAX];
+ char *controllers[CG_CONTROLLER_MAX];
+};
+
+
+/**
+ * Parse command line option with group specifier into provided data structure.
+ * The option must have form of 'controller1,controller2,..:group_name'.
+ *
+ * The parsed list of controllers and group name is added at the end of
+ * provided cdptr.
+ *
+ * @param cdptr Target data structure to fill. New item is allocated and added
+ * at the end.
+ * @param optarg Argument to parse.
+ * @return 0 on success, != 0 on error.
+ */
+int parse_cgroup_spec(struct cgroup_group_spec *cdptr[], char *optarg);
+
+
+#endif /* TOOLS_COMMON */
diff --git a/src/wrapper.c b/src/wrapper.c
new file mode 100644
index 0000000..2f8fcf2
--- /dev/null
+++ b/src/wrapper.c
@@ -0,0 +1,557 @@
+/*
+ * Copyright IBM Corporation. 2008
+ *
+ * Author: Dhaval Giani <dhaval@linux.vnet.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * Code initiated and designed by Dhaval Giani. All faults are most likely
+ * his mistake.
+ */
+
+#include <libcgroup.h>
+#include <libcgroup-internal.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct cgroup *cgroup_new_cgroup(const char *name)
+{
+ struct cgroup *cgroup = calloc(1, sizeof(struct cgroup));
+
+ if (!cgroup)
+ return NULL;
+
+ strncpy(cgroup->name, name, sizeof(cgroup->name));
+
+ return cgroup;
+}
+
+struct cgroup_controller *cgroup_add_controller(struct cgroup *cgroup,
+ const char *name)
+{
+ int i;
+ struct cgroup_controller *controller;
+
+ if (!cgroup)
+ return NULL;
+
+ /*
+ * Still not sure how to handle the failure here.
+ */
+ if (cgroup->index >= CG_CONTROLLER_MAX)
+ return NULL;
+
+ /*
+ * Still not sure how to handle the failure here.
+ */
+ for (i = 0; i < cgroup->index; i++) {
+ if (strncmp(name, cgroup->controller[i]->name,
+ sizeof(cgroup->controller[i]->name)) == 0)
+ return NULL;
+ }
+
+ controller = calloc(1, sizeof(struct cgroup_controller));
+
+ if (!controller)
+ return NULL;
+
+ strncpy(controller->name, name, sizeof(controller->name));
+ controller->index = 0;
+
+ cgroup->controller[cgroup->index] = controller;
+ cgroup->index++;
+
+ return controller;
+}
+
+void cgroup_free_controllers(struct cgroup *cgroup)
+{
+ int i, j;
+
+ if (!cgroup)
+ return;
+
+ for (i = 0; i < cgroup->index; i++) {
+ for (j = 0; j < cgroup->controller[i]->index; j++)
+ free(cgroup->controller[i]->values[j]);
+ free(cgroup->controller[i]);
+ }
+}
+
+void cgroup_free(struct cgroup **cgroup)
+{
+ struct cgroup *cg = *cgroup;
+
+ /*
+ * Passing NULL pointers is OK. We just return.
+ */
+ if (!cg)
+ return;
+
+ cgroup_free_controllers(cg);
+ free(cg);
+ *cgroup = NULL;
+}
+
+int cgroup_add_value_string(struct cgroup_controller *controller,
+ const char *name, const char *value)
+{
+ int i;
+ struct control_value *cntl_value;
+
+ if (!controller)
+ return ECGINVAL;
+
+ if (controller->index >= CG_VALUE_MAX)
+ return ECGMAXVALUESEXCEEDED;
+
+ for (i = 0; i < controller->index && i < CG_VALUE_MAX; i++) {
+ if (!strcmp(controller->values[i]->name, name))
+ return ECGVALUEEXISTS;
+ }
+
+ cntl_value = calloc(1, sizeof(struct control_value));
+
+ if (!cntl_value)
+ return ECGCONTROLLERCREATEFAILED;
+
+ strncpy(cntl_value->name, name, sizeof(cntl_value->name));
+ strncpy(cntl_value->value, value, sizeof(cntl_value->value));
+ controller->values[controller->index] = cntl_value;
+ controller->index++;
+
+ return 0;
+}
+
+int cgroup_add_value_int64(struct cgroup_controller *controller,
+ const char *name, int64_t value)
+{
+ int i;
+ unsigned ret;
+ struct control_value *cntl_value;
+
+ if (!controller)
+ return ECGINVAL;
+
+ if (controller->index >= CG_VALUE_MAX)
+ return ECGMAXVALUESEXCEEDED;
+
+ for (i = 0; i < controller->index && i < CG_VALUE_MAX; i++) {
+ if (!strcmp(controller->values[i]->name, name))
+ return ECGVALUEEXISTS;
+ }
+
+ cntl_value = calloc(1, sizeof(struct control_value));
+
+ if (!cntl_value)
+ return ECGCONTROLLERCREATEFAILED;
+
+ strncpy(cntl_value->name, name,
+ sizeof(cntl_value->name));
+ ret = snprintf(cntl_value->value,
+ sizeof(cntl_value->value), "%" PRId64, value);
+
+ if (ret >= sizeof(cntl_value->value))
+ return ECGINVAL;
+
+ controller->values[controller->index] = cntl_value;
+ controller->index++;
+
+ return 0;
+
+}
+
+int cgroup_add_value_uint64(struct cgroup_controller *controller,
+ const char *name, u_int64_t value)
+{
+ int i;
+ unsigned ret;
+ struct control_value *cntl_value;
+
+ if (!controller)
+ return ECGINVAL;
+
+ if (controller->index >= CG_VALUE_MAX)
+ return ECGMAXVALUESEXCEEDED;
+
+ for (i = 0; i < controller->index && i < CG_VALUE_MAX; i++) {
+ if (!strcmp(controller->values[i]->name, name))
+ return ECGVALUEEXISTS;
+ }
+
+ cntl_value = calloc(1, sizeof(struct control_value));
+
+ if (!cntl_value)
+ return ECGCONTROLLERCREATEFAILED;
+
+ strncpy(cntl_value->name, name, sizeof(cntl_value->name));
+ ret = snprintf(cntl_value->value, sizeof(cntl_value->value),
+ "%" PRIu64, value);
+
+ if (ret >= sizeof(cntl_value->value))
+ return ECGINVAL;
+
+ controller->values[controller->index] = cntl_value;
+ controller->index++;
+
+ return 0;
+
+}
+
+int cgroup_add_value_bool(struct cgroup_controller *controller,
+ const char *name, bool value)
+{
+ int i;
+ unsigned ret;
+ struct control_value *cntl_value;
+
+ if (!controller)
+ return ECGINVAL;
+
+ if (controller->index >= CG_VALUE_MAX)
+ return ECGMAXVALUESEXCEEDED;
+
+ for (i = 0; i < controller->index && i < CG_VALUE_MAX; i++) {
+ if (!strcmp(controller->values[i]->name, name))
+ return ECGVALUEEXISTS;
+ }
+
+ cntl_value = calloc(1, sizeof(struct control_value));
+
+ if (!cntl_value)
+ return ECGCONTROLLERCREATEFAILED;
+
+ strncpy(cntl_value->name, name, sizeof(cntl_value->name));
+
+ if (value)
+ ret = snprintf(cntl_value->value, sizeof(cntl_value->value), "1");
+ else
+ ret = snprintf(cntl_value->value, sizeof(cntl_value->value), "0");
+
+ if (ret >= sizeof(cntl_value->value))
+ return ECGINVAL;
+
+ controller->values[controller->index] = cntl_value;
+ controller->index++;
+
+ return 0;
+}
+
+int cgroup_compare_controllers(struct cgroup_controller *cgca,
+ struct cgroup_controller *cgcb)
+{
+ int i;
+
+ if (!cgca || !cgcb)
+ return ECGINVAL;
+
+ if (strcmp(cgca->name, cgcb->name))
+ return ECGCONTROLLERNOTEQUAL;
+
+ if (cgca->index != cgcb->index)
+ return ECGCONTROLLERNOTEQUAL;
+
+ for (i = 0; i < cgca->index; i++) {
+ struct control_value *cva = cgca->values[i];
+ struct control_value *cvb = cgcb->values[i];
+
+ if (strcmp(cva->name, cvb->name))
+ return ECGCONTROLLERNOTEQUAL;
+
+ if (strcmp(cva->value, cvb->value))
+ return ECGCONTROLLERNOTEQUAL;
+ }
+ return 0;
+}
+
+int cgroup_compare_cgroup(struct cgroup *cgroup_a, struct cgroup *cgroup_b)
+{
+ int i;
+
+ if (!cgroup_a || !cgroup_b)
+ return ECGINVAL;
+
+ if (strcmp(cgroup_a->name, cgroup_b->name))
+ return ECGROUPNOTEQUAL;
+
+ if (cgroup_a->tasks_uid != cgroup_b->tasks_uid)
+ return ECGROUPNOTEQUAL;
+
+ if (cgroup_a->tasks_gid != cgroup_b->tasks_gid)
+ return ECGROUPNOTEQUAL;
+
+ if (cgroup_a->control_uid != cgroup_b->control_uid)
+ return ECGROUPNOTEQUAL;
+
+ if (cgroup_a->control_gid != cgroup_b->control_gid)
+ return ECGROUPNOTEQUAL;
+
+ if (cgroup_a->index != cgroup_b->index)
+ return ECGROUPNOTEQUAL;
+
+ for (i = 0; i < cgroup_a->index; i++) {
+ struct cgroup_controller *cgca = cgroup_a->controller[i];
+ struct cgroup_controller *cgcb = cgroup_b->controller[i];
+
+ if (cgroup_compare_controllers(cgca, cgcb))
+ return ECGCONTROLLERNOTEQUAL;
+ }
+ return 0;
+}
+
+int cgroup_set_uid_gid(struct cgroup *cgroup, uid_t tasks_uid, gid_t tasks_gid,
+ uid_t control_uid, gid_t control_gid)
+{
+ if (!cgroup)
+ return ECGINVAL;
+
+ cgroup->tasks_uid = tasks_uid;
+ cgroup->tasks_gid = tasks_gid;
+ cgroup->control_uid = control_uid;
+ cgroup->control_gid = control_gid;
+
+ return 0;
+}
+
+int cgroup_get_uid_gid(struct cgroup *cgroup, uid_t *tasks_uid,
+ gid_t *tasks_gid, uid_t *control_uid, gid_t *control_gid)
+{
+ if (!cgroup)
+ return ECGINVAL;
+
+ *tasks_uid = cgroup->tasks_uid;
+ *tasks_gid = cgroup->tasks_gid;
+ *control_uid = cgroup->control_uid;
+ *control_gid = cgroup->control_uid;
+
+ return 0;
+}
+
+struct cgroup_controller *cgroup_get_controller(struct cgroup *cgroup,
+ const char *name)
+{
+ int i;
+ struct cgroup_controller *cgc;
+
+ if (!cgroup)
+ return NULL;
+
+ for (i = 0; i < cgroup->index; i++) {
+ cgc = cgroup->controller[i];
+
+ if (!strcmp(cgc->name, name))
+ return cgc;
+ }
+
+ return NULL;
+}
+
+int cgroup_get_value_string(struct cgroup_controller *controller,
+ const char *name, char **value)
+{
+ int i;
+
+ if (!controller)
+ return ECGINVAL;
+
+ for (i = 0; i < controller->index; i++) {
+ struct control_value *val = controller->values[i];
+
+ if (!strcmp(val->name, name)) {
+ *value = strdup(val->value);
+
+ if (!*value)
+ return ECGOTHER;
+
+ return 0;
+ }
+ }
+
+ return ECGROUPVALUENOTEXIST;
+
+}
+
+int cgroup_set_value_string(struct cgroup_controller *controller,
+ const char *name, const char *value)
+{
+ int i;
+
+ if (!controller)
+ return ECGINVAL;
+
+ for (i = 0; i < controller->index; i++) {
+ struct control_value *val = controller->values[i];
+ if (!strcmp(val->name, name)) {
+ strncpy(val->value, value, CG_VALUE_MAX);
+ return 0;
+ }
+ }
+
+ return cgroup_add_value_string(controller, name, value);
+}
+
+int cgroup_get_value_int64(struct cgroup_controller *controller,
+ const char *name, int64_t *value)
+{
+ int i;
+
+ if (!controller)
+ return ECGINVAL;
+
+ for (i = 0; i < controller->index; i++) {
+ struct control_value *val = controller->values[i];
+
+ if (!strcmp(val->name, name)) {
+
+ if (sscanf(val->value, "%" SCNd64, value) != 1)
+ return ECGINVAL;
+
+ return 0;
+ }
+ }
+
+ return ECGROUPVALUENOTEXIST;
+}
+
+int cgroup_set_value_int64(struct cgroup_controller *controller,
+ const char *name, int64_t value)
+{
+ int i;
+ unsigned ret;
+
+ if (!controller)
+ return ECGINVAL;
+
+ for (i = 0; i < controller->index; i++) {
+ struct control_value *val = controller->values[i];
+
+ if (!strcmp(val->name, name)) {
+ ret = snprintf(val->value,
+ sizeof(val->value), "%" PRId64, value);
+
+ if (ret >= sizeof(val->value) || ret < 0)
+ return ECGINVAL;
+
+ return 0;
+ }
+ }
+
+ return cgroup_add_value_int64(controller, name, value);
+}
+
+int cgroup_get_value_uint64(struct cgroup_controller *controller,
+ const char *name, u_int64_t *value)
+{
+ int i;
+
+ if (!controller)
+ return ECGINVAL;
+
+ for (i = 0; i < controller->index; i++) {
+ struct control_value *val = controller->values[i];
+ if (!strcmp(val->name, name)) {
+
+ if (sscanf(val->value, "%" SCNu64, value) != 1)
+ return ECGINVAL;
+
+ return 0;
+ }
+ }
+
+ return ECGROUPVALUENOTEXIST;
+}
+
+int cgroup_set_value_uint64(struct cgroup_controller *controller,
+ const char *name, u_int64_t value)
+{
+ int i;
+ unsigned ret;
+
+ if (!controller)
+ return ECGINVAL;
+
+ for (i = 0; i < controller->index; i++) {
+ struct control_value *val = controller->values[i];
+
+ if (!strcmp(val->name, name)) {
+ ret = snprintf(val->value,
+ sizeof(val->value), "%" PRIu64, value);
+
+ if (ret >= sizeof(val->value) || ret < 0)
+ return ECGINVAL;
+
+ return 0;
+ }
+ }
+
+ return cgroup_add_value_uint64(controller, name, value);
+}
+
+int cgroup_get_value_bool(struct cgroup_controller *controller,
+ const char *name, bool *value)
+{
+ int i;
+
+ if (!controller)
+ return ECGINVAL;
+
+ for (i = 0; i < controller->index; i++) {
+ struct control_value *val = controller->values[i];
+
+ if (!strcmp(val->name, name)) {
+ int cgc_val;
+
+ if (sscanf(val->value, "%d", &cgc_val) != 1)
+ return ECGINVAL;
+
+ if (cgc_val)
+ *value = true;
+ else
+ *value = false;
+
+ return 0;
+ }
+ }
+ return ECGROUPVALUENOTEXIST;
+}
+
+int cgroup_set_value_bool(struct cgroup_controller *controller,
+ const char *name, bool value)
+{
+ int i;
+ unsigned ret;
+
+ if (!controller)
+ return ECGINVAL;
+
+ for (i = 0; i < controller->index; i++) {
+ struct control_value *val = controller->values[i];
+
+ if (!strcmp(val->name, name)) {
+ if (value) {
+ ret = snprintf(val->value,
+ sizeof(val->value), "1");
+ } else {
+ ret = snprintf(val->value,
+ sizeof(val->value), "0");
+ }
+
+ if (ret >= sizeof(val->value) || ret < 0)
+ return ECGINVAL;
+
+ return 0;
+
+ }
+ }
+
+ return cgroup_add_value_bool(controller, name, value);
+}