summaryrefslogtreecommitdiff
path: root/daemon/workit.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'daemon/workit.cpp')
-rw-r--r--daemon/workit.cpp513
1 files changed, 513 insertions, 0 deletions
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;
+ }
+ }
+ }
+}