diff options
Diffstat (limited to 'daemon')
-rw-r--r-- | daemon/Makefile.am | 8 | ||||
-rw-r--r-- | daemon/Makefile.in | 554 | ||||
-rw-r--r-- | daemon/environment.cpp | 436 | ||||
-rw-r--r-- | daemon/environment.h | 43 | ||||
-rw-r--r-- | daemon/load.cpp | 373 | ||||
-rw-r--r-- | daemon/load.h | 29 | ||||
-rw-r--r-- | daemon/main.cpp | 1681 | ||||
-rw-r--r-- | daemon/ncpus.c | 163 | ||||
-rw-r--r-- | daemon/ncpus.h | 29 | ||||
-rw-r--r-- | daemon/serve.cpp | 251 | ||||
-rw-r--r-- | daemon/serve.h | 36 | ||||
-rw-r--r-- | daemon/workit.cpp | 513 | ||||
-rw-r--r-- | daemon/workit.h | 51 |
13 files changed, 4167 insertions, 0 deletions
diff --git a/daemon/Makefile.am b/daemon/Makefile.am new file mode 100644 index 0000000..21407b4 --- /dev/null +++ b/daemon/Makefile.am @@ -0,0 +1,8 @@ +INCLUDES = -I$(srcdir)/../services +# KDE_CXXFLAGS = $(USE_EXCEPTIONS) + +sbin_PROGRAMS = iceccd +iceccd_SOURCES = ncpus.c main.cpp serve.cpp workit.cpp environment.cpp load.cpp +iceccd_LDADD = ../services/libicecc.la $(LIB_KINFO) +noinst_HEADERS = environment.h load.h ncpus.h serve.h workit.h + diff --git a/daemon/Makefile.in b/daemon/Makefile.in new file mode 100644 index 0000000..c54c843 --- /dev/null +++ b/daemon/Makefile.in @@ -0,0 +1,554 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 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@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@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 = iceccd$(EXEEXT) +subdir = daemon +DIST_COMMON = $(noinst_HEADERS) $(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 = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(sbindir)" +PROGRAMS = $(sbin_PROGRAMS) +am_iceccd_OBJECTS = ncpus.$(OBJEXT) main.$(OBJEXT) serve.$(OBJEXT) \ + workit.$(OBJEXT) environment.$(OBJEXT) load.$(OBJEXT) +iceccd_OBJECTS = $(am_iceccd_OBJECTS) +am__DEPENDENCIES_1 = +iceccd_DEPENDENCIES = ../services/libicecc.la $(am__DEPENDENCIES_1) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +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 $@ +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ + --mode=link $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) \ + $(LDFLAGS) -o $@ +SOURCES = $(iceccd_SOURCES) +DIST_SOURCES = $(iceccd_SOURCES) +HEADERS = $(noinst_HEADERS) +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@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBRSYNC = @LIBRSYNC@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIB_KINFO = @LIB_KINFO@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +TAR = @TAR@ +VERSION = @VERSION@ +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_DUMPBIN = @ac_ct_DUMPBIN@ +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@ +lt_ECHO = @lt_ECHO@ +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_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +INCLUDES = -I$(srcdir)/../services +iceccd_SOURCES = ncpus.c main.cpp serve.cpp workit.cpp environment.cpp load.cpp +iceccd_LDADD = ../services/libicecc.la $(LIB_KINFO) +noinst_HEADERS = environment.h load.h ncpus.h serve.h workit.h +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .cpp .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 ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign daemon/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign 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 +$(am__aclocal_m4_deps): +install-sbinPROGRAMS: $(sbin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(sbindir)" || $(MKDIR_P) "$(DESTDIR)$(sbindir)" + @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p || test -f $$p1; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sbindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-sbinPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(sbindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(sbindir)" && rm -f $$files + +clean-sbinPROGRAMS: + @list='$(sbin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +iceccd$(EXEEXT): $(iceccd_OBJECTS) $(iceccd_DEPENDENCIES) + @rm -f iceccd$(EXEEXT) + $(CXXLINK) $(iceccd_OBJECTS) $(iceccd_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/environment.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/load.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ncpus.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/serve.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/workit.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(am__mv) $(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@ $(am__mv) $(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@ $(am__mv) $(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 $@ $< + +.cpp.o: +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ $< + +.cpp.obj: +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cpp.lo: +@am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LTCXXCOMPILE) -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; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + 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; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + 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)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__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 "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$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) $(HEADERS) +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) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_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 + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-sbinPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +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/daemon/environment.cpp b/daemon/environment.cpp new file mode 100644 index 0000000..979da91 --- /dev/null +++ b/daemon/environment.cpp @@ -0,0 +1,436 @@ +/* + This file is part of Icecream. + + Copyright (c) 2004 Stephan Kulow <coolo@suse.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include <config.h> +#include "environment.h" +#include <logging.h> +#include <errno.h> +#include <dirent.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#ifdef HAVE_SIGNAL_H +#include <signal.h> +#endif + +#include "comm.h" + +using namespace std; + +#if 0 +static string read_fromFILE( FILE *f ) +{ + string output; + if ( !f ) { + log_error() << "no pipe " << strerror( errno ) << endl; + return output; + } + char buffer[100]; + while ( !feof( f ) ) { + size_t bytes = fread( buffer, 1, 99, f ); + buffer[bytes] = 0; + output += buffer; + } + pclose( f ); + return output; +} + +static bool extract_version( string &version ) +{ + string::size_type pos = version.find_last_of( '\n' ); + if ( pos == string::npos ) + return false; + + while ( pos + 1 == version.size() ) { + version.resize( version.size() - 1 ); + pos = version.find_last_of( '\n' ); + if ( pos == string::npos ) + return false; + } + + version = version.substr( pos + 1); + return true; +} +#endif + +size_t sumup_dir( const string &dir ) +{ + size_t res = 0; + DIR *envdir = opendir( dir.c_str() ); + if ( !envdir ) + return res; + + struct stat st; + string tdir = dir + "/"; + + for ( struct dirent *ent = readdir(envdir); ent; ent = readdir( envdir ) ) + { + if ( !strcmp( ent->d_name, "." ) || !strcmp( ent->d_name, ".." ) ) + continue; + + if ( lstat( ( tdir + ent->d_name ).c_str(), &st ) ) { + perror( "stat" ); + continue; + } + + if ( S_ISDIR( st.st_mode ) ) + res += sumup_dir( tdir + ent->d_name ); + else if ( S_ISREG( st.st_mode ) ) + res += st.st_size; + // else ignore + } + closedir( envdir ); + return res; +} + +static string list_native_environment( const string &nativedir ) +{ + assert( nativedir.at( nativedir.length() - 1 ) == '/' ); + + string native_environment; + + DIR *tdir = opendir( nativedir.c_str() ); + if ( tdir ) { + string suff = ".tar.gz"; + do { + struct dirent *myenv = readdir(tdir); + if ( !myenv ) + break; + string versfile = myenv->d_name; + if ( versfile.size() > suff.size() && versfile.substr( versfile.size() - suff.size() ) == suff ) { + native_environment = nativedir + versfile; + break; + } + } while ( true ); + closedir( tdir ); + } + return native_environment; +} + +static void list_target_dirs( const string ¤t_target, const string &targetdir, Environments &envs ) +{ + DIR *envdir = opendir( targetdir.c_str() ); + if ( !envdir ) + return; + + for ( struct dirent *ent = readdir(envdir); ent; ent = readdir( envdir ) ) + { + string dirname = ent->d_name; + if ( !access( string( targetdir + "/" + dirname + "/usr/bin/gcc" ).c_str(), X_OK ) ) + envs.push_back( make_pair( current_target, dirname ) ); + } + closedir( envdir ); +} + +/* Returns true if the child exited with success */ +static bool exec_and_wait( const char *const argv[] ) +{ + pid_t pid = fork(); + if ( pid == -1 ) { + log_perror("fork"); + return false; + } + if ( pid ) { + // parent + int status; + while ( waitpid( pid, &status, 0 ) < 0 && errno == EINTR ) + ; + return WIFEXITED(status) && WEXITSTATUS(status) == 0; + } + // child + _exit(execv(argv[0], const_cast<char *const *>(argv))); +} + +bool cleanup_cache( const string &basedir ) +{ + flush_debug(); + + // make sure it ends with '/' to not fall into symlink traps + string bdir = basedir + '/'; + const char *const argv[] = { + "/bin/rm", "-rf", "--", bdir.c_str(), NULL + }; + + bool ret = exec_and_wait( argv ); + + if ( mkdir( basedir.c_str(), 0755 ) && errno != EEXIST ) { + if ( errno == EPERM ) + log_error() << "permission denied on mkdir " << basedir << endl; + else + log_perror( "mkdir in cleanup_cache() failed" ); + return false; + } + + return ret; +} + +Environments available_environmnents(const string &basedir) +{ + Environments envs; + + DIR *envdir = opendir( basedir.c_str() ); + if ( !envdir ) { + log_info() << "can't open envs dir " << strerror( errno ) << endl; + } else { + for ( struct dirent *target_ent = readdir(envdir); target_ent; target_ent = readdir( envdir ) ) + { + string dirname = target_ent->d_name; + if ( dirname.at( 0 ) == '.' ) + continue; + if ( dirname.substr( 0, 7 ) == "target=" ) + { + string current_target = dirname.substr( 7, dirname.length() - 7 ); + list_target_dirs( current_target, basedir + "/" + dirname, envs ); + } + } + closedir( envdir ); + } + + return envs; +} + +size_t setup_env_cache(const string &basedir, string &native_environment, uid_t nobody_uid, gid_t nobody_gid) +{ + native_environment = ""; + string nativedir = basedir + "/native/"; + + if ( ::access( "/usr/bin/gcc", X_OK ) || ::access( "/usr/bin/g++", X_OK ) ) + return 0; + + if ( mkdir( nativedir.c_str(), 0775 ) ) + return 0; + + if ( chown( nativedir.c_str(), 0, nobody_gid ) || + chmod( nativedir.c_str(), 0775 ) ) { + rmdir( nativedir.c_str() ); + return 0; + } + + flush_debug(); + pid_t pid = fork(); + if ( pid ) { + int status = 1; + while ( waitpid( pid, &status, 0 ) < 0 && errno == EINTR ) + ; + trace() << "waitpid " << status << endl; + if ( !status ) { + trace() << "opendir " << nativedir << endl; + native_environment = list_native_environment( nativedir ); + if ( native_environment.empty() ) + status = 1; + } + trace() << "native_environment " << native_environment << endl; + if ( status ) { + rmdir( nativedir.c_str() ); + return 0; + } + else { + return sumup_dir( nativedir ); + } + } + // else + + if ( setgid( nobody_gid ) < 0) { + log_perror("setgid failed"); + _exit(143); + } + if (!geteuid() && setuid( nobody_uid ) < 0) { + log_perror("setuid failed"); + _exit (142); + } + + if ( chdir( nativedir.c_str() ) ) { + log_perror( "chdir" ); + _exit(1); + } + + const char *const argv[] = { + BINDIR "/icecc", "--build-native", NULL + }; + if ( !exec_and_wait( argv ) ) { + log_error() << BINDIR "/icecc --build-native failed\n"; + _exit(1); + } + _exit( 0 ); +} + + +pid_t start_install_environment( const std::string &basename, const std::string &target, + const std::string &name, MsgChannel *c, + int& pipe_to_stdin, FileChunkMsg*& fmsg, + uid_t nobody_uid, gid_t nobody_gid ) +{ + if ( !name.size() || name[0] == '.' ) { + log_error() << "illegal name for environment " << name << endl; + return 0; + } + + for ( string::size_type i = 0; i < name.size(); ++i ) { + if ( isascii( name[i] ) && !isspace( name[i]) && name[i] != '/' && isprint( name[i] ) ) + continue; + log_error() << "illegal char '" << name[i] << "' - rejecting environment " << name << endl; + return 0; + } + + string dirname = basename + "/target=" + target; + Msg *msg = c->get_msg(30); + if ( !msg || msg->type != M_FILE_CHUNK ) + { + trace() << "Expected first file chunk\n"; + return 0; + } + + fmsg = dynamic_cast<FileChunkMsg*>( msg ); + enum { BZip2, Gzip, None} compression = None; + if ( fmsg->len > 2 ) + { + if ( fmsg->buffer[0] == 037 && fmsg->buffer[1] == 0213 ) + compression = Gzip; + else if ( fmsg->buffer[0] == 'B' && fmsg->buffer[1] == 'Z' ) + compression = BZip2; + } + + if ( mkdir( dirname.c_str(), 0770 ) && errno != EEXIST ) { + log_perror( "mkdir target" ); + return 0; + } + + if ( chown( dirname.c_str(), 0, nobody_gid ) || + chmod( dirname.c_str(), 0770 ) ) { + log_perror( "chown,chmod target" ); + return 0; + } + + dirname = dirname + "/" + name; + if ( mkdir( dirname.c_str(), 0770 ) ) { + log_perror( "mkdir name" ); + return 0; + } + + if ( chown( dirname.c_str(), 0, nobody_gid ) || + chmod( dirname.c_str(), 0770 ) ) { + log_perror( "chown,chmod name" ); + return 0; + } + + int fds[2]; + if ( pipe( fds ) ) + return 0; + + flush_debug(); + pid_t pid = fork(); + if ( pid ) + { + trace() << "pid " << pid << endl; + close( fds[0] ); + pipe_to_stdin = fds[1]; + + return pid; + } + // else + if ( setgid( nobody_gid ) < 0) { + log_perror("setgid fails"); + _exit(143); + } + if (!geteuid() && setuid( nobody_uid ) < 0) { + log_perror("setuid fails"); + _exit (142); + } + + // reset SIGPIPE and SIGCHILD handler so that tar + // isn't confused when gzip/bzip2 aborts + signal(SIGCHLD, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + + close( 0 ); + close( fds[1] ); + dup2( fds[0], 0 ); + + char **argv; + argv = new char*[6]; + argv[0] = strdup( TAR ); + argv[1] = strdup ("-C"); + argv[2] = strdup ( dirname.c_str() ); + if ( compression == BZip2 ) + argv[3] = strdup( "-xjf" ); + else if ( compression == Gzip ) + argv[3] = strdup( "-xzf" ); + else if ( compression == None ) + argv[3] = strdup( "-xf" ); + argv[4] = strdup( "-" ); + argv[5] = 0; + _exit( execv( argv[0], argv ) ); +} + + +size_t finalize_install_environment( const std::string &basename, const std::string &target, + pid_t pid, gid_t nobody_gid) +{ + int status = 1; + while ( waitpid( pid, &status, 0) < 0 && errno == EINTR) + ; + + if (!WIFEXITED(status) || WEXITSTATUS(status)) { + log_error() << "exit code: " << WEXITSTATUS(status) << endl; + remove_environment(basename, target); + return 0; + } + + string dirname = basename + "/target=" + target; + mkdir( ( dirname + "/tmp" ).c_str(), 01775 ); + chown( ( dirname + "/tmp" ).c_str(), 0, nobody_gid ); + chmod( ( dirname + "/tmp" ).c_str(), 01775 ); + + return sumup_dir (dirname); +} + +size_t remove_environment( const string &basename, const string &env ) +{ + string dirname = basename + "/target=" + env; + + size_t res = sumup_dir( dirname ); + + flush_debug(); + pid_t pid = fork(); + if ( pid ) + { + int status = 0; + while ( waitpid( pid, &status, 0 ) < 0 && errno == EINTR ) + ; + if ( WIFEXITED (status) ) + return res; + // something went wrong. assume no disk space was free'd. + return 0; + } + // else + + char **argv; + argv = new char*[5]; + argv[0] = strdup( "/bin/rm" ); + argv[1] = strdup( "-rf" ); + argv[2] = strdup("--"); + argv[3] = strdup( dirname.c_str() ); + argv[4] = NULL; + + _exit(execv(argv[0], argv)); +} diff --git a/daemon/environment.h b/daemon/environment.h new file mode 100644 index 0000000..a4b610b --- /dev/null +++ b/daemon/environment.h @@ -0,0 +1,43 @@ +/* + This file is part of Icecream. + + Copyright (c) 2004 Stephan Kulow <coolo@suse.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _ENVIRONMENT_H +#define _ENVIRONMENT_H + +#include <comm.h> +#include <list> +#include <string> + +class MsgChannel; +extern bool cleanup_cache( const std::string &basedir ); +extern size_t setup_env_cache(const std::string &basedir, + std::string &native_environment, uid_t nobody_uid, gid_t nobody_gid); +Environments available_environmnents(const std::string &basename); +extern pid_t start_install_environment( const std::string &basename, + const std::string &target, + const std::string &name, + MsgChannel *c, int& pipe_to_child, + FileChunkMsg*& fmsg, + uid_t nobody_uid, gid_t nobody_gid ); +extern size_t finalize_install_environment( const std::string &basename, const std::string& target, + pid_t pid, gid_t nobody_gid ); +extern size_t remove_environment( const std::string &basedir, const std::string &env); + +#endif diff --git a/daemon/load.cpp b/daemon/load.cpp new file mode 100644 index 0000000..ce64e23 --- /dev/null +++ b/daemon/load.cpp @@ -0,0 +1,373 @@ +/* + Copyright (c) 1999, 2000 Chris Schlaeger <cs@kde.org> + Copyright (c) 2003 Stephan Kulow <coolo@kde.org> + + This program is free software; you can redistribute it and/or + modify it under the terms of version 2 of the GNU General Public + License as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "config.h" +#include "load.h" +#include <unistd.h> +#include <stdio.h> +#include <math.h> +#include <logging.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif + +#ifdef HAVE_MACH_HOST_INFO_H +#define USE_MACH 1 +#elif !defined( __linux__ ) && !defined(__CYGWIN__) +#define USE_SYSCTL +#endif + +#ifdef USE_MACH +#include <mach/host_info.h> +#include <mach/mach_host.h> +#include <mach/mach_init.h> +#endif + +#ifdef HAVE_KINFO_H +#include <kinfo.h> +#endif + +#ifdef HAVE_DEVSTAT_H +#include <sys/resource.h> +#include <sys/sysctl.h> +#include <devstat.h> +#endif + +using namespace std; + +// what the kernel puts as ticks in /proc/stat +typedef unsigned long long load_t; + + +struct CPULoadInfo +{ + /* A CPU can be loaded with user processes, reniced processes and + * system processes. Unused processing time is called idle load. + * These variable store the percentage of each load type. */ + int userLoad; + int niceLoad; + int sysLoad; + int idleLoad; + + /* To calculate the loads we need to remember the tick values for each + * load type. */ + load_t userTicks; + load_t niceTicks; + load_t sysTicks; + load_t idleTicks; + load_t waitTicks; + + CPULoadInfo() { + userTicks = 0; + niceTicks = 0; + sysTicks = 0; + idleTicks = 0; + waitTicks = 0; + } +}; + +static void updateCPULoad( CPULoadInfo* load ) +{ + load_t totalTicks; + load_t currUserTicks, currSysTicks, currNiceTicks, currIdleTicks, currWaitTicks; + +#if defined(USE_SYSCTL) && defined(__DragonFly__) + static struct kinfo_cputime cp_time; + + kinfo_get_sched_cputime(&cp_time); + /* There is one more load type exported via this interface in DragonFlyBSD - + * interrupt load. But I think that we can do without it for our needs. */ + currUserTicks = cp_time.cp_user; + currNiceTicks = cp_time.cp_nice; + currSysTicks = cp_time.cp_sys; + currIdleTicks = cp_time.cp_idle; + /* It doesn't exist in DragonFlyBSD. */ + currWaitTicks = 0; + +#elif defined (USE_SYSCTL) + static int mibs[4] = { 0,0,0,0 }; + static size_t mibsize = 4; + unsigned long ticks[CPUSTATES]; + size_t mibdatasize = sizeof(ticks); + + if (mibs[0]==0) { + if (sysctlnametomib("kern.cp_time",mibs,&mibsize) < 0) { + load->userTicks = load->sysTicks = load->niceTicks = load->idleTicks = 0; + load->userLoad = load->sysLoad = load->niceLoad = load->idleLoad = 0; + mibs[0]=0; + return; + } + } + if (sysctl(mibs,mibsize,&ticks,&mibdatasize,NULL,0) < 0) { + load->userTicks = load->sysTicks = load->niceTicks = load->idleTicks = 0; + load->userLoad = load->sysLoad = load->niceLoad = load->idleLoad = 0; + return; + } else { + currUserTicks = ticks[CP_USER]; + currNiceTicks = ticks[CP_NICE]; + currSysTicks = ticks[CP_SYS]; + currIdleTicks = ticks[CP_IDLE]; + } + +#elif defined( USE_MACH ) + host_cpu_load_info r_load; + + kern_return_t error; + mach_msg_type_number_t count; + + count = HOST_CPU_LOAD_INFO_COUNT; + mach_port_t port = mach_host_self(); + error = host_statistics(port, HOST_CPU_LOAD_INFO, + (host_info_t)&r_load, &count); + + if (error != KERN_SUCCESS) + return; + + currUserTicks = r_load.cpu_ticks[CPU_STATE_USER]; + currNiceTicks = r_load.cpu_ticks[CPU_STATE_NICE]; + currSysTicks = r_load.cpu_ticks[CPU_STATE_SYSTEM]; + currIdleTicks = r_load.cpu_ticks[CPU_STATE_IDLE]; + currWaitTicks = 0; + +#else + char buf[ 256 ]; + static int fd = -1; + + if ( fd < 0 ) { + if (( fd = open( "/proc/stat", O_RDONLY ) ) < 0 ) { + log_error() << "Cannot open file \'/proc/stat\'!\n" + "The kernel needs to be compiled with support\n" + "for /proc filesystem enabled!" << endl; + return; + } + fcntl(fd, F_SETFD, FD_CLOEXEC); + } + + lseek(fd, 0, SEEK_SET); + ssize_t n; + + while ( (n = read( fd, buf, sizeof(buf) -1 )) < 0 && errno == EINTR) + ; + + if ( n < 20 ) { + log_error() << "no enough data in /proc/stat?" << endl; + return; + } + buf[n] = 0; + + /* wait ticks only exist with Linux >= 2.6.0. treat as 0 otherwise */ + currWaitTicks = 0; + // sscanf( buf, "%*s %lu %lu %lu %lu %lu", &currUserTicks, &currNiceTicks, + sscanf( buf, "%*s %llu %llu %llu %llu %llu", &currUserTicks, &currNiceTicks, // RL modif + &currSysTicks, &currIdleTicks, &currWaitTicks ); +#endif + + totalTicks = ( currUserTicks - load->userTicks ) + + ( currSysTicks - load->sysTicks ) + + ( currNiceTicks - load->niceTicks ) + + ( currIdleTicks - load->idleTicks ) + + ( currWaitTicks - load->waitTicks ); + + if ( totalTicks > 10 ) { + load->userLoad = ( 1000 * ( currUserTicks - load->userTicks ) ) / totalTicks; + load->sysLoad = ( 1000 * ( currSysTicks - load->sysTicks ) ) / totalTicks; + load->niceLoad = ( 1000 * ( currNiceTicks - load->niceTicks ) ) / totalTicks; + load->idleLoad = ( 1000 - ( load->userLoad + load->sysLoad + load->niceLoad) ); + if ( load->idleLoad < 0 ) + load->idleLoad = 0; + } else { + load->userLoad = load->sysLoad = load->niceLoad = 0; + load->idleLoad = 1000; + } + + load->userTicks = currUserTicks; + load->sysTicks = currSysTicks; + load->niceTicks = currNiceTicks; + load->idleTicks = currIdleTicks; + load->waitTicks = currWaitTicks; +} + +#ifndef USE_SYSCTL +static unsigned long int scan_one( const char* buff, const char *key ) +{ + const char *b = strstr( buff, key ); + if ( !b ) + return 0; + unsigned long int val = 0; + if ( sscanf( b + strlen( key ), ": %lu", &val ) != 1 ) + return 0; + return val; +} +#endif + +static unsigned int calculateMemLoad( unsigned long int &NetMemFree ) +{ + unsigned long long MemFree = 0, Buffers = 0, Cached = 0; + +#ifdef USE_MACH + /* Get VM statistics. */ + vm_statistics_data_t vm_stat; + mach_msg_type_number_t count = sizeof(vm_stat) / sizeof(natural_t); + kern_return_t error = host_statistics(mach_host_self(), HOST_VM_INFO, + (host_info_t)&vm_stat, &count); + if (error != KERN_SUCCESS) + return 0; + + vm_size_t pagesize; + host_page_size(mach_host_self(), &pagesize); + + unsigned long long MemInactive = (unsigned long long) vm_stat.inactive_count * pagesize; + MemFree = (unsigned long long) vm_stat.free_count * pagesize; + + // blunt lie - but when's sche macht + Buffers = MemInactive; + +#elif defined( USE_SYSCTL ) + size_t len = sizeof (MemFree); + if ((sysctlbyname("vm.stats.vm.v_free_count", &MemFree, &len, NULL, 0) == -1) || !len) + MemFree = 0; /* Doesn't work under FreeBSD v2.2.x */ + + + len = sizeof (Buffers); + if ((sysctlbyname("vfs.bufspace", &Buffers, &len, NULL, 0) == -1) || !len) + Buffers = 0; /* Doesn't work under FreeBSD v2.2.x */ + + len = sizeof (Cached); + if ((sysctlbyname("vm.stats.vm.v_cache_count", &Cached, &len, NULL, 0) == -1) || !len) + Cached = 0; /* Doesn't work under FreeBSD v2.2.x */ +#else + /* The interesting information is definitely within the first 256 bytes */ + char buf[256]; + static int fd = -1; + + if ( fd < 0 ) { + if ( ( fd = open( "/proc/meminfo", O_RDONLY ) ) < 0 ) { + log_error() << "Cannot open file \'/proc/meminfo\'!\n" + "The kernel needs to be compiled with support\n" + "for /proc filesystem enabled!" << endl; + return 0; + } + fcntl(fd, F_SETFD, FD_CLOEXEC); + } + lseek (fd, 0, SEEK_SET); + ssize_t n; + while ((n = read( fd, buf, sizeof( buf ) -1 )) < 0 && errno == EINTR) + ; + if (n < 20) + return 0; + + buf[n] = '\0'; + MemFree = scan_one( buf, "MemFree" ); + Buffers = scan_one( buf, "Buffers" ); + Cached = scan_one( buf, "Cached" ); +#endif + + if ( Buffers > 50 * 1024 ) + Buffers -= 50 * 1024; + else + Buffers /= 2; + + if ( Cached > 50 * 1024 ) + Cached -= 50 * 1024; + else + Cached /= 2; + + NetMemFree = MemFree + Cached + Buffers; + if ( NetMemFree > 128 * 1024 ) + return 0; + else + return 1000 - ( NetMemFree * 1000 / ( 128 * 1024 ) ); +} + +// Load average calculation based on CALC_LOAD(), in the 2.6 Linux kernel +// oldVal - previous load avg. +// numJobs - current number of active jobs +// rate - update rate, in seconds (usually 60, 300, or 900) +// delta_t - time since last update, in seconds +double compute_load( double oldVal, unsigned int currentJobs, unsigned int rate, double delta_t ) +{ + double weight = 1.0 / exp( delta_t / rate ); + return oldVal * weight + currentJobs * (1.0 - weight); +} + +double getEpocTime() +{ + timeval tv; + gettimeofday( &tv, NULL ); + return (double) tv.tv_sec + (double) tv.tv_usec / 1000000.0; +} + +// Simulates getloadavg(), but only for specified number of jobs +// Note: this is stateful and not thread-safe! +// Also, it differs from getloadavg() in that its notion of load +// is only updated as often as it's called. +int fakeloadavg( double *p_result, int resultEntries, unsigned int currentJobs ) +{ + // internal state + static const int numLoads = 3; + static double loads[numLoads] = { 0.0, 0.0, 0.0 }; + static unsigned int rates[numLoads] = { 60, 300, 900 }; + static double lastUpdate = getEpocTime(); + + // First, update all state + double now = getEpocTime(); + double delta_t = std::max( now - lastUpdate, 0.0 ); // guard against user changing system time backwards + lastUpdate = now; + for (int l = 0; l < numLoads; l++) { + loads[l] = compute_load( loads[0], currentJobs, rates[l], delta_t ); + } + + // Then, return requested values + int numFilled = std::min( std::max( resultEntries, 0 ), numLoads ); + for (int n = 0; n < numFilled; n++) p_result[n] = loads[n]; + return numFilled; +} + +bool fill_stats( unsigned long &myidleload, unsigned long &myniceload, unsigned int &memory_fillgrade, StatsMsg *msg, unsigned int hint ) +{ + static CPULoadInfo load; + + updateCPULoad( &load ); + + myidleload = load.idleLoad; + myniceload = load.niceLoad; + + if ( msg ) { + unsigned long int MemFree = 0; + + memory_fillgrade = calculateMemLoad( MemFree ); + + double avg[3]; +#if HAVE_GETLOADAVG + getloadavg( avg, 3 ); + (void) hint; +#else + fakeloadavg( avg, 3, hint ); +#endif + msg->loadAvg1 = (load_t)( avg[0] * 1000 ); + msg->loadAvg5 = (load_t)( avg[1] * 1000 ); + msg->loadAvg10 = (load_t)( avg[2] * 1000 ); + + msg->freeMem = (load_t)( MemFree / 1024.0 + 0.5 ); + + } + return true; +} diff --git a/daemon/load.h b/daemon/load.h new file mode 100644 index 0000000..c726cbd --- /dev/null +++ b/daemon/load.h @@ -0,0 +1,29 @@ +/* + This file is part of Icecream. + + Copyright (c) 2004 Stephan Kulow <coolo@suse.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _LOAD_H_ +#define _LOAD_H_ + +#include <comm.h> + + // 'hint' is used to approximate the load, whenever getloadavg() is unavailable. +bool fill_stats( unsigned long &myidleload, unsigned long &myniceload, unsigned int &memory_fillgrade, StatsMsg *msg, unsigned int hint ); + +#endif diff --git a/daemon/main.cpp b/daemon/main.cpp new file mode 100644 index 0000000..4031e13 --- /dev/null +++ b/daemon/main.cpp @@ -0,0 +1,1681 @@ +/* + This file is part of Icecream. + + Copyright (c) 2004 Stephan Kulow <coolo@suse.de> + 2002, 2003 by Martin Pool <mbp@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +//#define ICECC_DEBUG 1 +#ifndef _GNU_SOURCE +// getopt_long +#define _GNU_SOURCE 1 +#endif +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <netdb.h> +#include <getopt.h> + +#ifdef HAVE_SIGNAL_H +#include <signal.h> +#endif +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/un.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <pwd.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <sys/utsname.h> + +#ifdef HAVE_ARPA_NAMESER_H +# include <arpa/nameser.h> +#endif + +#ifdef HAVE_SYS_VFS_H +#include <sys/vfs.h> +#endif + +#include <arpa/inet.h> + +#ifdef HAVE_RESOLV_H +# include <resolv.h> +#endif +#include <netdb.h> + +#ifdef HAVE_SYS_RESOURCE_H +# include <sys/resource.h> +#endif + +#ifndef RUSAGE_SELF +# define RUSAGE_SELF (0) +#endif +#ifndef RUSAGE_CHILDREN +# define RUSAGE_CHILDREN (-1) +#endif + +#include <deque> +#include <map> +#include <algorithm> +#include <set> +#include <fstream> +#include <string> + +#include "ncpus.h" +#include "exitcode.h" +#include "serve.h" +#include "workit.h" +#include "logging.h" +#include <comm.h> +#include "load.h" +#include "environment.h" +#include "platform.h" + +const int PORT = 10245; +static std::string pidFilePath; + +#ifndef __attribute_warn_unused_result__ +#define __attribute_warn_unused_result__ +#endif + +using namespace std; +using namespace __gnu_cxx; // for the extensions we like, e.g. hash_set + +struct Client { +public: + /* + * UNKNOWN: Client was just created - not supposed to be long term + * GOTNATIVE: Client asked us for the native env - this is the first step + * PENDING_USE_CS: We have a CS from scheduler and need to tell the client + * as soon as there is a spot available on the local machine + * JOBDONE: This was compiled by a local client and we got a jobdone - awaiting END + * LINKJOB: This is a local job (aka link job) by a local client we told the scheduler about + * and await the finish of it + * TOINSTALL: We're receiving an environment transfer and wait for it to complete. + * TOCOMPILE: We're supposed to compile it ourselves + * WAITFORCS: Client asked for a CS and we asked the scheduler - waiting for its answer + * WAITCOMPILE: Client got a CS and will ask him now (it's not me) + * CLIENTWORK: Client is busy working and we reserve the spot (job_id is set if it's a scheduler job) + * WAITFORCHILD: Client is waiting for the compile job to finish. + */ + enum Status { UNKNOWN, GOTNATIVE, PENDING_USE_CS, JOBDONE, LINKJOB, TOINSTALL, TOCOMPILE, + WAITFORCS, WAITCOMPILE, CLIENTWORK, WAITFORCHILD, LASTSTATE=WAITFORCHILD } status; + Client() + { + job_id = 0; + channel = 0; + job = 0; + usecsmsg = 0; + client_id = 0; + status = UNKNOWN; + pipe_to_child = -1; + child_pid = -1; + } + + static string status_str( Status status ) + { + switch ( status ) { + case UNKNOWN: + return "unknown"; + case GOTNATIVE: + return "gotnative"; + case PENDING_USE_CS: + return "pending_use_cs"; + case JOBDONE: + return "jobdone"; + case LINKJOB: + return "linkjob"; + case TOINSTALL: + return "toinstall"; + case TOCOMPILE: + return "tocompile"; + case WAITFORCS: + return "waitforcs"; + case CLIENTWORK: + return "clientwork"; + case WAITCOMPILE: + return "waitcompile"; + case WAITFORCHILD: + return "waitforchild"; + } + assert( false ); + return string(); // shutup gcc + } + + ~Client() + { + status = (Status) -1; + delete channel; + channel = 0; + delete usecsmsg; + usecsmsg = 0; + delete job; + job = 0; + if (pipe_to_child >= 0) + close (pipe_to_child); + + } + uint32_t job_id; + string outfile; // only useful for LINKJOB or TOINSTALL + MsgChannel *channel; + UseCSMsg *usecsmsg; + CompileJob *job; + int client_id; + int pipe_to_child; // pipe to child process, only valid if WAITFORCHILD or TOINSTALL + pid_t child_pid; + + string dump() const + { + string ret = status_str( status ) + " " + channel->dump(); + switch ( status ) { + case LINKJOB: + return ret + " CID: " + toString( client_id ) + " " + outfile; + case TOINSTALL: + return ret + " " + toString( client_id ) + " " + outfile; + case WAITFORCHILD: + return ret + " CID: " + toString( client_id ) + " PID: " + toString( child_pid ) + " PFD: " + toString( pipe_to_child ); + default: + if ( job_id ) { + string jobs; + if ( usecsmsg ) + { + jobs = " CS: " + usecsmsg->hostname; + } + return ret + " CID: " + toString( client_id ) + " ID: " + toString( job_id ) + jobs; + } + else + return ret + " CID: " + toString( client_id ); + } + return ret; + } +}; + +class Clients : public map<MsgChannel*, Client*> +{ +public: + Clients() { + active_processes = 0; + } + unsigned int active_processes; + + Client *find_by_client_id( int id ) const + { + for ( const_iterator it = begin(); it != end(); ++it ) + if ( it->second->client_id == id ) + return it->second; + return 0; + } + + Client *find_by_channel( MsgChannel *c ) const { + const_iterator it = find( c ); + if ( it == end() ) + return 0; + return it->second; + } + + Client *find_by_pid( pid_t pid ) const { + for ( const_iterator it = begin(); it != end(); ++it ) + if ( it->second->child_pid == pid ) + return it->second; + return 0; + } + + Client *first() + { + iterator it = begin(); + if ( it == end() ) + return 0; + Client *cl = it->second; + return cl; + } + + string dump_status(Client::Status s) const + { + int count = 0; + for ( const_iterator it = begin(); it != end(); ++it ) + { + if ( it->second->status == s ) + count++; + } + if ( count ) + return toString( count ) + " " + Client::status_str( s ) + ", "; + else + return string(); + } + + string dump_per_status() const { + string s; + for(Client::Status i = Client::UNKNOWN; i <= Client::LASTSTATE; + i=Client::Status(int(i)+1)) + s += dump_status(i); + return s; + } + Client *get_earliest_client( Client::Status s ) const + { + // TODO: possibly speed this up in adding some sorted lists + Client *client = 0; + int min_client_id = 0; + for ( const_iterator it = begin(); it != end(); ++it ) + if ( it->second->status == s && ( !min_client_id || min_client_id > it->second->client_id )) + { + client = it->second; + min_client_id = client->client_id; + } + return client; + } +}; + +static int set_new_pgrp(void) +{ + /* If we're a session group leader, then we are not able to call + * setpgid(). However, setsid will implicitly have put us into a new + * process group, so we don't have to do anything. */ + + /* Does everyone have getpgrp()? It's in POSIX.1. We used to call + * getpgid(0), but that is not available on BSD/OS. */ + if (getpgrp() == getpid()) { + trace() << "already a process group leader\n"; + return 0; + } + + if (setpgid(0, 0) == 0) { + trace() << "entered process group\n"; + return 0; + } else { + trace() << "setpgid(0, 0) failed: " << strerror(errno) << endl; + return EXIT_DISTCC_FAILED; + } +} + +static void dcc_daemon_terminate(int); + +/** + * Catch all relevant termination signals. Set up in parent and also + * applies to children. + **/ +void dcc_daemon_catch_signals(void) +{ + /* SIGALRM is caught to allow for built-in timeouts when running test + * cases. */ + + signal(SIGTERM, &dcc_daemon_terminate); + signal(SIGINT, &dcc_daemon_terminate); + signal(SIGALRM, &dcc_daemon_terminate); +} + +pid_t dcc_master_pid; + +/** + * Called when a daemon gets a fatal signal. + * + * Some cleanup is done only if we're the master/parent daemon. + **/ +static void dcc_daemon_terminate(int whichsig) +{ + /** + * This is a signal handler. don't do stupid stuff. + * Don't call printf. and especially don't call the log_*() functions. + */ + + bool am_parent = ( getpid() == dcc_master_pid ); + + /* Make sure to remove handler before re-raising signal, or + * Valgrind gets its kickers in a knot. */ + signal(whichsig, SIG_DFL); + + if (am_parent) { + /* kill whole group */ + kill(0, whichsig); + + /* Remove pid file */ + unlink(pidFilePath.c_str()); + } + + raise(whichsig); +} + +void usage(const char* reason = 0) +{ + if (reason) + cerr << reason << endl; + + cerr << "usage: iceccd [-n <netname>] [-m <max_processes>] [--no-remote] [-w] [-d|--daemonize] [-l logfile] [-s <schedulerhost>] [-v[v[v]]] [-r|--run-as-user] [-b <env-basedir>] [-u|--nobody-uid <nobody_uid>] [--cache-limit <MB>] [-N <node_name>]" << endl; + exit(1); +} + +int setup_listen_fd() +{ + int listen_fd; + if ((listen_fd = socket (PF_INET, SOCK_STREAM, 0)) < 0) { + log_perror ("socket()"); + return -1; + } + + int optval = 1; + if (setsockopt (listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) { + log_perror ("setsockopt()"); + return -1; + } + + int count = 5; + while ( count ) { + struct sockaddr_in myaddr; + myaddr.sin_family = AF_INET; + myaddr.sin_port = htons (PORT); + myaddr.sin_addr.s_addr = INADDR_ANY; + if (bind (listen_fd, (struct sockaddr *) &myaddr, + sizeof (myaddr)) < 0) { + log_perror ("bind()"); + sleep( 2 ); + if ( !--count ) + return -1; + continue; + } else + break; + } + + if (listen (listen_fd, 20) < 0) + { + log_perror ("listen()"); + return -1; + } + + fcntl(listen_fd, F_SETFD, FD_CLOEXEC); + + return listen_fd; +} + + +struct timeval last_stat; +int mem_limit = 100; +unsigned int max_kids = 0; + +size_t cache_size_limit = 100 * 1024 * 1024; + +struct Daemon +{ + Clients clients; + map<string, time_t> envs_last_use; + string native_environment; + string envbasedir; + uid_t nobody_uid; + gid_t nobody_gid; + int listen_fd; + string machine_name; + string nodename; + bool noremote; + bool custom_nodename; + size_t cache_size; + map<int, MsgChannel *> fd2chan; + int new_client_id; + string remote_name; + time_t next_scheduler_connect; + unsigned long icecream_load; + struct timeval icecream_usage; + int current_load; + int num_cpus; + MsgChannel *scheduler; + DiscoverSched *discover; + string netname; + string schedname; + + int max_scheduler_pong; + int max_scheduler_ping; + string bench_source; + unsigned int current_kids; + + Daemon() { + envbasedir = "/tmp/icecc-envs"; + nobody_uid = 65534; + nobody_gid = 65533; + listen_fd = -1; + new_client_id = 0; + next_scheduler_connect = 0; + cache_size = 0; + noremote = false; + custom_nodename = false; + icecream_load = 0; + icecream_usage.tv_sec = icecream_usage.tv_usec = 0; + current_load = - 1000; + num_cpus = 0; + scheduler = 0; + discover = 0; + max_scheduler_pong = MAX_SCHEDULER_PONG; + max_scheduler_ping = MAX_SCHEDULER_PING; + bench_source = ""; + current_kids = 0; + } + + bool reannounce_environments() __attribute_warn_unused_result__; + int answer_client_requests(); + bool handle_transfer_env( Client *client, Msg *msg ) __attribute_warn_unused_result__; + bool handle_transfer_env_done( Client *client ); + bool handle_get_native_env( Client *client ) __attribute_warn_unused_result__; + void handle_old_request(); + bool handle_compile_file( Client *client, Msg *msg ) __attribute_warn_unused_result__; + bool handle_activity( Client *client ) __attribute_warn_unused_result__; + bool handle_file_chunk_env(Client* client, Msg *msg) __attribute_warn_unused_result__; + void handle_end( Client *client, int exitcode ); + int scheduler_get_internals( ) __attribute_warn_unused_result__; + void clear_children(); + int scheduler_use_cs( UseCSMsg *msg ) __attribute_warn_unused_result__; + bool handle_get_cs( Client *client, Msg *msg ) __attribute_warn_unused_result__; + bool handle_local_job( Client *client, Msg *msg ) __attribute_warn_unused_result__; + bool handle_job_done( Client *cl, JobDoneMsg *m ) __attribute_warn_unused_result__; + bool handle_compile_done (Client* client) __attribute_warn_unused_result__; + int handle_cs_conf( ConfCSMsg *msg); + string dump_internals() const; + string determine_nodename(); + void determine_system(); + bool maybe_stats(bool force = false); + bool send_scheduler(const Msg& msg) __attribute_warn_unused_result__; + void close_scheduler(); + bool reconnect(); + int working_loop(); +}; + +void Daemon::determine_system() +{ + struct utsname uname_buf; + if ( uname( &uname_buf ) ) { + log_perror( "uname call failed" ); + return; + } + + if ( nodename.length() && nodename != uname_buf.nodename ) + custom_nodename = true; + + if (!custom_nodename) + nodename = uname_buf.nodename; + + machine_name = determine_platform(); +} + +string Daemon::determine_nodename() +{ + if (custom_nodename && !nodename.empty()) + return nodename; + + // perhaps our host name changed due to network change? + struct utsname uname_buf; + if ( !uname( &uname_buf ) ) + nodename = uname_buf.nodename; + + return nodename; +} + +bool Daemon::send_scheduler(const Msg& msg) +{ + if (!scheduler) { + log_error() << "scheduler dead ?!" << endl; + return false; + } + + if (!scheduler->send_msg(msg)) { + log_error() << "sending to scheduler failed.." << endl; + close_scheduler(); + return false; + } + + return true; +} + +bool Daemon::reannounce_environments() +{ + log_error() << "reannounce_environments " << endl; + LoginMsg lmsg( 0, nodename, ""); + lmsg.envs = available_environmnents(envbasedir); + return send_scheduler( lmsg ); +} + +void Daemon::close_scheduler() +{ + if ( !scheduler ) + return; + + delete scheduler; + scheduler = 0; + delete discover; + discover = 0; + next_scheduler_connect = time(0) + 20 + (rand() & 31); +} + +bool Daemon::maybe_stats(bool send_ping) +{ + struct timeval now; + gettimeofday( &now, 0 ); + + time_t diff_sent = ( now.tv_sec - last_stat.tv_sec ) * 1000 + ( now.tv_usec - last_stat.tv_usec ) / 1000; + if ( diff_sent >= max_scheduler_pong * 1000 ) { + StatsMsg msg; + unsigned int memory_fillgrade; + unsigned long idleLoad = 0; + unsigned long niceLoad = 0; + + if ( !fill_stats( idleLoad, niceLoad, memory_fillgrade, &msg, clients.active_processes ) ) + return false; + + time_t diff_stat = ( now.tv_sec - last_stat.tv_sec ) * 1000 + ( now.tv_usec - last_stat.tv_usec ) / 1000; + last_stat = now; + + /* icecream_load contains time in milliseconds we have used for icecream */ + /* idle time could have been used for icecream, so claim it */ + icecream_load += idleLoad * diff_stat / 1000; + + /* add the time of our childrens, but only the time since the last run */ + struct rusage ru; + if (!getrusage(RUSAGE_CHILDREN, &ru)) { + uint32_t ice_msec = ( ( ru.ru_utime.tv_sec - icecream_usage.tv_sec ) * 1000 + + ( ru.ru_utime.tv_usec - icecream_usage.tv_usec ) / 1000) / num_cpus; + + /* heuristics when no child terminated yet: account 25% of total nice as our clients */ + if ( !ice_msec && current_kids ) + ice_msec = (niceLoad * diff_stat) / (4 * 1000); + + icecream_load += ice_msec * diff_stat / 1000; + + icecream_usage.tv_sec = ru.ru_utime.tv_sec; + icecream_usage.tv_usec = ru.ru_utime.tv_usec; + } + + int idle_average = icecream_load; + + if (diff_sent) + idle_average = icecream_load * 1000 / diff_sent; + + if (idle_average > 1000) + idle_average = 1000; + + msg.load = ( 700 * (1000 - idle_average) + 300 * memory_fillgrade ) / 1000; + if ( memory_fillgrade > 600 ) + msg.load = 1000; + if ( idle_average < 100 ) + msg.load = 1000; + +#ifdef HAVE_SYS_VFS_H + struct statfs buf; + int ret = statfs(envbasedir.c_str(), &buf); + if (!ret && long(buf.f_bavail) < long(max_kids + 1 - current_kids) * 4 * 1024 * 1024 / buf.f_bsize) + msg.load = 1000; +#endif + + // Matz got in the urine that not all CPUs are always feed + mem_limit = std::max( int( msg.freeMem / std::min( std::max( max_kids, 1U ), 4U ) ), int( 100U ) ); + + if ( abs(int(msg.load)-current_load) >= 100 || send_ping ) { + if (!send_scheduler( msg ) ) + return false; + } + icecream_load = 0; + current_load = msg.load; + } + + return true; +} + +string Daemon::dump_internals() const +{ + string result; + result += "Node Name: " + nodename + "\n"; + result += " Remote name: " + remote_name + "\n"; + for (map<int, MsgChannel *>::const_iterator it = fd2chan.begin(); + it != fd2chan.end(); ++it) { + result += " fd2chan[" + toString( it->first ) + "] = " + it->second->dump() + "\n"; + } + for (Clients::const_iterator it = clients.begin(); + it != clients.end(); ++it) { + result += " client " + toString( it->second->client_id ) + ": " + + it->second->dump() + "\n"; + } + if ( cache_size ) + result += " Cache Size: " + toString( cache_size ) + "\n"; + result += " Architecture: " + machine_name + "\n"; + if ( !native_environment.empty() ) + result += " NativeEnv: " + native_environment + "\n"; + + if ( !envs_last_use.empty() ) + result += " Now: " + toString( time( 0 ) ) + "\n"; + for (map<string, time_t>::const_iterator it = envs_last_use.begin(); + it != envs_last_use.end(); ++it) { + result += " envs_last_use[" + it->first + "] = " + + toString( it->second ) + "\n"; + } + + result += " Current kids: " + toString( current_kids ) + " (max: " + toString( max_kids ) + ")\n"; + if ( scheduler ) + result += " Scheduler protocol: " + toString( scheduler->protocol ) + "\n"; + + StatsMsg msg; + unsigned int memory_fillgrade = 0; + unsigned long idleLoad = 0; + unsigned long niceLoad = 0; + + if ( fill_stats( idleLoad, niceLoad, memory_fillgrade, &msg, clients.active_processes ) ) + { + result += " cpu: " + toString( idleLoad ) + " idle, " + + toString( niceLoad ) + " nice\n"; + result += " load: " + toString( msg.loadAvg1 / 1000. ) + ", icecream_load: " + + toString( icecream_load ) + "\n"; + result += " memory: " + toString( memory_fillgrade ) + " (free: " + toString( msg.freeMem ) + ")\n"; + } + + return result; +} + +int Daemon::scheduler_get_internals( ) +{ + trace() << "handle_get_internals " << dump_internals() << endl; + return send_scheduler( StatusTextMsg( dump_internals() ) ) ? 0 : 1; +} + +int Daemon::scheduler_use_cs( UseCSMsg *msg ) +{ + Client *c = clients.find_by_client_id( msg->client_id ); + trace() << "handle_use_cs " << msg->job_id << " " << msg->client_id + << " " << c << " " << msg->hostname << " " << remote_name << endl; + if ( !c ) { + if (send_scheduler( JobDoneMsg( msg->job_id, 107, JobDoneMsg::FROM_SUBMITTER ) )) + return 1; + return 1; + } + if ( msg->hostname == remote_name ) { + c->usecsmsg = new UseCSMsg( msg->host_platform, "127.0.0.1", PORT, msg->job_id, true, 1, + msg->matched_job_id ); + c->status = Client::PENDING_USE_CS; + } else { + c->usecsmsg = new UseCSMsg( msg->host_platform, msg->hostname, msg->port, + msg->job_id, true, 1, msg->matched_job_id ); + if (!c->channel->send_msg( *msg )) { + handle_end(c, 143); + return 0; + } + c->status = Client::WAITCOMPILE; + } + c->job_id = msg->job_id; + return 0; +} + +bool Daemon::handle_transfer_env( Client *client, Msg *_msg ) +{ + log_error() << "handle_transfer_env" << endl; + + assert(client->status != Client::TOINSTALL && + client->status != Client::TOCOMPILE && + client->status != Client::WAITCOMPILE); + assert(client->pipe_to_child < 0); + + EnvTransferMsg *emsg = static_cast<EnvTransferMsg*>( _msg ); + string target = emsg->target; + if ( target.empty() ) + target = machine_name; + + int sock_to_stdin = -1; + FileChunkMsg* fmsg = 0; + + pid_t pid = start_install_environment( envbasedir, emsg->target, + emsg->name, client->channel, sock_to_stdin, fmsg, nobody_uid, nobody_gid ); + + client->status = Client::TOINSTALL; + client->outfile = emsg->target + "/" + emsg->name; + + if ( pid > 0) { + log_error() << "got pid " << pid << endl; + current_kids++; + client->pipe_to_child = sock_to_stdin; + client->child_pid = pid; + if (!handle_file_chunk_env(client, fmsg)) + pid = 0; + } + if (pid <= 0) + handle_transfer_env_done (client); + + delete fmsg; + return pid > 0; +} + +bool Daemon::handle_transfer_env_done( Client *client ) +{ + log_error() << "handle_transfer_env_done" << endl; + + assert(client->outfile.size()); + assert(client->status == Client::TOINSTALL); + + size_t installed_size = finalize_install_environment(envbasedir, client->outfile, + client->child_pid, nobody_gid); + + if (client->pipe_to_child >= 0) { + installed_size = 0; + close(client->pipe_to_child); + client->pipe_to_child = -1; + } + + client->status = Client::UNKNOWN; + string current = client->outfile; + client->outfile.clear(); + client->child_pid = -1; + assert( current_kids > 0 ); + current_kids--; + + log_error() << "installed_size: " << installed_size << endl; + + if (installed_size) { + cache_size += installed_size; + envs_last_use[current] = time( NULL ); + log_error() << "installed " << current << " size: " << installed_size + << " all: " << cache_size << endl; + } + + time_t now = time( NULL ); + while ( cache_size > cache_size_limit ) { + string oldest; + // I don't dare to use (time_t)-1 + time_t oldest_time = time( NULL ) + 90000; + for ( map<string, time_t>::const_iterator it = envs_last_use.begin(); + it != envs_last_use.end(); ++it ) { + trace() << "das ist jetzt so: " << it->first << " " << it->second << " " << oldest_time << endl; + // ignore recently used envs (they might be in use _right_ now) + if ( it->second < oldest_time && now - it->second > 200 ) { + bool env_currently_in_use = false; + for (Clients::const_iterator it2 = clients.begin(); it2 != clients.end(); ++it2) { + if (it2->second->status == Client::TOCOMPILE || + it2->second->status == Client::TOINSTALL || + it2->second->status == Client::WAITFORCHILD) { + + assert( it2->second->job ); + string envforjob = it2->second->job->targetPlatform() + "/" + + it2->second->job->environmentVersion(); + if (envforjob == it->first) + env_currently_in_use = true; + } + } + if (!env_currently_in_use) { + oldest_time = it->second; + oldest = it->first; + } + } + } + if ( oldest.empty() || oldest == current ) + break; + size_t removed = remove_environment( envbasedir, oldest ); + trace() << "removing " << envbasedir << "/" << oldest << " " << oldest_time << " " << removed << endl; + cache_size -= min( removed, cache_size ); + envs_last_use.erase( oldest ); + } + + bool r = reannounce_environments(); // do that before the file compiles + // we do that here so we're not given out in case of full discs + if ( !maybe_stats(true) ) + r = false; + return r; +} + +bool Daemon::handle_get_native_env( Client *client ) +{ + trace() << "get_native_env " << native_environment << endl; + + if ( !native_environment.length() ) { + size_t installed_size = setup_env_cache( envbasedir, native_environment, + nobody_uid, nobody_gid ); + // we only clean out cache on next target install + cache_size += installed_size; + trace() << "cache_size = " << cache_size << endl; + if ( ! installed_size ) { + client->channel->send_msg( EndMsg() ); + handle_end( client, 121 ); + return false; + } + } + UseNativeEnvMsg m( native_environment ); + if (!client->channel->send_msg( m )) { + handle_end(client, 138); + return false; + } + client->status = Client::GOTNATIVE; + return true; +} + +bool Daemon::handle_job_done( Client *cl, JobDoneMsg *m ) +{ + if ( cl->status == Client::CLIENTWORK ) + clients.active_processes--; + cl->status = Client::JOBDONE; + JobDoneMsg *msg = static_cast<JobDoneMsg*>( m ); + trace() << "handle_job_done " << msg->job_id << " " << msg->exitcode << endl; + + if(!m->is_from_server() + && ( m->user_msec + m->sys_msec ) <= m->real_msec) + icecream_load += (m->user_msec + m->sys_msec) / num_cpus; + + assert(msg->job_id == cl->job_id); + cl->job_id = 0; // the scheduler doesn't have it anymore + return send_scheduler( *msg ); +} + +void Daemon::handle_old_request() +{ + while ( current_kids + clients.active_processes < max_kids ) { + + Client *client = clients.get_earliest_client(Client::LINKJOB); + if ( client ) { + trace() << "send JobLocalBeginMsg to client" << endl; + if (!client->channel->send_msg (JobLocalBeginMsg())) { + log_warning() << "can't send start message to client" << endl; + handle_end (client, 112); + } else { + client->status = Client::CLIENTWORK; + clients.active_processes++; + trace() << "pushed local job " << client->client_id << endl; + if (!send_scheduler( JobLocalBeginMsg( client->client_id, client->outfile ) )) + return; + } + continue; + } + + client = clients.get_earliest_client( Client::PENDING_USE_CS ); + if ( client ) { + trace() << "pending " << client->dump() << endl; + if(client->channel->send_msg( *client->usecsmsg )) { + client->status = Client::CLIENTWORK; + /* we make sure we reserve a spot and the rest is done if the + * client contacts as back with a Compile request */ + clients.active_processes++; + } + else + handle_end(client, 129); + + continue; + } + + /* we don't want to handle TOCOMPILE jobs as long as our load + is too high */ + if ( current_load >= 1000) + break; + + client = clients.get_earliest_client( Client::TOCOMPILE ); + if ( client ) { + CompileJob *job = client->job; + assert( job ); + int sock = -1; + pid_t pid = -1; + + trace() << "requests--" << job->jobID() << endl; + + string envforjob = job->targetPlatform() + "/" + job->environmentVersion(); + envs_last_use[envforjob] = time( NULL ); + pid = handle_connection( envbasedir, job, client->channel, sock, mem_limit, nobody_uid, nobody_gid ); + trace() << "handle connection returned " << pid << endl; + + if ( pid > 0) { + current_kids++; + client->status = Client::WAITFORCHILD; + client->pipe_to_child = sock; + client->child_pid = pid; + if ( !send_scheduler( JobBeginMsg( job->jobID() ) ) ) + log_info() << "failed sending scheduler about " << job->jobID() << endl; + } + else + handle_end(client, 117); + continue; + } + break; + } +} + +bool Daemon::handle_compile_done (Client* client) +{ + assert(client->status == Client::WAITFORCHILD); + assert(client->child_pid > 0); + assert(client->pipe_to_child >= 0); + + JobDoneMsg *msg = new JobDoneMsg(client->job->jobID(), -1, JobDoneMsg::FROM_SERVER); + assert(msg); + assert(current_kids > 0); + current_kids--; + + unsigned int job_stat[8]; + int end_status = 151; + + if(read(client->pipe_to_child, job_stat, sizeof(job_stat)) == sizeof(job_stat)) { + msg->in_uncompressed = job_stat[JobStatistics::in_uncompressed]; + msg->in_compressed = job_stat[JobStatistics::in_compressed]; + msg->out_compressed = msg->out_uncompressed = job_stat[JobStatistics::out_uncompressed]; + end_status = msg->exitcode = job_stat[JobStatistics::exit_code]; + msg->real_msec = job_stat[JobStatistics::real_msec]; + msg->user_msec = job_stat[JobStatistics::user_msec]; + msg->sys_msec = job_stat[JobStatistics::sys_msec]; + msg->pfaults = job_stat[JobStatistics::sys_pfaults]; + end_status = job_stat[JobStatistics::exit_code]; + } + + close(client->pipe_to_child); + client->pipe_to_child = -1; + string envforjob = client->job->targetPlatform() + "/" + client->job->environmentVersion(); + envs_last_use[envforjob] = time( NULL ); + + bool r = send_scheduler( *msg ); + handle_end(client, end_status); + delete msg; + return r; +} + +bool Daemon::handle_compile_file( Client *client, Msg *msg ) +{ + CompileJob *job = dynamic_cast<CompileFileMsg*>( msg )->takeJob(); + assert( client ); + assert( job ); + client->job = job; + if ( client->status == Client::CLIENTWORK ) + { + assert( job->environmentVersion() == "__client" ); + if ( !send_scheduler( JobBeginMsg( job->jobID() ) ) ) + { + trace() << "can't reach scheduler to tell him about compile file job " + << job->jobID() << endl; + return false; + } + // no scheduler is not an error case! + } else + client->status = Client::TOCOMPILE; + return true; +} + +void Daemon::handle_end( Client *client, int exitcode ) +{ +#ifdef ICECC_DEBUG + trace() << "handle_end " << client->dump() << endl; + trace() << dump_internals() << endl; +#endif + fd2chan.erase (client->channel->fd); + + if (client->status == Client::TOINSTALL && client->pipe_to_child >= 0) + { + close(client->pipe_to_child); + client->pipe_to_child = -1; + handle_transfer_env_done(client); + } + + if ( client->status == Client::CLIENTWORK ) + clients.active_processes--; + + if ( client->status == Client::WAITCOMPILE && exitcode == 119 ) { + /* the client sent us a real good bye, so forget about the scheduler */ + client->job_id = 0; + } + + /* Delete from the clients map before send_scheduler, which causes a + double deletion. */ + if (!clients.erase( client->channel )) + { + log_error() << "client can't be erased: " << client->channel << endl; + flush_debug(); + log_error() << dump_internals() << endl; + flush_debug(); + assert(false); + } + + if ( scheduler && client->status != Client::WAITFORCHILD ) { + int job_id = client->job_id; + if ( client->status == Client::TOCOMPILE ) + job_id = client->job->jobID(); + if ( client->status == Client::WAITFORCS ) { + job_id = client->client_id; // it's all we have + exitcode = CLIENT_WAS_WAITING_FOR_CS; // this is the message + } + + if ( job_id > 0 ) { + JobDoneMsg::from_type flag = JobDoneMsg::FROM_SUBMITTER; + switch ( client->status ) { + case Client::TOCOMPILE: + flag = JobDoneMsg::FROM_SERVER; + break; + case Client::UNKNOWN: + case Client::GOTNATIVE: + case Client::JOBDONE: + case Client::WAITFORCHILD: + case Client::LINKJOB: + case Client::TOINSTALL: + assert( false ); // should not have a job_id + break; + case Client::WAITCOMPILE: + case Client::PENDING_USE_CS: + case Client::CLIENTWORK: + case Client::WAITFORCS: + flag = JobDoneMsg::FROM_SUBMITTER; + break; + } + trace() << "scheduler->send_msg( JobDoneMsg( " << client->dump() << ", " << exitcode << "))\n"; + if (!send_scheduler( JobDoneMsg( job_id, exitcode, flag) )) + trace() << "failed to reach scheduler for remote job done msg!" << endl; + } else if ( client->status == Client::CLIENTWORK ) { + // Clientwork && !job_id == LINK + trace() << "scheduler->send_msg( JobLocalDoneMsg( " << client->client_id << ") );\n"; + if (!send_scheduler( JobLocalDoneMsg( client->client_id ) )) + trace() << "failed to reach scheduler for local job done msg!" << endl; + } + } + + delete client; +} + +void Daemon::clear_children() +{ + while ( !clients.empty() ) { + Client *cl = clients.first(); + handle_end( cl, 116 ); + } + + while ( current_kids > 0 ) { + int status; + pid_t child; + while ( (child = waitpid( -1, &status, 0 )) < 0 && errno == EINTR ) + ; + current_kids--; + } + + // they should be all in clients too + assert( fd2chan.empty() ); + + fd2chan.clear(); + new_client_id = 0; + trace() << "cleared children\n"; +} + +bool Daemon::handle_get_cs( Client *client, Msg *msg ) +{ + GetCSMsg *umsg = dynamic_cast<GetCSMsg*>( msg ); + assert( client ); + client->status = Client::WAITFORCS; + umsg->client_id = client->client_id; + trace() << "handle_get_cs " << umsg->client_id << endl; + if ( !scheduler ) + { + /* now the thing is this: if there is no scheduler + there is no point in trying to ask him. So we just + redefine this as local job */ + client->usecsmsg = new UseCSMsg( umsg->target, "127.0.0.1", PORT, + umsg->client_id, true, 1, 0 ); + client->status = Client::PENDING_USE_CS; + client->job_id = umsg->client_id; + return true; + } + + return send_scheduler( *umsg ); +} + +int Daemon::handle_cs_conf(ConfCSMsg* msg) +{ + max_scheduler_pong = msg->max_scheduler_pong; + max_scheduler_ping = msg->max_scheduler_ping; + bench_source = msg->bench_source; + + return 0; +} + +bool Daemon::handle_local_job( Client *client, Msg *msg ) +{ + client->status = Client::LINKJOB; + client->outfile = dynamic_cast<JobLocalBeginMsg*>( msg )->outfile; + return true; +} + +bool Daemon::handle_file_chunk_env(Client *client, Msg *msg) +{ + /* this sucks, we can block when we're writing + the file chunk to the child, but we can't let the child + handle MsgChannel itself due to MsgChannel's stupid + caching layer inbetween, which causes us to loose partial + data after the M_END msg of the env transfer. */ + + assert (client && client->status == Client::TOINSTALL); + + if (msg->type == M_FILE_CHUNK && client->pipe_to_child >= 0) + { + FileChunkMsg *fcmsg = static_cast<FileChunkMsg*>( msg ); + ssize_t len = fcmsg->len; + off_t off = 0; + while ( len ) { + ssize_t bytes = write( client->pipe_to_child, fcmsg->buffer + off, len ); + if ( bytes < 0 && errno == EINTR ) + continue; + + if ( bytes == -1 ) { + log_perror("write to transfer env pipe failed. "); + + delete msg; + msg = 0; + handle_end(client, 137); + return false; + } + + len -= bytes; + off += bytes; + } + return true; + } + + if (msg->type == M_END) { + close(client->pipe_to_child); + client->pipe_to_child = -1; + return handle_transfer_env_done(client); + } + + if (client->pipe_to_child >= 0) + handle_end(client, 138); + + return false; +} + +bool Daemon::handle_activity( Client *client ) +{ + assert(client->status != Client::TOCOMPILE); + + Msg *msg = client->channel->get_msg(); + if ( !msg ) { + handle_end( client, 118 ); + return false; + } + + bool ret = false; + if (client->status == Client::TOINSTALL && client->pipe_to_child >= 0) + ret = handle_file_chunk_env(client, msg); + + if (ret) { + delete msg; + return ret; + } + + switch ( msg->type ) { + case M_GET_NATIVE_ENV: ret = handle_get_native_env( client ); break; + case M_COMPILE_FILE: ret = handle_compile_file( client, msg ); break; + case M_TRANFER_ENV: ret = handle_transfer_env( client, msg ); break; + case M_GET_CS: ret = handle_get_cs( client, msg ); break; + case M_END: handle_end( client, 119 ); ret = false; break; + case M_JOB_LOCAL_BEGIN: ret = handle_local_job (client, msg); break; + case M_JOB_DONE: ret = handle_job_done( client, dynamic_cast<JobDoneMsg*>(msg) ); break; + default: + log_error() << "not compile: " << ( char )msg->type << "protocol error on client " << client->dump() << endl; + client->channel->send_msg( EndMsg() ); + handle_end( client, 120 ); + ret = false; + } + delete msg; + return ret; +} + +int Daemon::answer_client_requests() +{ +#ifdef ICECC_DEBUG + if ( clients.size() + current_kids ) + log_info() << dump_internals() << endl; + log_info() << "clients " << clients.dump_per_status() << " " << current_kids << " (" << max_kids << ")" << endl; + +#endif + + /* reap zombis */ + int status; + while (waitpid(-1, &status, WNOHANG) < 0 && errno == EINTR) + ; + + handle_old_request(); + + /* collect the stats after the children exited icecream_load */ + if ( scheduler ) + maybe_stats(); + + fd_set listen_set; + struct timeval tv; + + FD_ZERO( &listen_set ); + FD_SET( listen_fd, &listen_set ); + int max_fd = listen_fd; + + for (map<int, MsgChannel *>::const_iterator it = fd2chan.begin(); + it != fd2chan.end();) { + int i = it->first; + MsgChannel *c = it->second; + ++it; + /* don't select on a fd that we're currently not interested in. + Avoids that we wake up on an event we're not handling anyway */ + Client* client = clients.find_by_channel(c); + assert(client); + int current_status = client->status; + bool ignore_channel = current_status == Client::TOCOMPILE || + current_status == Client::WAITFORCHILD; + if (!ignore_channel && (!c->has_msg() || handle_activity(client))) { + if (i > max_fd) + max_fd = i; + FD_SET (i, &listen_set); + } + + if (current_status == Client::WAITFORCHILD + && client->pipe_to_child != -1) { + if (client->pipe_to_child > max_fd) + max_fd = client->pipe_to_child; + FD_SET (client->pipe_to_child, &listen_set); + } + } + + if ( scheduler ) { + FD_SET( scheduler->fd, &listen_set ); + if ( max_fd < scheduler->fd ) + max_fd = scheduler->fd; + } else if ( discover && discover->listen_fd() >= 0) { + /* We don't explicitely check for discover->get_fd() being in + the selected set below. If it's set, we simply will return + and our call will make sure we try to get the scheduler. */ + FD_SET( discover->listen_fd(), &listen_set); + if ( max_fd < discover->listen_fd() ) + max_fd = discover->listen_fd(); + } + + tv.tv_sec = max_scheduler_pong; + tv.tv_usec = 0; + + int ret = select (max_fd + 1, &listen_set, NULL, NULL, &tv); + if ( ret < 0 && errno != EINTR ) { + log_perror( "select" ); + return 5; + } + + if ( ret > 0 ) { + bool had_scheduler = scheduler; + if ( scheduler && FD_ISSET( scheduler->fd, &listen_set ) ) { + while (!scheduler->read_a_bit() || scheduler->has_msg()) { + Msg *msg = scheduler->get_msg(); + if ( !msg ) { + log_error() << "scheduler closed connection\n"; + close_scheduler(); + clear_children(); + return 1; + } else { + ret = 0; + switch ( msg->type ) + { + case M_PING: + if (!IS_PROTOCOL_27(scheduler)) + ret = !send_scheduler(PingMsg()); + break; + case M_USE_CS: + ret = scheduler_use_cs( static_cast<UseCSMsg*>( msg ) ); + break; + case M_GET_INTERNALS: + ret = scheduler_get_internals( ); + break; + case M_CS_CONF: + ret = handle_cs_conf(static_cast<ConfCSMsg*>( msg )); + break; + default: + log_error() << "unknown scheduler type " << ( char )msg->type << endl; + ret = 1; + } + } + delete msg; + if (ret) + return ret; + } + } + + if ( FD_ISSET( listen_fd, &listen_set ) ) { + struct sockaddr cli_addr; + socklen_t cli_len = sizeof cli_addr; + int acc_fd = accept(listen_fd, &cli_addr, &cli_len); + if (acc_fd < 0) + log_perror("accept error"); + if (acc_fd == -1 && errno != EINTR) { + log_perror("accept failed:"); + return EXIT_CONNECT_FAILED; + } else { + MsgChannel *c = Service::createChannel( acc_fd, &cli_addr, cli_len ); + if ( !c ) + return 0; + trace() << "accepted " << c->fd << " " << c->name << endl; + + Client *client = new Client; + client->client_id = ++new_client_id; + client->channel = c; + clients[c] = client; + + fd2chan[c->fd] = c; + while (!c->read_a_bit() || c->has_msg()) { + if (!handle_activity(client)) + break; + if (client->status == Client::TOCOMPILE || + client->status == Client::WAITFORCHILD) + break; + } + } + } else { + for (map<int, MsgChannel *>::const_iterator it = fd2chan.begin(); + max_fd && it != fd2chan.end();) { + int i = it->first; + MsgChannel *c = it->second; + Client* client = clients.find_by_channel(c); + assert(client); + ++it; + if (client->status == Client::WAITFORCHILD + && client->pipe_to_child >= 0 + && FD_ISSET(client->pipe_to_child, &listen_set) ) + { + max_fd--; + if (!handle_compile_done(client)) + return 1; + } + + if (FD_ISSET (i, &listen_set)) { + assert(client->status != Client::TOCOMPILE); + while (!c->read_a_bit() || c->has_msg()) { + if (!handle_activity(client)) + break; + if (client->status == Client::TOCOMPILE || + client->status == Client::WAITFORCHILD) + break; + } + max_fd--; + } + } + } + if ( had_scheduler && !scheduler ) { + clear_children(); + return 2; + } + + } + return 0; +} + +bool Daemon::reconnect() +{ + if ( scheduler ) + return true; + + if (!discover && + next_scheduler_connect > time(0)) { + trace() << "timeout.." << endl; + return false; + } + + trace() << "reconn " << dump_internals() << endl; + if (!discover + || discover->timed_out()) + { + delete discover; + discover = new DiscoverSched (netname, max_scheduler_pong, schedname); + } + + scheduler = discover->try_get_scheduler (); + if ( !scheduler ) { + log_warning() << "scheduler not yet found.\n"; + return false; + } + delete discover; + discover = 0; + sockaddr_in name; + socklen_t len = sizeof(name); + int error = getsockname(scheduler->fd, (struct sockaddr*)&name, &len); + if ( !error ) + remote_name = inet_ntoa( name.sin_addr ); + else + remote_name = string(); + log_info() << "Connected to scheduler (I am known as" << remote_name << ")\n"; + current_load = -1000; + gettimeofday( &last_stat, 0 ); + icecream_load = 0; + + LoginMsg lmsg( PORT, determine_nodename(), machine_name ); + lmsg.envs = available_environmnents(envbasedir); + lmsg.max_kids = max_kids; + lmsg.noremote = noremote; + return send_scheduler ( lmsg ); +} + +int Daemon::working_loop() +{ + for (;;) { + reconnect(); + + int ret = answer_client_requests(); + if ( ret ) { + trace() << "answer_client_requests returned " << ret << endl; + close_scheduler(); + } + } + // never really reached + return 0; +} + +int main( int argc, char ** argv ) +{ + int max_processes = -1; + srand( time( 0 ) + getpid() ); + + Daemon d; + + int debug_level = Error; + string logfile; + bool detach = false; + nice_level = 5; // defined in serve.h + + while ( true ) { + int option_index = 0; + static const struct option long_options[] = { + { "netname", 1, NULL, 'n' }, + { "max-processes", 1, NULL, 'm' }, + { "help", 0, NULL, 'h' }, + { "daemonize", 0, NULL, 'd'}, + { "log-file", 1, NULL, 'l'}, + { "nice", 1, NULL, 0}, + { "name", 1, NULL, 'n'}, + { "scheduler-host", 1, NULL, 's' }, + { "env-basedir", 1, NULL, 'b' }, + { "nobody-uid", 1, NULL, 'u'}, + { "cache-limit", 1, NULL, 0}, + { "no-remote", 0, NULL, 0}, + { 0, 0, 0, 0 } + }; + + const int c = getopt_long( argc, argv, "N:n:m:l:s:whvdrb:u:", long_options, &option_index ); + if ( c == -1 ) break; // eoo + + switch ( c ) { + case 0: + { + string optname = long_options[option_index].name; + if ( optname == "nice" ) { + if ( optarg && *optarg ) { + errno = 0; + int tnice = atoi( optarg ); + if ( !errno ) + nice_level = tnice; + } else + usage("Error: --nice requires argument"); + } else if ( optname == "name" ) { + if ( optarg && *optarg ) + d.nodename = optarg; + else + usage("Error: --name requires argument"); + } else if ( optname == "cache-limit" ) { + if ( optarg && *optarg ) { + errno = 0; + int mb = atoi( optarg ); + if ( !errno ) + cache_size_limit = mb * 1024 * 1024; + } + else + usage("Error: --cache-limit requires argument"); + } else if ( optname == "no-remote" ) { + d.noremote = true; + } + + } + break; + case 'd': + detach = true; + break; + case 'N': + if ( optarg && *optarg ) + d.nodename = optarg; + else + usage("Error: -N requires argument"); + break; + case 'l': + if ( optarg && *optarg ) + logfile = optarg; + else + usage( "Error: -l requires argument" ); + break; + case 'v': + if ( debug_level & Warning ) + if ( debug_level & Info ) // for second call + debug_level |= Debug; + else + debug_level |= Info; + else + debug_level |= Warning; + break; + case 'n': + if ( optarg && *optarg ) + d.netname = optarg; + else + usage("Error: -n requires argument"); + break; + case 'm': + if ( optarg && *optarg ) + max_processes = atoi(optarg); + else + usage("Error: -m requires argument"); + break; + case 's': + if ( optarg && *optarg ) + d.schedname = optarg; + else + usage("Error: -s requires hostname argument"); + break; + case 'b': + if ( optarg && *optarg ) + d.envbasedir = optarg; + break; + case 'u': + if ( optarg && *optarg ) + { + struct passwd *pw = getpwnam( optarg ); + if ( !pw ) { + usage( "Error: -u requires a valid username" ); + } else { + d.nobody_uid = pw->pw_uid; + d.nobody_gid = pw->pw_gid; + if (!d.nobody_gid || !d.nobody_uid) { + usage( "Error: -u <username> must not be root"); + } + } + } else + usage( "Error: -u requires a valid username" ); + break; + + default: + usage(); + } + } + + umask(022); + + if ( !logfile.length() && detach) + logfile = "/var/log/iceccd"; + + setup_debug( debug_level, logfile ); + + if ((getuid()!=0)) + d.noremote = true; + + log_info() << "ICECREAM daemon " VERSION " starting up (nice level " + << nice_level << ") " << endl; + + d.determine_system(); + + chdir( "/" ); + + if ( detach ) + if (daemon(0, 0)) { + log_perror("daemon()"); + exit (EXIT_DISTCC_FAILED); + } + + if (dcc_ncpus(&d.num_cpus) == 0) + log_info() << d.num_cpus << " CPU(s) online on this server" << endl; + + if ( max_processes < 0 ) + max_kids = d.num_cpus; + else + max_kids = max_processes; + + log_info() << "allowing up to " << max_kids << " active jobs\n"; + + int ret; + + /* Still create a new process group, even if not detached */ + trace() << "not detaching\n"; + if ((ret = set_new_pgrp()) != 0) + return ret; + + /* Don't catch signals until we've detached or created a process group. */ + dcc_daemon_catch_signals(); + + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { + log_warning() << "signal(SIGPIPE, ignore) failed: " << strerror(errno) << endl; + exit( EXIT_DISTCC_FAILED ); + } + + if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) { + log_warning() << "signal(SIGCHLD) failed: " << strerror(errno) << endl; + exit( EXIT_DISTCC_FAILED ); + } + + /* This is called in the master daemon, whether that is detached or + * not. */ + dcc_master_pid = getpid(); + + ofstream pidFile; + string progName = argv[0]; + progName = progName.substr(progName.rfind('/')+1); + pidFilePath = string(RUNDIR)+string("/")+progName+string(".pid"); + pidFile.open(pidFilePath.c_str()); + pidFile << dcc_master_pid << endl; + pidFile.close(); + + if ( !cleanup_cache( d.envbasedir ) ) + return 1; + + list<string> nl = get_netnames (200); + trace() << "Netnames:" << endl; + for (list<string>::const_iterator it = nl.begin(); it != nl.end(); ++it) + trace() << *it << endl; + + d.listen_fd = setup_listen_fd(); + if ( d.listen_fd == -1 ) // error + return 1; + + return d.working_loop(); +} diff --git a/daemon/ncpus.c b/daemon/ncpus.c new file mode 100644 index 0000000..550d7db --- /dev/null +++ b/daemon/ncpus.c @@ -0,0 +1,163 @@ +/* -*- c-file-style: "java"; indent-tabs-mode: nil; fill-column: 78 -*- + * + * distcc -- A simple distributed compiler system + * + * Copyright (C) 2003 by Martin Pool <mbp@samba.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +/* Thanks to Dimitri PAPADOPOULOS-ORFANOS for researching many of the methods + * in this file. */ + +#include "config.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <sys/stat.h> + +#include "ncpus.h" +#include "exitcode.h" + +/** + * Determine number of processors online. + * + * We will in the future use this to gauge how many concurrent tasks + * should run on this machine. Obviously this is only very rough: the + * correct number needs to take into account disk buffers, IO + * bandwidth, other tasks, etc. +**/ + +#if defined(__hpux__) || defined(__hpux) + +#include <sys/param.h> +#include <sys/pstat.h> + +int dcc_ncpus(int *ncpus) +{ + struct pst_dynamic psd; + if (pstat_getdynamic(&psd, sizeof(psd), 1, 0) != -1) { + *ncpus = psd.psd_proc_cnt; + return 0; + } else { + rs_log_error("pstat_getdynamic failed: %s", strerror(errno)); + *ncpus = 1; + return EXIT_DISTCC_FAILED; + } +} + + +#elif defined(__VOS__) + +#ifdef __GNUC__ +#define $shortmap +#endif + +#include <module_info.h> + +extern void s$get_module_info (char_varying *module_name, void *mip, + short int *code); + +int dcc_ncpus(int *ncpus) +{ +short int code; +module_info mi; +char_varying(66) module_name; + + strcpy_vstr_nstr (&module_name, ""); + mi.version = MODULE_INFO_VERSION_1; + s$get_module_info ((char_varying *)&module_name, (void *)&mi, &code); + if (code != 0) + *ncpus = 1; /* safe guess... */ + else *ncpus = mi.n_user_cpus; + return 0; +} + +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined(__bsdi__) || defined(__DragonFly__) + +/* http://www.FreeBSD.org/cgi/man.cgi?query=sysctl&sektion=3&manpath=FreeBSD+4.6-stable + http://www.openbsd.org/cgi-bin/man.cgi?query=sysctl&sektion=3&manpath=OpenBSD+Current + http://www.tac.eu.org/cgi-bin/man-cgi?sysctl+3+NetBSD-current +*/ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/sysctl.h> + +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#undef HAVE_RS_LOG_ERROR +#else +#define HAVE_RS_LOG_ERROR +#endif + +int dcc_ncpus(int *ncpus) +{ + int mib[2]; + size_t len = sizeof(*ncpus); + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + if (sysctl(mib, 2, ncpus, &len, NULL, 0) == 0) + return 0; + else { +#ifdef have_rs_log_error + rs_log_error("sysctl(CTL_HW:HW_NCPU) failed: %s", + strerror(errno)); +#else + fprintf(stderr,"sysctl(CTL_HW:HW_NCPU) failed: %s", + strerror(errno)); +#endif + *ncpus = 1; + return EXIT_DISTCC_FAILED; + } +} + +#else /* every other system */ + +/* + http://www.opengroup.org/onlinepubs/007904975/functions/sysconf.html + http://docs.sun.com/?p=/doc/816-0213/6m6ne38dd&a=view + http://www.tru64unix.compaq.com/docs/base_doc/DOCUMENTATION/V40G_HTML/MAN/MAN3/0629____.HTM + http://techpubs.sgi.com/library/tpl/cgi-bin/getdoc.cgi?coll=0650&db=man&fname=/usr/share/catman/p_man/cat3c/sysconf.z +*/ + +int dcc_ncpus(int *ncpus) +{ +#if defined(_SC_NPROCESSORS_ONLN) + /* Linux, Solaris, Tru64, UnixWare 7, and Open UNIX 8 */ + *ncpus = sysconf(_SC_NPROCESSORS_ONLN); +#elif defined(_SC_NPROC_ONLN) + /* IRIX */ + *ncpus = sysconf(_SC_NPROC_ONLN); +#else +#warning "Please port this function" + *ncpus = -1; /* unknown */ +#endif + + if (*ncpus == -1) { + *ncpus = 1; + return EXIT_DISTCC_FAILED; + } else if (*ncpus == 0) { + /* if there are no cpus, what are we running on? But it has + * apparently been observed to happen on ARM Linux */ + *ncpus = 1; + } + + return 0; +} +#endif diff --git a/daemon/ncpus.h b/daemon/ncpus.h new file mode 100644 index 0000000..f4570a3 --- /dev/null +++ b/daemon/ncpus.h @@ -0,0 +1,29 @@ +/* + This file is part of Icecream. + + Copyright (C) 2002, 2003 by Martin Pool <mbp@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +int dcc_ncpus(int *); + +#ifdef __cplusplus +} +#endif diff --git a/daemon/serve.cpp b/daemon/serve.cpp new file mode 100644 index 0000000..aaeb0a5 --- /dev/null +++ b/daemon/serve.cpp @@ -0,0 +1,251 @@ +/* + This file is part of Icecream. + + Copyright (c) 2004 Stephan Kulow <coolo@suse.de> + 2002, 2003 by Martin Pool <mbp@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <setjmp.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <signal.h> +#include <cassert> + +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#ifdef HAVE_SYS_SIGNAL_H +# include <sys/signal.h> +#endif /* HAVE_SYS_SIGNAL_H */ +#include <sys/param.h> +#include <unistd.h> + +#include <job.h> +#include <comm.h> + +#include "exitcode.h" +#include "tempfile.h" +#include "workit.h" +#include "logging.h" +#include "serve.h" + +#include <sys/time.h> + +#ifdef __FreeBSD__ +#include <sys/socket.h> +#include <sys/uio.h> +#endif + +#ifndef O_LARGEFILE +#define O_LARGEFILE 0 +#endif + +#ifndef _PATH_TMP +#define _PATH_TMP "/tmp" +#endif + +using namespace std; + +int nice_level = 5; + +static void +error_client( MsgChannel *client, string error ) +{ + if ( IS_PROTOCOL_22( client ) ) + client->send_msg( StatusTextMsg( error ) ); +} + +/** + * Read a request, run the compiler, and send a response. + **/ +int handle_connection( const string &basedir, CompileJob *job, + MsgChannel *client, int &out_fd, + unsigned int mem_limit, uid_t nobody_uid, gid_t nobody_gid ) +{ + int socket[2]; + if ( pipe( socket ) == -1) + return -1; + + flush_debug(); + pid_t pid = fork(); + assert(pid >= 0); + if ( pid > 0) { // parent + close( socket[1] ); + out_fd = socket[0]; + fcntl(out_fd, F_SETFD, FD_CLOEXEC); + return pid; + } + + reset_debug(0); + close( socket[0] ); + out_fd = socket[1]; + + /* internal communication channel, don't inherit to gcc */ + fcntl(out_fd, F_SETFD, FD_CLOEXEC); + + nice( nice_level ); + + Msg *msg = 0; // The current read message + unsigned int job_id = 0; + int obj_fd = -1; // the obj_fd + string obj_file; + + try { + if ( job->environmentVersion().size() ) { + string dirname = basedir + "/target=" + job->targetPlatform() + "/" + job->environmentVersion(); + if ( ::access( string( dirname + "/usr/bin/gcc" ).c_str(), X_OK ) ) { + error_client( client, dirname + "/usr/bin/gcc is not executable" ); + log_error() << "I don't have environment " << job->environmentVersion() << "(" << job->targetPlatform() << ") " << job->jobID() << endl; + throw myexception( EXIT_DISTCC_FAILED ); // the scheduler didn't listen to us! + } + + if ( getuid() == 0 ) { + // without the chdir, the chroot will escape the + // jail right away + if ( chdir( dirname.c_str() ) < 0 ) { + error_client( client, string( "chdir to " ) + dirname + "failed" ); + log_perror("chdir() failed" ); + _exit(145); + } + if ( chroot( dirname.c_str() ) < 0 ) { + error_client( client, string( "chroot " ) + dirname + "failed" ); + log_perror("chroot() failed" ); + _exit(144); + } + if ( setgid( nobody_gid ) < 0 ) { + error_client( client, string( "setgid failed" )); + log_perror("setgid() failed" ); + _exit(143); + } + if ( setuid( nobody_uid ) < 0) { + error_client( client, string( "setuid failed" )); + log_perror("setuid() failed" ); + _exit(142); + } + } + else + if ( chdir( dirname.c_str() ) ) { + log_perror( "chdir" ); + } else { + trace() << "chdir to " << dirname << endl; + } + } + else + chdir( "/" ); + + if ( ::access( _PATH_TMP + 1, W_OK ) ) { + error_client( client, "can't write to " _PATH_TMP ); + log_error() << "can't write into " << _PATH_TMP << " " << strerror( errno ) << endl; + throw myexception( -1 ); + } + + int ret; + unsigned int job_stat[8]; + CompileResultMsg rmsg; + job_id = job->jobID(); + + memset(job_stat, 0, sizeof(job_stat)); + + char tmp_output[PATH_MAX]; + char prefix_output[PATH_MAX]; // I'm too lazy to calculate how many digits 2^64 is :) + sprintf( prefix_output, "icecc-%d", job_id ); + + if ( ( ret = dcc_make_tmpnam(prefix_output, ".o", tmp_output, 1 ) ) == 0 ) { + obj_file = tmp_output; + ret = work_it( *job, job_stat, client, rmsg, obj_file, mem_limit, client->fd, + -1 ); + } + + delete job; + job = 0; + + if ( ret ) { + if ( ret == EXIT_OUT_OF_MEMORY ) { // we catch that as special case + rmsg.was_out_of_memory = true; + } else { + throw myexception( ret ); + } + } + + if ( !client->send_msg( rmsg ) ) { + log_info() << "write of result failed\n"; + throw myexception( EXIT_DISTCC_FAILED ); + } + + struct stat st; + if (!stat(obj_file.c_str(), &st)) + job_stat[JobStatistics::out_uncompressed] = st.st_size; + + /* wake up parent and tell him that compile finished */ + /* if the write failed, well, doesn't matter */ + write( out_fd, job_stat, sizeof( job_stat ) ); + close( out_fd ); + + if ( rmsg.status == 0 ) { + obj_fd = open( obj_file.c_str(), O_RDONLY|O_LARGEFILE ); + if ( obj_fd == -1 ) { + log_error() << "open failed\n"; + error_client( client, "open of object file failed" ); + throw myexception( EXIT_DISTCC_FAILED ); + } + + unsigned char buffer[100000]; + do { + ssize_t bytes = read(obj_fd, buffer, sizeof(buffer)); + if ( bytes < 0 ) + { + if ( errno == EINTR ) + continue; + throw myexception( EXIT_DISTCC_FAILED ); + } + if ( !bytes ) + break; + FileChunkMsg fcmsg( buffer, bytes ); + if ( !client->send_msg( fcmsg ) ) { + log_info() << "write of obj chunk failed " << bytes << endl; + throw myexception( EXIT_DISTCC_FAILED ); + } + } while (1); + } + + throw myexception( rmsg.status ); + + } catch ( myexception e ) + { + if ( client && e.exitcode() == 0 ) + client->send_msg( EndMsg() ); + delete client; + client = 0; + + delete msg; + delete job; + + if ( obj_fd > -1) + close( obj_fd ); + + if ( !obj_file.empty() ) + unlink( obj_file.c_str() ); + + _exit( e.exitcode() ); + } +} diff --git a/daemon/serve.h b/daemon/serve.h new file mode 100644 index 0000000..99adce1 --- /dev/null +++ b/daemon/serve.h @@ -0,0 +1,36 @@ +/* + This file is part of Icecream. + + Copyright (c) 2004 Stephan Kulow <coolo@suse.de> + 2002, 2003 by Martin Pool <mbp@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _SERVE_H +#define _SERVE_H + +#include <string> + +class CompileJob; +class MsgChannel; + +extern int nice_level; + +int handle_connection( const std::string &basedir, CompileJob *job, + MsgChannel *serv, int & out_fd, + unsigned int mem_limit, uid_t nobody_uid, gid_t nobody_gid); + +#endif diff --git a/daemon/workit.cpp b/daemon/workit.cpp new file mode 100644 index 0000000..286af5c --- /dev/null +++ b/daemon/workit.cpp @@ -0,0 +1,513 @@ +/* + This file is part of Icecream. + + Copyright (c) 2004 Stephan Kulow <coolo@suse.de> + 2002, 2003 by Martin Pool <mbp@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "config.h" +#include "workit.h" +#include "tempfile.h" +#include "assert.h" +#include "exitcode.h" +#include "logging.h" +#include <sys/select.h> +#include <algorithm> + +#ifdef __FreeBSD__ +#include <sys/param.h> +#endif + +/* According to earlier standards */ +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/fcntl.h> +#include <sys/wait.h> +#if HAVE_SYS_USER_H && !defined(__DragonFly__) +# include <sys/user.h> +#endif +#include <sys/socket.h> + +#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__APPLE__) +#include <signal.h> +#include <sys/resource.h> +#ifndef RUSAGE_SELF +#define RUSAGE_SELF (0) +#endif +#ifndef RUSAGE_CHILDREN +#define RUSAGE_CHILDREN (-1) +#endif +#endif + +#include <stdio.h> +#include <errno.h> +#include <string> + +#include "comm.h" + +using namespace std; + +// code based on gcc - Copyright (C) 1999, 2000, 2001, 2002 Free Software Foundation, Inc. + +/* Heuristic to set a default for GGC_MIN_EXPAND. */ +static int +ggc_min_expand_heuristic(unsigned int mem_limit) +{ + double min_expand = mem_limit; + + /* The heuristic is a percentage equal to 30% + 70%*(RAM/1GB), yielding + a lower bound of 30% and an upper bound of 100% (when RAM >= 1GB). */ + min_expand /= 1024; + min_expand *= 70; + min_expand = std::min (min_expand, 70.); + min_expand += 30; + + return int( min_expand ); +} + +/* Heuristic to set a default for GGC_MIN_HEAPSIZE. */ +static unsigned int +ggc_min_heapsize_heuristic(unsigned int mem_limit) +{ + /* The heuristic is RAM/8, with a lower bound of 4M and an upper + bound of 128M (when RAM >= 1GB). */ + mem_limit /= 8; + mem_limit = std::max (mem_limit, 4U); + mem_limit = std::min (mem_limit, 128U); + + return mem_limit * 1024; +} + + +static int death_pipe[2]; + +extern "C" { + +static void theSigCHLDHandler( int ) +{ + char foo = 0; + write(death_pipe[1], &foo, 1); +} + +} + +static void +error_client( MsgChannel *client, string error ) +{ + if ( IS_PROTOCOL_23( client ) ) + client->send_msg( StatusTextMsg( error ) ); +} + +/* + * This is all happening in a forked child. + * That means that we can block and be lazy about closing fds + * (in the error cases which exit quickly). + */ + +int work_it( CompileJob &j, unsigned int job_stat[], MsgChannel* client, + CompileResultMsg& rmsg, const string &outfilename, + unsigned long int mem_limit, int client_fd, int /*job_in_fd*/ ) +{ + rmsg.out.erase(rmsg.out.begin(), rmsg.out.end()); + rmsg.out.erase(rmsg.out.begin(), rmsg.out.end()); + + std::list<string> list = j.remoteFlags(); + appendList( list, j.restFlags() ); + + int sock_err[2]; + int sock_out[2]; + int sock_in[2]; + int main_sock[2]; + char buffer[4096]; + + if ( pipe( sock_err ) ) + return EXIT_DISTCC_FAILED; + if ( pipe( sock_out ) ) + return EXIT_DISTCC_FAILED; + if ( pipe( main_sock ) ) + return EXIT_DISTCC_FAILED; + if ( pipe( death_pipe ) ) + return EXIT_DISTCC_FAILED; + + // We use a socket pair instead of a pipe to get a "slightly" bigger + // output buffer. This saves context switches and latencies. + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock_in) < 0) + return EXIT_DISTCC_FAILED; + int maxsize = 2*1024*2024; +#ifdef SO_SNDBUFFORCE + if (setsockopt(sock_in[1], SOL_SOCKET, SO_SNDBUFFORCE, &maxsize, sizeof(maxsize)) < 0) +#endif + { + setsockopt(sock_in[1], SOL_SOCKET, SO_SNDBUF, &maxsize, sizeof(maxsize)); + } + + if ( fcntl( sock_in[1], F_SETFL, O_NONBLOCK ) ) + return EXIT_DISTCC_FAILED; + + /* Testing */ + struct sigaction act; + sigemptyset( &act.sa_mask ); + + act.sa_handler = SIG_IGN; + act.sa_flags = 0; + sigaction( SIGPIPE, &act, 0L ); + + act.sa_handler = theSigCHLDHandler; + act.sa_flags = SA_NOCLDSTOP; + sigaction( SIGCHLD, &act, 0 ); + + sigaddset( &act.sa_mask, SIGCHLD ); + // Make sure we don't block this signal. gdb tends to do that :-( + sigprocmask( SIG_UNBLOCK, &act.sa_mask, 0 ); + + flush_debug(); + pid_t pid = fork(); + if ( pid == -1 ) { + return EXIT_OUT_OF_MEMORY; + } else if ( pid == 0 ) { + + setenv( "PATH", "usr/bin", 1 ); + // Safety check + if (getuid() == 0 || getgid() == 0) { + error_client( client, "UID is 0 - aborting." ); + _exit(142); + } + + +#ifdef RLIMIT_AS + struct rlimit rlim; + if ( getrlimit( RLIMIT_AS, &rlim ) ) { + error_client( client, "getrlimit failed." ); + log_perror( "getrlimit" ); + } + + rlim.rlim_cur = mem_limit*1024*1024; + rlim.rlim_max = mem_limit*1024*1024; + if ( setrlimit( RLIMIT_AS, &rlim ) ) { + error_client( client, "setrlimit failed." ); + log_perror( "setrlimit" ); + } +#endif + + int argc = list.size(); + argc++; // the program + argc += 6; // -x c - -o file.o -fpreprocessed + argc += 4; // gpc parameters + argc += 1; // -pipe + char **argv = new char*[argc + 1]; + int i = 0; + if (j.language() == CompileJob::Lang_C) + argv[i++] = strdup( "usr/bin/gcc" ); + else if (j.language() == CompileJob::Lang_CXX) + argv[i++] = strdup( "usr/bin/g++" ); + else + assert(0); + + bool hasPipe = false; + for ( std::list<string>::const_iterator it = list.begin(); + it != list.end(); ++it) { + if(*it == "-pipe") + hasPipe = true; + argv[i++] = strdup( it->c_str() ); + } + argv[i++] = strdup("-fpreprocessed"); + if(!hasPipe) + argv[i++] = strdup("-pipe"); + argv[i++] = strdup("-x"); + argv[i++] = strdup((j.language() == CompileJob::Lang_CXX) ? "c++" : "c"); + argv[i++] = strdup( "-" ); + argv[i++] = strdup( "-o" ); + argv[i++] = strdup(outfilename.c_str()); + argv[i++] = strdup( "--param" ); + sprintf( buffer, "ggc-min-expand=%d", ggc_min_expand_heuristic( mem_limit ) ); + argv[i++] = strdup( buffer ); + argv[i++] = strdup( "--param" ); + sprintf( buffer, "ggc-min-heapsize=%d", ggc_min_heapsize_heuristic( mem_limit ) ); + argv[i++] = strdup( buffer ); + // before you add new args, check above for argc + argv[i] = 0; + assert(i <= argc); + + close_debug(); + + close( sock_out[0] ); + dup2 (sock_out[1], STDOUT_FILENO ); + close(sock_out[1]); + + close(sock_err[0]); + dup2( sock_err[1], STDERR_FILENO ); + close(sock_err[1]); + + close( sock_in[1] ); + dup2( sock_in[0], STDIN_FILENO); + close (sock_in[0]); + + close( main_sock[0] ); + fcntl(main_sock[1], F_SETFD, FD_CLOEXEC); + + close( death_pipe[0] ); + close( death_pipe[1] ); + +#ifdef ICECC_DEBUG + for(int f = STDERR_FILENO+1; f < 4096; ++f) { + long flags; + assert((flags = fcntl(f, F_GETFD, 0)) < 0 || (flags & FD_CLOEXEC)); + } +#endif + + execv( argv[0], const_cast<char *const*>( argv ) ); // no return + perror( "ICECC: execv" ); + + char resultByte = 1; + write(main_sock[1], &resultByte, 1); + _exit(-1); + } + close( sock_in[0] ); + close( sock_out[1] ); + close( sock_err[1] ); + + // idea borrowed from kprocess. + // check whether the compiler could be run at all. + close( main_sock[1] ); + for(;;) + { + char resultByte; + ssize_t n = ::read(main_sock[0], &resultByte, 1); + if (n == -1 && errno == EINTR) + continue; // Ignore + + if (n == 1) + { + rmsg.status = resultByte; + + error_client( client, "compiler did not start" ); + return EXIT_COMPILER_MISSING; + } + break; // != EINTR + } + close( main_sock[0] ); + + struct timeval starttv; + gettimeofday(&starttv, 0 ); + + int return_value = 0; + // Got EOF for preprocessed input. stdout send may be still pending. + bool input_complete = false; + // Pending data to send to stdin + FileChunkMsg *fcmsg = 0; + size_t off = 0; + + log_block parent_wait("parent, waiting"); + + for(;;) + { + if ( client_fd >= 0 && !fcmsg ) { + if (Msg *msg = client->get_msg(0)) { + if (input_complete) { + rmsg.err.append( "client cancelled\n" ); + return_value = EXIT_CLIENT_KILLED; + client_fd = -1; + kill(pid, SIGTERM); + delete fcmsg; + fcmsg = 0; + delete msg; + } else { + if ( msg->type == M_END ) { + input_complete = true; + if (!fcmsg) { + close( sock_in[1] ); + sock_in[1] = -1; + } + delete msg; + } else if ( msg->type == M_FILE_CHUNK ) { + fcmsg = static_cast<FileChunkMsg*>( msg ); + off = 0; + + job_stat[JobStatistics::in_uncompressed] += fcmsg->len; + job_stat[JobStatistics::in_compressed] += fcmsg->compressed; + } else { + log_error() << "protocol error while reading preprocessed file\n"; + return_value = EXIT_IO_ERROR; + client_fd = -1; + kill(pid, SIGTERM); + delete fcmsg; + fcmsg = 0; + delete msg; + } + } + } else if (client->at_eof()) { + log_error() << "unexpected EOF while reading preprocessed file\n"; + return_value = EXIT_IO_ERROR; + client_fd = -1; + kill(pid, SIGTERM); + delete fcmsg; + fcmsg = 0; + } + } + + fd_set rfds; + FD_ZERO( &rfds ); + if (sock_out[0] >= 0) + FD_SET( sock_out[0], &rfds ); + if (sock_err[0] >= 0) + FD_SET( sock_err[0], &rfds ); + int max_fd = std::max( sock_out[0], sock_err[0] ); + + if ( client_fd >= 0 && !fcmsg ) { + FD_SET( client_fd, &rfds ); + if ( client_fd > max_fd ) + max_fd = client_fd; + // Note that we don't actually query the status of this fd - + // we poll it in every iteration. + } + + FD_SET( death_pipe[0], &rfds ); + if ( death_pipe[0] > max_fd ) + max_fd = death_pipe[0]; + + fd_set wfds, *wfdsp = 0; + FD_ZERO( &wfds ); + if (fcmsg) { + FD_SET( sock_in[1], &wfds ); + wfdsp = &wfds; + if ( sock_in[1] > max_fd ) + max_fd = sock_in[1]; + } + + struct timeval tv, *tvp = 0; + if (!input_complete) { + tv.tv_sec = 60; + tv.tv_usec = 0; + tvp = &tv; + } + + switch( select( max_fd+1, &rfds, wfdsp, 0, tvp ) ) + { + case 0: + if (!input_complete) { + log_error() << "timeout while reading preprocessed file\n"; + kill(pid, SIGTERM); // Won't need it any more ... + return_value = EXIT_IO_ERROR; + client_fd = -1; + input_complete = true; + delete fcmsg; + fcmsg = 0; + continue; + } + // this should never happen + assert( false ); + return EXIT_DISTCC_FAILED; + case -1: + if (errno == EINTR) + continue; + // this should never happen + assert( false ); + return EXIT_DISTCC_FAILED; + default: + if ( fcmsg && FD_ISSET(sock_in[1], &wfds) ) { + ssize_t bytes = write( sock_in[1], fcmsg->buffer + off, fcmsg->len - off ); + if ( bytes < 0 ) { + if (errno == EINTR) + continue; + kill(pid, SIGTERM); // Most likely crashed anyway ... + return_value = EXIT_COMPILER_CRASHED; + continue; + } + + // The fd is -1 anyway + //write(job_in_fd, fcmsg->buffer + off, bytes); + + off += bytes; + + if (off == fcmsg->len) { + delete fcmsg; + fcmsg = 0; + if (input_complete) { + close( sock_in[1] ); + sock_in[1] = -1; + } + } + } + + if ( sock_out[0] >= 0 && FD_ISSET(sock_out[0], &rfds) ) { + ssize_t bytes = read( sock_out[0], buffer, sizeof(buffer)-1 ); + if ( bytes > 0 ) { + buffer[bytes] = 0; + rmsg.out.append( buffer ); + } + else if (bytes == 0) { + close(sock_out[0]); + sock_out[0] = -1; + } + } + if ( sock_err[0] >= 0 && FD_ISSET(sock_err[0], &rfds) ) { + ssize_t bytes = read( sock_err[0], buffer, sizeof(buffer)-1 ); + if ( bytes > 0 ) { + buffer[bytes] = 0; + rmsg.err.append( buffer ); + } + else if (bytes == 0) { + close(sock_err[0]); + sock_err[0] = -1; + } + } + + if ( FD_ISSET(death_pipe[0], &rfds) ) { + // Note that we have already read any remaining stdout/stderr: + // the sigpipe is delivered after everything was written, + // and the notification is multiplexed into the select above. + + struct rusage ru; + int status; + if (wait4(pid, &status, 0, &ru) != pid) { + // this should never happen + assert( false ); + return EXIT_DISTCC_FAILED; + } + + if ( !WIFEXITED(status) || WEXITSTATUS(status) ) { + unsigned long int mem_used = ( ru.ru_minflt + ru.ru_majflt ) * getpagesize() / 1024; + rmsg.status = EXIT_OUT_OF_MEMORY; + + if ( mem_used * 100 > 85 * mem_limit * 1024 || + rmsg.err.find( "memory exhausted" ) != string::npos ) + { + // the relation between ulimit and memory used is pretty thin ;( + return EXIT_OUT_OF_MEMORY; + } + } + + if ( WIFEXITED(status) ) { + struct timeval endtv; + gettimeofday(&endtv, 0 ); + rmsg.status = WEXITSTATUS(status); + job_stat[JobStatistics::exit_code] = WEXITSTATUS(status); + job_stat[JobStatistics::real_msec] = (endtv.tv_sec - starttv.tv_sec) * 1000 + + (long(endtv.tv_usec) - long(starttv.tv_usec)) / 1000; + job_stat[JobStatistics::user_msec] = ru.ru_utime.tv_sec * 1000 + + ru.ru_utime.tv_usec / 1000; + job_stat[JobStatistics::sys_msec] = ru.ru_stime.tv_sec * 1000 + + ru.ru_stime.tv_usec / 1000; + job_stat[JobStatistics::sys_pfaults] = ru.ru_majflt + ru.ru_nswap + ru.ru_minflt; + } + + return return_value; + } + } + } +} diff --git a/daemon/workit.h b/daemon/workit.h new file mode 100644 index 0000000..3da595c --- /dev/null +++ b/daemon/workit.h @@ -0,0 +1,51 @@ +/* + This file is part of Icecream. + + Copyright (c) 2004 Stephan Kulow <coolo@suse.de> + 2002, 2003 by Martin Pool <mbp@samba.org> + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _WORKIT_H +#define _WORKIT_H + +#include <job.h> +#include <sys/types.h> +#include <string> + +#include <exception> + +class MsgChannel; +class CompileResultMsg; + +// No icecream ;( +class myexception : public std::exception +{ + int code; +public: + myexception( int _exitcode ) : exception(), code( _exitcode ) {} + int exitcode() const { return code; } +}; + +namespace JobStatistics { + enum job_stat_fields { in_compressed, in_uncompressed, out_uncompressed, exit_code, + real_msec, user_msec, sys_msec, sys_pfaults }; +} + +extern int work_it( CompileJob &j, unsigned int job_stats[], MsgChannel* client, + CompileResultMsg& msg, const std::string &outfilename, + unsigned long int mem_limit, int client_fd, int job_in_fd ); + +#endif |