summaryrefslogtreecommitdiff
path: root/daemon
diff options
context:
space:
mode:
Diffstat (limited to 'daemon')
-rw-r--r--daemon/Makefile.am8
-rw-r--r--daemon/Makefile.in554
-rw-r--r--daemon/environment.cpp436
-rw-r--r--daemon/environment.h43
-rw-r--r--daemon/load.cpp373
-rw-r--r--daemon/load.h29
-rw-r--r--daemon/main.cpp1681
-rw-r--r--daemon/ncpus.c163
-rw-r--r--daemon/ncpus.h29
-rw-r--r--daemon/serve.cpp251
-rw-r--r--daemon/serve.h36
-rw-r--r--daemon/workit.cpp513
-rw-r--r--daemon/workit.h51
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 &current_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