1 /* -*- c-file-style: "java"; indent-tabs-mode: nil; tab-width: 4; fill-column: 78 -*-
2  *
3  * distcc -- A simple distributed compiler system
4  *
5  * Copyright (C) 2002, 2003, 2004 by Martin Pool
6  * Copyright 2007 Google Inc.
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
21  * USA.
22  */
23 
24 /*
25  * Send a compilation request to a remote server.
26  */
27 
28 
29 #include <config.h>
30 
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <string.h>
35 #include <fcntl.h>
36 #include <errno.h>
37 
38 #include <sys/types.h>
39 #include <sys/time.h>
40 
41 #include "distcc.h"
42 #include "trace.h"
43 #include "rpc.h"
44 #include "exitcode.h"
45 #include "util.h"
46 #include "clinet.h"
47 #include "hosts.h"
48 #include "exec.h"
49 #include "lock.h"
50 #include "compile.h"
51 #include "bulk.h"
52 #ifdef HAVE_GSSAPI
53 #include "auth.h"
54 
55 /* Global security context in case confidentiality/integrity */
56 /* type services are needed in the future. */
57 extern gss_ctx_id_t distcc_ctx_handle;
58 #endif
59 
60 /*
61  * TODO: If cpp finishes early and fails then perhaps break out of
62  * trying to connect.
63  *
64  * TODO: If we abort, perhaps kill the SSH child rather than closing
65  * the socket.  Closing while a lot of stuff has been written through
66  * might make us block until the other side reads all the data.
67  */
68 
69 /**
70  * Open a connection using either a TCP socket or SSH.  Return input
71  * and output file descriptors (which may or may not be different.)
72  **/
dcc_remote_connect(struct dcc_hostdef * host,int * to_net_fd,int * from_net_fd,pid_t * ssh_pid)73 static int dcc_remote_connect(struct dcc_hostdef *host,
74                               int *to_net_fd,
75                               int *from_net_fd,
76                               pid_t *ssh_pid)
77 {
78     int ret;
79 
80     if (host->mode == DCC_MODE_TCP) {
81         *ssh_pid = 0;
82         if ((ret = dcc_connect_by_name(host->hostname, host->port,
83                                        to_net_fd)) != 0)
84             return ret;
85         *from_net_fd = *to_net_fd;
86         return 0;
87     } else if (host->mode == DCC_MODE_SSH) {
88         if ((ret = dcc_ssh_connect(NULL, host->user, host->hostname,
89                                    host->ssh_command,
90                                    from_net_fd, to_net_fd,
91                                    ssh_pid)))
92             return ret;
93         return 0;
94     } else {
95         rs_log_crit("impossible host mode");
96         return EXIT_DISTCC_FAILED;
97     }
98 }
99 
100 
dcc_wait_for_cpp(pid_t cpp_pid,int * status,const char * input_fname)101 static int dcc_wait_for_cpp(pid_t cpp_pid,
102                             int *status,
103                             const char *input_fname)
104 {
105     int ret;
106 
107     if (cpp_pid) {
108         dcc_note_state(DCC_PHASE_CPP, NULL, NULL, DCC_LOCAL);
109         /* Wait for cpp to finish (if not already done), check the
110          * result, then send the .i file */
111 
112         if ((ret = dcc_collect_child("cpp", cpp_pid, status, timeout_null_fd)))
113             return ret;
114 
115         /* Although cpp failed, there is no need to try running the command
116          * locally, because we'd presumably get the same result.  Therefore
117          * critique the command and log a message and return an indication
118          * that compilation is complete. */
119         if (dcc_critique_status(*status, "cpp", input_fname, dcc_hostdef_local, 0))
120             return 0;
121     }
122     return 0;
123 }
124 
125 
126 /* Send a request across to the already-open server.
127  *
128  * CPP_PID is the PID of the preprocessor running in the background.
129  * We wait for it to complete before reading its output.
130  */
131 static int
dcc_send_header(int net_fd,char ** argv,struct dcc_hostdef * host)132 dcc_send_header(int net_fd,
133                 char **argv,
134                 struct dcc_hostdef *host)
135 {
136     int ret;
137 
138     tcp_cork_sock(net_fd, 1);
139 
140     if ((ret = dcc_x_req_header(net_fd, host->protover)))
141         return ret;
142     if (host->cpp_where == DCC_CPP_ON_SERVER) {
143         if ((ret = dcc_x_cwd(net_fd)))
144             return ret;
145     }
146     if ((ret = dcc_x_argv(net_fd, "ARGC", "ARGV", argv)))
147         return ret;
148 
149     return 0;
150 }
151 
152 
153 /**
154  * Pass a compilation across the network.
155  *
156  * When this function is called, the preprocessor has already been
157  * started in the background.  It may have already completed, or it
158  * may still be running.  The goal is that preprocessing will overlap
159  * with setting up the network connection, which may take some time
160  * but little CPU.
161  *
162  * If this function fails, compilation will be retried on the local
163  * machine.
164  *
165  * @param argv Compiler command to run.
166  *
167  * @param cpp_fname Filename of preprocessed source.  May not be complete yet,
168  * depending on @p cpp_pid.
169  *
170  * @param files If we are doing preprocessing on the server, the names of
171  * all the files needed; otherwise, NULL.
172  *
173  * @param output_fname File that the object code should be delivered to.
174  *
175  * @param cpp_pid If nonzero, the pid of the preprocessor.  Must be
176  * allowed to complete before we send the input file.
177  *
178  * @param local_cpu_lock_fd If != -1, file descriptor for the lock file.
179  * Should be != -1 iff (host->cpp_where != DCC_CPP_ON_SERVER).
180  * If != -1, the lock must be held on entry to this function,
181  * and THIS FUNCTION WILL RELEASE THE LOCK.
182  *
183  * @param host Definition of host to send this job to.
184  *
185  * @param status on return contains the wait-status of the remote
186  * compiler.
187  *
188  * Returns 0 on success, otherwise error.  Returning nonzero does not
189  * necessarily imply the remote compiler itself succeeded, only that
190  * there were no communications problems.
191  *
192  * TODO: consider refactoring this (perhaps as two separate subroutines?)
193  * to avoid the need for releasing the lock as a side effect of this call.
194  */
dcc_compile_remote(char ** argv,char * input_fname,char * cpp_fname,char ** files,char * output_fname,char * deps_fname,char * server_stderr_fname,pid_t cpp_pid,int local_cpu_lock_fd,struct dcc_hostdef * host,int * status)195 int dcc_compile_remote(char **argv,
196                        char *input_fname,
197                        char *cpp_fname,
198                        char **files,
199                        char *output_fname,
200                        char *deps_fname,
201                        char *server_stderr_fname,
202                        pid_t cpp_pid,
203                        int local_cpu_lock_fd,
204                        struct dcc_hostdef *host,
205                        int *status)
206 {
207     int to_net_fd = -1, from_net_fd = -1;
208     int ret;
209     pid_t ssh_pid = 0;
210     int ssh_status;
211     off_t doti_size;
212     struct timeval before, after;
213     unsigned int n_files;
214 
215     if (gettimeofday(&before, NULL))
216         rs_log_warning("gettimeofday failed");
217 
218     dcc_note_execution(host, argv);
219     dcc_note_state(DCC_PHASE_CONNECT, input_fname, host->hostname, DCC_REMOTE);
220 
221     /* For ssh support, we need to allow for separate fds writing to and
222      * reading from the network, because our connection to the ssh client may
223      * be over pipes, which are one-way connections. */
224 
225     *status = 0;
226     if ((ret = dcc_remote_connect(host, &to_net_fd, &from_net_fd, &ssh_pid)))
227         goto out;
228 
229 #ifdef HAVE_GSSAPI
230     /* Perform requested security. */
231     if(host->authenticate) {
232         rs_log_info("Performing authentication.");
233 
234         if ((ret = dcc_gssapi_perform_requested_security(host, to_net_fd, from_net_fd)) != 0) {
235             rs_log_crit("Failed to perform authentication.");
236             goto out;
237         }
238 
239         /* Context deleted here as we no longer need it.  However, we have it available */
240         /* in case we want to use confidentiality/integrity type services in the future. */
241         dcc_gssapi_delete_ctx(&distcc_ctx_handle);
242     } else {
243         rs_log_info("No authentication requested.");
244     }
245 #endif
246 
247     dcc_note_state(DCC_PHASE_SEND, NULL, NULL, DCC_REMOTE);
248 
249     if (host->cpp_where == DCC_CPP_ON_SERVER) {
250         if ((ret = dcc_send_header(to_net_fd, argv, host))) {
251           goto out;
252         }
253 
254         n_files = dcc_argv_len(files);
255         if ((ret = dcc_x_many_files(to_net_fd, n_files, files))) {
256             goto out;
257         }
258     } else {
259         /* This waits for cpp and puts its status in *status.  If cpp failed,
260          * then the connection will have been dropped and we need not bother
261          * trying to get any response from the server. */
262 
263         if ((ret = dcc_send_header(to_net_fd, argv, host)))
264             goto out;
265 
266         if ((ret = dcc_wait_for_cpp(cpp_pid, status, input_fname)))
267             goto out;
268 
269         /* We are done with local preprocessing.  Unlock to allow someone
270          * else to start preprocessing. */
271         if (local_cpu_lock_fd != -1) {
272             dcc_unlock(local_cpu_lock_fd);
273             local_cpu_lock_fd = -1;
274         }
275 
276         if (*status != 0)
277             goto out;
278 
279         if ((ret = dcc_x_file(to_net_fd, cpp_fname, "DOTI", host->compr,
280                               &doti_size)))
281             goto out;
282     }
283 
284     rs_trace("client finished sending request to server");
285     tcp_cork_sock(to_net_fd, 0);
286     /* but it might not have been read in by the server yet; there's
287      * 100kB or more of buffers in the two kernels. */
288 
289     /* OK, now all of the source has at least made it into the
290      * client's TCP transmission queue, sometime soon the server will
291      * start compiling it.  */
292     dcc_note_state(DCC_PHASE_COMPILE, NULL, host->hostname, DCC_REMOTE);
293 
294     /* If cpp failed, just abandon the connection, without trying to
295      * receive results. */
296     if (ret == 0 && *status == 0) {
297         ret = dcc_retrieve_results(from_net_fd, status, output_fname,
298                                    deps_fname, server_stderr_fname, host);
299     }
300 
301     if (gettimeofday(&after, NULL)) {
302         rs_log_warning("gettimeofday failed");
303     } else if (host->cpp_where == DCC_CPP_ON_CLIENT) {
304         double secs, rate;
305 
306         dcc_calc_rate(doti_size, &before, &after, &secs, &rate);
307         rs_log(RS_LOG_INFO|RS_LOG_NONAME,
308                "%lu bytes from %s compiled on %s in %.4fs, rate %.0fkB/s",
309                (unsigned long) doti_size, input_fname, host->hostname,
310                secs, rate);
311     }
312 
313   out:
314     if (local_cpu_lock_fd != -1) {
315         dcc_unlock(local_cpu_lock_fd);
316         local_cpu_lock_fd = -1; /* Not really needed; just for consistency. */
317     }
318 
319     /* Close socket so that the server can terminate, rather than
320      * making it wait until we've finished our work. */
321     if (to_net_fd != from_net_fd) {
322         if (to_net_fd != -1)
323             dcc_close(to_net_fd);
324     }
325     if (from_net_fd != -1)
326         dcc_close(from_net_fd);
327 
328     /* Collect the SSH child.  Strictly this is unnecessary; it might slow the
329      * client down a little when things could otherwise be proceeding in the
330      * background.  But it helps make sure that we don't assume we succeeded
331      * when something possibly went wrong, and it allows us to account for the
332      * cost of the ssh child. */
333     if (ssh_pid) {
334         dcc_collect_child("ssh", ssh_pid, &ssh_status, timeout_null_fd); /* ignore failure */
335     }
336 
337     return ret;
338 }
339