1 // This file is part of BOINC.
2 // http://boinc.berkeley.edu
3 // Copyright (C) 2008 University of California
4 //
5 // BOINC is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU Lesser General Public License
7 // as published by the Free Software Foundation,
8 // either version 3 of the License, or (at your option) any later version.
9 //
10 // BOINC is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 // See the GNU Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public License
16 // along with BOINC. If not, see <http://www.gnu.org/licenses/>.
17
18 // Handle a scheduling server RPC
19
20 #include "config.h"
21 #ifdef _USING_FCGI_
22 #include "boinc_fcgi.h"
23 #else
24 #include <cstdio>
25 #endif
26 #include <cassert>
27 #include <cstdlib>
28 #include <vector>
29 #include <string>
30 #include <cstring>
31 #include <ctime>
32 #include <cmath>
33
34 #include <unistd.h>
35 #include <sys/wait.h>
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <errno.h>
39 #include <sys/stat.h>
40
41 #include "backend_lib.h"
42 #include "boinc_db.h"
43 #include "error_numbers.h"
44 #include "filesys.h"
45 #include "parse.h"
46 #include "str_replace.h"
47 #include "str_util.h"
48 #include "util.h"
49
50 #include "sched_vda.h"
51
52 #include "credit.h"
53 #include "sched_files.h"
54 #include "sched_main.h"
55 #include "sched_types.h"
56 #include "sched_util.h"
57 #include "handle_request.h"
58 #include "sched_msgs.h"
59 #include "sched_resend.h"
60 #include "sched_send.h"
61 #include "sched_config.h"
62 #include "sched_locality.h"
63 #include "sched_result.h"
64 #include "sched_customize.h"
65 #include "time_stats_log.h"
66
67 // are the 2 hosts obviously different computers?
68 //
obviously_different(HOST & h1,HOST & h2)69 static bool obviously_different(HOST& h1, HOST& h2) {
70 if (h1.p_ncpus != h2.p_ncpus) return true;
71 if (strcmp(h1.p_vendor, h2.p_vendor)) return true;
72 if (strcmp(h1.p_model, h2.p_model)) return true;
73 if (strcmp(h1.os_name, h2.os_name)) return true;
74 if (strcmp(h1.os_version, h2.os_version)) return true;
75 return false;
76 }
77
78 // find the user's most recently-created host with given various characteristics
79 //
find_host_by_other(DB_USER & user,HOST req_host,DB_HOST & host)80 static bool find_host_by_other(DB_USER& user, HOST req_host, DB_HOST& host) {
81 char buf[2048];
82 char dn[512], ip[512], os[512], pm[512];
83
84 // don't dig through hosts of these users
85 // prevents flooding the DB with slow queries from users with many hosts
86 //
87 for (unsigned int i=0; i < config.dont_search_host_for_userid.size(); i++) {
88 if (user.id == config.dont_search_host_for_userid[i]) {
89 return false;
90 }
91 }
92
93 // Only check if all the fields are populated
94 //
95 if (strlen(req_host.domain_name) && strlen(req_host.last_ip_addr) && strlen(req_host.os_name) && strlen(req_host.p_model)) {
96 safe_strcpy(dn, req_host.domain_name);
97 escape_string(dn, sizeof(dn));
98 safe_strcpy(ip, req_host.last_ip_addr);
99 escape_string(ip, sizeof(ip));
100 safe_strcpy(os, req_host.os_name);
101 escape_string(os, sizeof(os));
102 safe_strcpy(pm, req_host.p_model);
103 escape_string(pm, sizeof(pm));
104
105 sprintf(buf,
106 "where userid=%lu and id>%lu and domain_name='%s' and last_ip_addr = '%s' and os_name = '%s' and p_model = '%s'"
107 " and m_nbytes = %lf order by id desc", user.id, req_host.id, dn, ip, os, pm, req_host.m_nbytes
108 );
109 if (!host.enumerate(buf)) {
110 host.end_enumerate();
111 return true;
112 }
113 }
114 return false;
115 }
116
send_error_message(const char * msg,int delay)117 static void send_error_message(const char* msg, int delay) {
118 g_reply->insert_message(msg, "low");
119 g_reply->set_delay(delay);
120 g_reply->nucleus_only = true;
121 }
122
123 // Try to lock a file with name based on host ID,
124 // to prevent 2 schedulers from running at same time for same host.
125 // Return:
126 // 0 if successful
127 // In this case store file descriptor in reply struct so we can unlock later
128 // In other cases store -1 in reply struct
129 // PID (>0) if another process has lock
130 // -1 if error (e.g. can't create file)
131 //
lock_sched()132 int lock_sched() {
133 char filename[256];
134 char pid_string[16];
135 int fd, pid, count;
136
137 g_reply->lockfile_fd=-1;
138
139 sprintf(filename, "%s/CGI_%07lu",
140 config.sched_lockfile_dir, g_reply->host.id
141 );
142
143 fd = open(filename, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
144 if (fd < 0) return -1;
145
146 // if we can't get an advisory write lock on the file,
147 // return the PID of the process that DOES hold the lock.
148 // (or -1 if failure)
149 //
150 pid = mylockf(fd);
151 if (pid) {
152 close(fd);
153 return pid;
154 }
155
156 // write PID into the CGI_<HOSTID> file and flush to disk
157 //
158 count = sprintf(pid_string, "%d\n", getpid());
159 ssize_t n = write(fd, pid_string, count);
160 if (n < 0) {
161 close(fd);
162 return -1;
163 }
164 fsync(fd);
165
166 g_reply->lockfile_fd = fd;
167 return 0;
168 }
169
170 // unlock and delete per-host lockfile
171 //
unlock_sched()172 void unlock_sched() {
173 char filename[256];
174
175 if (g_reply->lockfile_fd < 0) return;
176 sprintf(filename, "%s/CGI_%07lu", config.sched_lockfile_dir, g_reply->host.id);
177 unlink(filename);
178 close(g_reply->lockfile_fd);
179 }
180
181
182 // find the user's most recently-created host with given host CPID
183 //
find_host_by_cpid(DB_USER & user,char * host_cpid,DB_HOST & host)184 static bool find_host_by_cpid(DB_USER& user, char* host_cpid, DB_HOST& host) {
185 char buf[1024], buf2[256];
186 sprintf(buf, "%s%s", host_cpid, user.email_addr);
187 md5_block((const unsigned char*)buf, strlen(buf), buf2);
188
189 sprintf(buf,
190 "where userid=%lu and host_cpid='%s' order by id desc", user.id, buf2
191 );
192 if (!host.enumerate(buf)) {
193 host.end_enumerate();
194 return true;
195 }
196 return false;
197 }
198
199 // Called when there's evidence that the host has detached.
200 // Mark in-progress results for the given host
201 // as server state OVER, outcome CLIENT_DETACHED.
202 // This serves two purposes:
203 // 1) make sure we don't resend these results to the host
204 // (they may be the reason the user detached)
205 // 2) trigger the generation of new results for these WUs
206 //
mark_results_over(DB_HOST & host)207 static void mark_results_over(DB_HOST& host) {
208 char buf[256], buf2[256];
209 DB_RESULT result;
210 sprintf(buf, "where hostid=%lu and server_state=%d",
211 host.id,
212 RESULT_SERVER_STATE_IN_PROGRESS
213 );
214 while (!result.enumerate(buf)) {
215 sprintf(buf2,
216 "server_state=%d, outcome=%d, received_time = %ld",
217 RESULT_SERVER_STATE_OVER,
218 RESULT_OUTCOME_CLIENT_DETACHED,
219 time(0)
220 );
221 result.update_field(buf2);
222
223 // and trigger WU transition
224 //
225 DB_WORKUNIT wu;
226 wu.id = result.workunitid;
227 sprintf(buf2, "transition_time=%d", (int)time(0));
228 wu.update_field(buf2);
229
230 log_messages.printf(MSG_CRITICAL,
231 "[HOST#%lu] [RESULT#%lu] [WU#%lu] changed CPID: marking in-progress result %s as client error!\n",
232 host.id, result.id, result.workunitid, result.name
233 );
234 }
235 }
236
237 // Based on the info in the request message,
238 // look up the host and its user, and make sure the authenticator matches.
239 // Some special cases:
240 // 1) If no host ID is supplied, or if RPC seqno mismatch,
241 // create a new host record
242 // 2) If the host record specified by g_request->hostid is a "zombie"
243 // (i.e. it was merged with another host via the web site)
244 // then follow links to find the proper host
245 //
246 // POSTCONDITION:
247 // If this function returns zero, then:
248 // - reply.host contains a valid host record (possibly new)
249 // - reply.user contains a valid user record
250 // - if user belongs to a team, reply.team contains team record
251 //
authenticate_user()252 int authenticate_user() {
253 int retval;
254 char buf[1024];
255 DB_HOST host;
256 DB_USER user;
257 DB_TEAM team;
258
259 if (g_request->hostid) {
260 retval = host.lookup_id(g_request->hostid);
261 while (!retval && host.userid==0) {
262 // if host record is zombie, follow link to new host
263 //
264 retval = host.lookup_id(host.rpc_seqno);
265 if (!retval) {
266 g_reply->hostid = host.id;
267 log_messages.printf(MSG_NORMAL,
268 "[HOST#%lu] forwarding to new host ID %lu\n",
269 g_request->hostid, host.id
270 );
271 }
272 }
273 if (retval) {
274 g_reply->insert_message("Can't find host record", "low");
275 log_messages.printf(MSG_NORMAL,
276 "[HOST#%lu?] can't find host\n",
277 g_request->hostid
278 );
279 g_request->hostid = 0;
280 goto lookup_user_and_make_new_host;
281 }
282
283 g_reply->host = host;
284
285 // look up user based on the ID in host record,
286 // and see if the authenticator matches (regular or weak)
287 //
288 g_request->using_weak_auth = false;
289 sprintf(buf, "where id=%lu", host.userid);
290 retval = user.lookup(buf);
291 if (!retval && !strcmp(user.authenticator, g_request->authenticator)) {
292 // req auth matches user auth - go on
293 } else {
294 if (!retval) {
295 // user for host.userid exists - check weak auth
296 //
297 get_weak_auth(user, buf);
298 if (!strcmp(buf, g_request->authenticator)) {
299 g_request->using_weak_auth = true;
300 log_messages.printf(MSG_DEBUG,
301 "[HOST#%lu] accepting weak authenticator\n",
302 host.id
303 );
304 }
305 }
306 if (!g_request->using_weak_auth) {
307 // weak auth failed - look up user based on authenticator
308 //
309 strlcpy(
310 user.authenticator, g_request->authenticator, sizeof(user.authenticator)
311 );
312 escape_string(user.authenticator, sizeof(user.authenticator));
313 sprintf(buf, "where authenticator='%s'", user.authenticator);
314 retval = user.lookup(buf);
315 if (retval) {
316 g_reply->insert_message(
317 _("Invalid or missing account key. To fix, remove and add this project."),
318 "notice"
319 );
320 g_reply->set_delay(DELAY_MISSING_KEY);
321 g_reply->nucleus_only = true;
322 log_messages.printf(MSG_CRITICAL,
323 "[HOST#%lu] [USER#%lu] Bad authenticator '%s'\n",
324 host.id, user.id, g_request->authenticator
325 );
326 return ERR_AUTHENTICATOR;
327 }
328 }
329 }
330
331 g_reply->user = user;
332
333 if (host.userid != user.id) {
334 // If the request's host ID isn't consistent with the authenticator,
335 // create a new host record.
336 //
337 log_messages.printf(MSG_NORMAL,
338 "[HOST#%lu] [USER#%lu] inconsistent host ID; creating new host\n",
339 host.id, user.id
340 );
341 goto make_new_host;
342 }
343
344
345 // If the seqno from the host is less than what we expect,
346 // the user must have copied the state file to a different host.
347 // Make a new host record.
348 //
349 if (!batch && g_request->rpc_seqno < g_reply->host.rpc_seqno) {
350 g_request->hostid = 0;
351 log_messages.printf(MSG_NORMAL,
352 "[HOST#%lu] [USER#%lu] RPC seqno %d less than expected %d; creating new host\n",
353 g_reply->host.id, user.id, g_request->rpc_seqno, g_reply->host.rpc_seqno
354 );
355 goto make_new_host;
356 }
357
358 } else {
359 // Here no hostid was given, or the ID was bad.
360 // Look up the user, then create a new host record
361 //
362 lookup_user_and_make_new_host:
363 // if authenticator contains _, it's a weak auth
364 //
365 if (strchr(g_request->authenticator, '_')) {
366 int userid = atoi(g_request->authenticator);
367 retval = user.lookup_id(userid);
368 if (!retval) {
369 get_weak_auth(user, buf);
370 if (strcmp(buf, g_request->authenticator)) {
371 retval = ERR_AUTHENTICATOR;
372 }
373 }
374 } else {
375 strlcpy(
376 user.authenticator, g_request->authenticator,
377 sizeof(user.authenticator)
378 );
379 escape_string(user.authenticator, sizeof(user.authenticator));
380 sprintf(buf, "where authenticator='%s'", user.authenticator);
381 retval = user.lookup(buf);
382 }
383 if (retval) {
384 g_reply->insert_message(
385 "Invalid or missing account key. To fix, remove and add this project .",
386 "low"
387 );
388 g_reply->set_delay(DELAY_MISSING_KEY);
389 log_messages.printf(MSG_CRITICAL,
390 "[HOST#<none>] Bad authenticator '%s': %s\n",
391 g_request->authenticator, boincerror(retval)
392 );
393 return ERR_AUTHENTICATOR;
394 }
395 g_reply->user = user;
396
397 // If host CPID is present,
398 // scan backwards through this user's hosts,
399 // looking for one with the same host CPID.
400 // If we find one, it means the user detached and reattached.
401 // Use the existing host record,
402 // and mark in-progress results as over.
403 //
404 if (strlen(g_request->host.host_cpid)) {
405 if (find_host_by_cpid(user, g_request->host.host_cpid, host)) {
406 log_messages.printf(MSG_NORMAL,
407 "[HOST#%lu] [USER#%lu] No host ID in request, but host with matching CPID found.\n",
408 host.id, host.userid
409 );
410 if (obviously_different(host, g_request->host)) {
411 log_messages.printf(MSG_NORMAL,
412 "[HOST#%lu] [USER#%lu] But that host doesn't match request.\n",
413 host.id, host.userid
414 );
415 } else {
416 if ((g_request->allow_multiple_clients != 1)
417 && (g_request->other_results.size() == 0)
418 ) {
419 mark_results_over(host);
420 }
421 goto got_host;
422 }
423 }
424 }
425
426 make_new_host:
427 // One final attempt to locate an existing host record:
428 // scan backwards through this user's hosts,
429 // looking for one with the same host name,
430 // IP address, processor and amount of RAM.
431 // If found, use the existing host record,
432 // and mark in-progress results as over.
433 //
434 // NOTE: If the client was run with --allow_multiple_clients, skip this.
435 //
436 if ((g_request->allow_multiple_clients != 1)
437 && find_host_by_other(user, g_request->host, host)
438 ) {
439 log_messages.printf(MSG_NORMAL,
440 "[HOST#%lu] [USER#%lu] Found similar existing host for this user - assigned.\n",
441 host.id, host.userid
442 );
443 if (g_request->other_results.size() == 0) {
444 // mark host's jobs as abandoned
445 // if client has no jobs in progress
446 //
447 mark_results_over(host);
448 }
449 goto got_host;
450 }
451 // either of the above cases,
452 // or host ID didn't match user ID,
453 // or RPC seqno was too low.
454 //
455 // Create a new host.
456 // g_reply->user is filled in and valid at this point
457 //
458 host = g_request->host;
459 host.id = 0;
460 host.create_time = time(0);
461 host.userid = g_reply->user.id;
462 host.rpc_seqno = 0;
463 host.expavg_time = time(0);
464 safe_strcpy(host.venue, g_reply->user.venue);
465 host.fix_nans();
466 retval = host.insert();
467 if (retval) {
468 g_reply->insert_message(
469 "Couldn't create host record in database", "low"
470 );
471 boinc_db.print_error("host.insert()");
472 log_messages.printf(MSG_CRITICAL, "host.insert() failed\n");
473 return retval;
474 }
475 host.id = boinc_db.insert_id();
476
477 got_host:
478 g_reply->host = host;
479 g_reply->hostid = g_reply->host.id;
480 // this tells client to updates its host ID
481 g_request->rpc_seqno = 0;
482 // this value eventually gets written to host DB record;
483 // for new hosts it must be zero.
484 // This kludge forces this.
485 }
486
487 // have user record in g_reply->user at this point
488 //
489
490 if (g_reply->user.teamid) {
491 retval = team.lookup_id(g_reply->user.teamid);
492 if (!retval) g_reply->team = team;
493 }
494
495 // compute email hash
496 //
497 md5_block(
498 (unsigned char*)g_reply->user.email_addr,
499 strlen(g_reply->user.email_addr),
500 g_reply->email_hash
501 );
502
503 // if new user CPID, update user record
504 //
505 if (!g_request->using_weak_auth && strlen(g_request->cross_project_id)) {
506 if (strcmp(g_request->cross_project_id, g_reply->user.cross_project_id)) {
507 user.id = g_reply->user.id;
508 escape_string(g_request->cross_project_id, sizeof(g_request->cross_project_id));
509 sprintf(buf, "cross_project_id='%s'", g_request->cross_project_id);
510 unescape_string(g_request->cross_project_id, sizeof(g_request->cross_project_id));
511 user.update_field(buf);
512 }
513 }
514
515 return 0;
516 }
517
get_remote_addr()518 inline static const char* get_remote_addr() {
519 // Server is behind a load balancer or proxy
520 const char* p = getenv("HTTP_X_FORWARDED_FOR");
521 if (p) {
522 return p;
523 }
524
525 const char * r = getenv("REMOTE_ADDR");
526 return r ? r : "?.?.?.?";
527 }
528
529 // modify host struct based on request.
530 // Copy all fields that are determined by the client.
531 //
modify_host_struct(HOST & host)532 static int modify_host_struct(HOST& host) {
533 host.timezone = g_request->host.timezone;
534 strlcpy(host.domain_name, g_request->host.domain_name, sizeof(host.domain_name));
535 char buf[1024], buf2[1024];
536 sprintf(buf, "[BOINC|%d.%d.%d",
537 g_request->core_client_major_version,
538 g_request->core_client_minor_version,
539 g_request->core_client_release
540 );
541 if (strlen(g_request->client_brand)) {
542 strcat(buf, "|");
543 strcat(buf, g_request->client_brand);
544 }
545 strcat(buf, "]");
546 g_request->coprocs.summary_string(buf2, sizeof(buf2));
547 strlcpy(host.serialnum, buf, sizeof(host.serialnum));
548 strlcat(host.serialnum, buf2, sizeof(host.serialnum));
549 if (strlen(g_request->host.virtualbox_version)) {
550 sprintf(buf2, "[vbox|%s|%d|%d]",
551 g_request->host.virtualbox_version,
552 (strstr(g_request->host.p_features, "vmx") || strstr(g_request->host.p_features, "svm"))?1:0,
553 g_request->host.p_vm_extensions_disabled?0:1
554 );
555 strlcat(host.serialnum, buf2, sizeof(host.serialnum));
556 }
557 if (strcmp(host.last_ip_addr, g_request->host.last_ip_addr)) {
558 strlcpy(
559 host.last_ip_addr, g_request->host.last_ip_addr,
560 sizeof(host.last_ip_addr)
561 );
562 host.nsame_ip_addr = 0;
563 } else {
564 host.nsame_ip_addr++;
565 }
566 host.on_frac = g_request->host.on_frac;
567 host.connected_frac = g_request->host.connected_frac;
568 host.active_frac = g_request->host.active_frac;
569 host.gpu_active_frac = g_request->host.gpu_active_frac;
570 host.cpu_and_network_available_frac = g_request->host.cpu_and_network_available_frac;
571 host.client_start_time = g_request->host.client_start_time;
572 host.previous_uptime = g_request->host.previous_uptime;
573 host.duration_correction_factor = g_request->host.duration_correction_factor;
574 host.p_ncpus = g_request->host.p_ncpus;
575 strlcpy(host.p_vendor, g_request->host.p_vendor, sizeof(host.p_vendor));
576 // unlikely this will change
577 strlcpy(host.p_model, g_request->host.p_model, sizeof(host.p_model));
578 host.p_fpops = g_request->host.p_fpops;
579 host.p_iops = g_request->host.p_iops;
580 host.p_membw = g_request->host.p_membw;
581 strlcpy(host.os_name, g_request->host.os_name, sizeof(host.os_name));
582 strlcpy(host.os_version, g_request->host.os_version, sizeof(host.os_version));
583 host.m_nbytes = g_request->host.m_nbytes;
584 host.m_cache = g_request->host.m_cache;
585 host.m_swap = g_request->host.m_swap;
586 host.d_total = g_request->host.d_total;
587 host.d_free = g_request->host.d_free;
588 host.d_boinc_used_total = g_request->host.d_boinc_used_total;
589 host.d_boinc_used_project = g_request->host.d_boinc_used_project;
590 host.n_bwup = g_request->host.n_bwup;
591 host.n_bwdown = g_request->host.n_bwdown;
592 if (strlen(g_request->host.host_cpid)) {
593 safe_strcpy(host.host_cpid, g_request->host.host_cpid);
594 }
595 strlcpy(host.product_name, g_request->host.product_name, sizeof(host.product_name));
596 host.fix_nans();
597
598 return 0;
599 }
600
601 // update the DB record to the values in "xhost"
602 // "initial_host" stores the current DB values;
603 // update only those fields that have changed
604 //
update_host_record(HOST & initial_host,HOST & xhost,USER & user)605 static int update_host_record(HOST& initial_host, HOST& xhost, USER& user) {
606 DB_HOST host;
607 int retval;
608 char buf[1024];
609
610 host = xhost;
611
612 // hash the CPID reported by the host with the user's email address.
613 // This prevents one user from spoofing another one's host.
614 //
615 if (strlen(host.host_cpid)) {
616 sprintf(buf, "%s%s", host.host_cpid, user.email_addr);
617 md5_block((const unsigned char*)buf, strlen(buf), host.host_cpid);
618 }
619
620 const char* p = get_remote_addr();
621 if (p) {
622 strlcpy(host.external_ip_addr, p, sizeof(host.external_ip_addr));
623 }
624 retval = host.update_diff_sched(initial_host);
625 if (retval) {
626 log_messages.printf(MSG_CRITICAL,
627 "host.update() failed: %s\n", boincerror(retval)
628 );
629 }
630 return 0;
631 }
632
reason_str(int n)633 inline const char* reason_str(int n) {
634 switch (n) {
635 case ABORT_REASON_NOT_FOUND: return "result not in request";
636 case ABORT_REASON_WU_CANCELLED: return "WU cancelled";
637 case ABORT_REASON_ASSIMILATED: return "WU assimilated";
638 case ABORT_REASON_TIMED_OUT: return "result timed out";
639 }
640 return "Unknown";
641 }
642
643 // Figure out which of the results the host currently has
644 // should be aborted outright, or aborted if not started yet
645 //
send_result_abort()646 int send_result_abort() {
647 int aborts_sent = 0;
648 int retval = 0;
649 DB_IN_PROGRESS_RESULT result;
650 std::string result_names;
651 unsigned int i;
652
653 if (g_request->other_results.size() == 0) {
654 return 0;
655 }
656
657 // build list of result names
658 //
659 for (i=0; i<g_request->other_results.size(); i++) {
660 OTHER_RESULT& orp=g_request->other_results[i];
661 orp.abort = true;
662 // if the host has a result not in the DB, abort it
663 orp.abort_if_not_started = false;
664 orp.reason = ABORT_REASON_NOT_FOUND;
665 if (i > 0) result_names.append(", ");
666 result_names.append("'");
667 char buf[1024];
668 safe_strcpy(buf, orp.name);
669 escape_string(buf, sizeof(buf));
670 result_names.append(buf);
671 result_names.append("'");
672 }
673
674 // look up selected fields from the results and their WUs,
675 // and decide if they should be aborted
676 //
677 while (!(retval = result.enumerate(g_reply->host.id, result_names.c_str()))) {
678 for (i=0; i<g_request->other_results.size(); i++) {
679 OTHER_RESULT& orp = g_request->other_results[i];
680 if (!strcmp(orp.name, result.result_name)) {
681 if (result.error_mask&WU_ERROR_CANCELLED ) {
682 // if the WU has been canceled, abort the result
683 //
684 orp.abort = true;
685 orp.abort_if_not_started = false;
686 orp.reason = ABORT_REASON_WU_CANCELLED;
687 } else if (result.assimilate_state == ASSIMILATE_DONE) {
688 // if the WU has been assimilated, abort if not started
689 //
690 orp.abort = false;
691 orp.abort_if_not_started = true;
692 orp.reason = ABORT_REASON_ASSIMILATED;
693 } else if (result.server_state == RESULT_SERVER_STATE_OVER
694 && result.outcome == RESULT_OUTCOME_NO_REPLY
695 ) {
696 // if timed out, abort if not started
697 //
698 orp.abort = false;
699 orp.abort_if_not_started = true;
700 orp.reason = ABORT_REASON_TIMED_OUT;
701 } else {
702 // all is good with the result - let it process
703 orp.abort = false;
704 orp.abort_if_not_started = false;
705 }
706 break;
707 }
708 }
709 }
710
711 // If enumeration returned an error, don't send any aborts
712 //
713 if (retval && (retval != ERR_DB_NOT_FOUND)) {
714 return retval;
715 }
716
717 // loop through the results and send the appropriate message (if any)
718 //
719 for (i=0; i<g_request->other_results.size(); i++) {
720 OTHER_RESULT& orp = g_request->other_results[i];
721 if (orp.abort) {
722 g_reply->result_aborts.push_back(orp.name);
723 log_messages.printf(MSG_NORMAL,
724 "[HOST#%lu]: Send result_abort for result %s; reason: %s\n",
725 g_reply->host.id, orp.name, reason_str(orp.reason)
726 );
727 // send user message
728 char buf[256];
729 sprintf(buf, "Result %s is no longer usable", orp.name);
730 g_reply->insert_message(buf, "low");
731 } else if (orp.abort_if_not_started) {
732 g_reply->result_abort_if_not_starteds.push_back(orp.name);
733 log_messages.printf(MSG_NORMAL,
734 "[HOST#%lu]: Send result_abort_if_unstarted for result %s; reason %d\n",
735 g_reply->host.id, orp.name, orp.reason
736 );
737 }
738 }
739
740 return aborts_sent;
741 }
742
743 // 1) Decide which global prefs to use for sched decisions: either
744 // - <working_global_prefs> from request msg
745 // - <global_prefs> from request message
746 // - prefs from user DB record
747 // and parse them into g_request->global_prefs.
748 // 2) update prefs in user record if needed
749 // 2) send global prefs in reply msg if needed
750 //
handle_global_prefs()751 int handle_global_prefs() {
752 char buf[BLOB_SIZE+256];
753 g_reply->send_global_prefs = false;
754 bool have_working_prefs = (strlen(g_request->working_global_prefs_xml)>0);
755 bool have_master_prefs = (strlen(g_request->global_prefs_xml)>0);
756 // absent if the host has host-specific prefs
757 bool have_db_prefs = (strlen(g_reply->user.global_prefs)>0);
758 bool same_account = !strcmp(
759 g_request->global_prefs_source_email_hash, g_reply->email_hash
760 );
761 double master_mod_time=0, db_mod_time=0, working_mod_time=0;
762 if (have_master_prefs) {
763 parse_double(g_request->global_prefs_xml, "<mod_time>", master_mod_time);
764 if (master_mod_time > dtime()) master_mod_time = dtime();
765 }
766 if (have_working_prefs) {
767 parse_double(g_request->working_global_prefs_xml, "<mod_time>", working_mod_time);
768 if (working_mod_time > dtime()) working_mod_time = dtime();
769 }
770 if (have_db_prefs) {
771 parse_double(g_reply->user.global_prefs, "<mod_time>", db_mod_time);
772 if (db_mod_time > dtime()) db_mod_time = dtime();
773 }
774
775 if (config.debug_prefs) {
776 log_messages.printf(MSG_NORMAL,
777 "[prefs] have_master:%d have_working: %d have_db: %d\n",
778 have_master_prefs, have_working_prefs, have_db_prefs
779 );
780 }
781
782 // decide which prefs to use for sched decisions,
783 // and parse them into g_request->global_prefs
784 //
785 if (have_working_prefs) {
786 g_request->global_prefs.parse(g_request->working_global_prefs_xml, "");
787 if (config.debug_prefs) {
788 log_messages.printf(MSG_NORMAL, "[prefs] using working prefs\n");
789 }
790 } else {
791 if (have_master_prefs) {
792 if (have_db_prefs && db_mod_time > master_mod_time) {
793 g_request->global_prefs.parse(g_reply->user.global_prefs, g_reply->host.venue);
794 if (config.debug_prefs) {
795 log_messages.printf(MSG_NORMAL,
796 "[prefs] using db prefs - more recent\n"
797 );
798 }
799 } else {
800 g_request->global_prefs.parse(g_request->global_prefs_xml, g_reply->host.venue);
801 if (config.debug_prefs) {
802 log_messages.printf(MSG_NORMAL,
803 "[prefs] using master prefs\n"
804 );
805 }
806 }
807 } else {
808 if (have_db_prefs) {
809 g_request->global_prefs.parse(g_reply->user.global_prefs, g_reply->host.venue);
810 if (config.debug_prefs) {
811 log_messages.printf(MSG_NORMAL, "[prefs] using db prefs\n");
812 }
813 } else {
814 g_request->global_prefs.defaults();
815 if (config.debug_prefs) {
816 log_messages.printf(MSG_NORMAL, "[prefs] using default prefs\n");
817 }
818 }
819 }
820 }
821
822 // decide whether to update DB
823 //
824 if (!g_request->using_weak_auth && have_master_prefs) {
825 bool update_user_record = false;
826 if (have_db_prefs) {
827 if (master_mod_time > db_mod_time && same_account) {
828 update_user_record = true;
829 }
830 } else {
831 if (same_account) update_user_record = true;
832 }
833 if (update_user_record) {
834 if (config.debug_prefs) {
835 log_messages.printf(MSG_NORMAL, "[prefs] updating db prefs\n");
836 }
837 safe_strcpy(g_reply->user.global_prefs, g_request->global_prefs_xml);
838 DB_USER user;
839 user.id = g_reply->user.id;
840 escape_string(g_request->global_prefs_xml, sizeof(g_request->global_prefs_xml));
841 sprintf(buf, "global_prefs='%s'", g_request->global_prefs_xml);
842 unescape_string(g_request->global_prefs_xml, sizeof(g_request->global_prefs_xml));
843 int retval = user.update_field(buf);
844 if (retval) {
845 log_messages.printf(MSG_CRITICAL,
846 "user.update_field() failed: %s\n", boincerror(retval)
847 );
848 }
849 }
850 }
851
852 // decide whether to send DB prefs in reply msg
853 //
854 if (config.debug_prefs) {
855 log_messages.printf(MSG_NORMAL,
856 "[prefs] have DB prefs: %d; dbmod %f; global mod %f; working mod %f\n",
857 have_db_prefs, db_mod_time, g_request->global_prefs.mod_time, working_mod_time
858 );
859 }
860 if (have_db_prefs && db_mod_time > master_mod_time && db_mod_time > working_mod_time) {
861 if (config.debug_prefs) {
862 log_messages.printf(MSG_DEBUG,
863 "[prefs] sending DB prefs in reply\n"
864 );
865 }
866 g_reply->send_global_prefs = true;
867 }
868 return 0;
869 }
870
871 // if the client has an old code sign public key,
872 // send it the new one, with a signature based on the old one.
873 // If they don't have a code sign key, send them one.
874 // Return false if they have a key we don't recognize
875 // (in which case we won't send them work).
876 //
send_code_sign_key(char * code_sign_key)877 bool send_code_sign_key(char* code_sign_key) {
878 char* oldkey, *signature;
879 int i, retval;
880 char path[MAXPATHLEN];
881
882 if (!strlen(g_request->code_sign_key)) {
883 safe_strcpy(g_reply->code_sign_key, code_sign_key);
884 return true;
885 }
886 if (!strcmp(g_request->code_sign_key, code_sign_key)) {
887 return true;
888 }
889
890 log_messages.printf(MSG_NORMAL, "received old code sign key\n");
891
892 // look for a signature file for the client's key.
893 // These are in pairs of files (N = 0, 1, ...)
894 // old_key_N: contains an old key
895 // signature_N: contains a signature for new key,
896 // based on the old key
897 // signature_stripped_N: signature for new key w/ trailing \n removed
898 // (needed for 7.0+ clients, which strip trailing whitespace)
899 //
900 // A project can have several of these if it wants,
901 // e.g. if it changes keys a lot.
902 //
903 for (i=0; ; i++) {
904 sprintf(path, "%s/old_key_%d", config.key_dir, i);
905 retval = read_file_malloc(path, oldkey);
906 if (retval) {
907 // we've scanned all the signature files and
908 // didn't find one that worked.
909 // User must reattach.
910 //
911 log_messages.printf(MSG_CRITICAL,
912 "scanned old_key_i files, can find client's key\n"
913 );
914 break;
915 }
916 strip_whitespace(oldkey);
917 if (!strcmp(oldkey, g_request->code_sign_key)) {
918 // We've found the client's key.
919 // Get the signature for the new key.
920 //
921 if (g_request->core_client_major_version < 7) {
922 sprintf(path, "%s/signature_%d", config.key_dir, i);
923 } else {
924 sprintf(path, "%s/signature_stripped_%d", config.key_dir, i);
925 }
926 retval = read_file_malloc(path, signature);
927 if (retval) {
928 // project is missing the signature file.
929 // Tell the user to reattach.
930 //
931 log_messages.printf(MSG_CRITICAL,
932 "Missing signature file for old key %d\n", i
933 );
934 free(oldkey);
935 break;
936 } else {
937 log_messages.printf(MSG_NORMAL,
938 "sending new code sign key and signature\n"
939 );
940 safe_strcpy(g_reply->code_sign_key, code_sign_key);
941 safe_strcpy(g_reply->code_sign_key_signature, signature);
942 free(signature);
943 free(oldkey);
944 return true;
945 }
946 }
947 free(oldkey);
948 }
949
950 g_reply->insert_message(
951 _("The project has changed its security key. Please remove and add this project."),
952 "notice"
953 );
954 return false;
955 }
956
957 // If <min_core_client_version_announced> is set,
958 // and the core client version is less than this version,
959 // send a warning to users to upgrade before deadline
960 // <min_core_client_upgrade_deadline>
961 //
warn_user_if_core_client_upgrade_scheduled()962 void warn_user_if_core_client_upgrade_scheduled() {
963 if (g_request->core_client_version < config.min_core_client_version_announced) {
964
965 // time remaining in hours, before upgrade required
966 int remaining = config.min_core_client_upgrade_deadline-time(0);
967 remaining /= 3600;
968
969 if (remaining > 0) {
970
971 char msg[512];
972 int days = remaining / 24;
973 int hours = remaining % 24;
974
975 sprintf(msg,
976 "In %d days and %d hours, this project will require a minimum "
977 "BOINC version of %d.%d.%d. You are currently using "
978 "version %d.%d.%d; please upgrade before this time.",
979 days, hours,
980 config.min_core_client_version_announced / 10000,
981 (config.min_core_client_version_announced / 100)%100,
982 config.min_core_client_version_announced % 100,
983 g_request->core_client_major_version,
984 g_request->core_client_minor_version,
985 g_request->core_client_release
986 );
987 // make this low priority until three days are left. Then
988 // bump to high.
989 //
990 if (days<3) {
991 g_reply->insert_message(msg, "notice");
992 } else {
993 g_reply->insert_message(msg, "low");
994 }
995 log_messages.printf(MSG_DEBUG,
996 "Sending warning: upgrade client %d.%d.%d within %d days %d hours\n",
997 g_request->core_client_major_version,
998 g_request->core_client_minor_version,
999 g_request->core_client_release,
1000 days, hours
1001 );
1002 }
1003 }
1004 return;
1005 }
1006
unacceptable_os()1007 bool unacceptable_os() {
1008 unsigned int i;
1009 char buf[1024];
1010
1011 for (i=0; i<config.ban_os->size(); i++) {
1012 regex_t& re = (*config.ban_os)[i];
1013 safe_strcpy(buf, g_request->host.os_name);
1014 safe_strcat(buf, "\t");
1015 safe_strcat(buf, g_request->host.os_version);
1016 if (!regexec(&re, buf, 0, NULL, 0)) {
1017 log_messages.printf(MSG_NORMAL,
1018 "Unacceptable OS %s %s\n",
1019 g_request->host.os_name, g_request->host.os_version
1020 );
1021 sprintf(buf, "%s %s %s",
1022 _("This project doesn't support operating system"),
1023 g_request->host.os_name, g_request->host.os_version
1024 );
1025 g_reply->insert_message(buf, "notice");
1026 g_reply->set_delay(DELAY_UNACCEPTABLE_OS);
1027 return true;
1028 }
1029 }
1030 return false;
1031 }
1032
unacceptable_cpu()1033 bool unacceptable_cpu() {
1034 unsigned int i;
1035 char buf[1024];
1036
1037 for (i=0; i<config.ban_cpu->size(); i++) {
1038 regex_t& re = (*config.ban_cpu)[i];
1039 safe_strcpy(buf, g_request->host.p_vendor);
1040 safe_strcat(buf, "\t");
1041 safe_strcat(buf, g_request->host.p_model);
1042 if (!regexec(&re, buf, 0, NULL, 0)) {
1043 log_messages.printf(MSG_NORMAL,
1044 "Unacceptable CPU %s %s\n",
1045 g_request->host.p_vendor, g_request->host.p_model
1046 );
1047 sprintf(buf, "%s %s %s",
1048 _("This project doesn't support CPU type"),
1049 g_request->host.p_vendor, g_request->host.p_model
1050 );
1051 g_reply->insert_message(buf, "notice");
1052 g_reply->set_delay(DELAY_UNACCEPTABLE_OS);
1053 return true;
1054 }
1055 }
1056 return false;
1057 }
1058
wrong_core_client_version()1059 bool wrong_core_client_version() {
1060 if (!config.min_core_client_version) {
1061 return false;
1062 }
1063 if (g_request->core_client_version >= config.min_core_client_version) {
1064 return false;
1065 }
1066 log_messages.printf(MSG_NORMAL,
1067 "[HOST#%lu] Wrong client version from user: wanted %d, got %d\n",
1068 g_request->hostid,
1069 config.min_core_client_version, g_request->core_client_minor_version
1070 );
1071 g_reply->insert_message(
1072 _("Your BOINC client software is too old. Please install the current version."),
1073 "notice"
1074 );
1075 g_reply->set_delay(DELAY_BAD_CLIENT_VERSION);
1076 return true;
1077 }
1078
handle_msgs_from_host()1079 void handle_msgs_from_host() {
1080 unsigned int i;
1081 DB_MSG_FROM_HOST mfh;
1082 int retval;
1083
1084 for (i=0; i<g_request->msgs_from_host.size(); i++) {
1085 g_reply->send_msg_ack = true;
1086 MSG_FROM_HOST_DESC& md = g_request->msgs_from_host[i];
1087 mfh.clear();
1088 mfh.create_time = time(0);
1089 safe_strcpy(mfh.variety, md.variety);
1090 mfh.hostid = g_reply->host.id;
1091 mfh.handled = false;
1092 safe_strcpy(mfh.xml, md.msg_text.c_str());
1093 log_messages.printf(MSG_NORMAL,
1094 "got msg from host; variety %s \n",
1095 mfh.variety
1096 );
1097 retval = mfh.insert();
1098 if (retval) {
1099 log_messages.printf(MSG_CRITICAL,
1100 "[HOST#%lu] message insert failed: %s\n",
1101 g_reply->host.id, boincerror(retval)
1102 );
1103 g_reply->send_msg_ack = false;
1104
1105 // may as well return; if one insert failed, others will too
1106 //
1107 return;
1108 }
1109 }
1110 }
1111
handle_msgs_to_host()1112 void handle_msgs_to_host() {
1113 DB_MSG_TO_HOST mth;
1114 char buf[256];
1115 sprintf(buf, "where hostid = %lu and handled = %d", g_reply->host.id, 0);
1116 while (!mth.enumerate(buf)) {
1117 g_reply->msgs_to_host.push_back(mth);
1118 mth.handled = true;
1119 mth.update();
1120 }
1121 }
1122
log_request()1123 static void log_request() {
1124 log_messages.printf(MSG_NORMAL,
1125 "Request: [USER#%lu] [HOST#%lu] [IP %s] client %d.%d.%d\n",
1126 g_reply->user.id, g_reply->host.id, get_remote_addr(),
1127 g_request->core_client_major_version,
1128 g_request->core_client_minor_version,
1129 g_request->core_client_release
1130 );
1131 if (config.debug_request_details) {
1132 log_messages.printf(MSG_DEBUG,
1133 "Request details: auth %s, RPC seqno %d, platform %s\n",
1134 g_request->authenticator,
1135 g_request->rpc_seqno,
1136 g_request->platform.name
1137 );
1138 }
1139 log_messages.set_indent_level(2);
1140 }
1141
bad_install_type()1142 bool bad_install_type() {
1143 if (config.no_vista_sandbox) {
1144 if (!strcmp(g_request->host.os_name, "Microsoft Windows Vista")) {
1145 if (g_request->sandbox == 1) {
1146 log_messages.printf(MSG_NORMAL,
1147 "Vista secure install - not sending work\n"
1148 );
1149 g_reply->insert_message(
1150 "Unable to send work to Vista with BOINC installed in protected mode. Please reinstall BOINC and uncheck 'Protected application execution'",
1151 "notice"
1152 );
1153 }
1154 }
1155 }
1156 return false;
1157 }
1158
requesting_work()1159 static inline bool requesting_work() {
1160 if (g_request->dont_send_work) return false;
1161 if (g_request->work_req_seconds > 0) return true;
1162 if (g_request->cpu_req_secs > 0) return true;
1163 for (int i=1; i<NPROC_TYPES; i++) {
1164 COPROC* cp = g_request->coprocs.proc_type_to_coproc(i);
1165 if (cp && cp->count && cp->req_secs) return true;
1166 }
1167 if (ssp->have_nci_app) return true;
1168 return false;
1169 }
1170
process_request(char * code_sign_key)1171 void process_request(char* code_sign_key) {
1172 PLATFORM* platform;
1173 int retval;
1174 double last_rpc_time, x;
1175 struct tm *rpc_time_tm;
1176 bool ok_to_send_work = !config.dont_send_jobs;
1177 bool have_no_work = false;
1178 char buf[256];
1179 HOST initial_host;
1180 unsigned int i;
1181 time_t t;
1182
1183 memset(&g_reply->wreq, 0, sizeof(g_reply->wreq));
1184
1185 // if client has sticky files we don't need any more, tell it
1186 //
1187 do_file_delete_regex();
1188
1189 // if different major version of BOINC, just send a message
1190 //
1191 if (wrong_core_client_version()
1192 || unacceptable_os()
1193 || unacceptable_cpu()
1194 ) {
1195 ok_to_send_work = false;
1196 }
1197
1198 // if no jobs reported and none to send, return without accessing DB
1199 //
1200 if (!ok_to_send_work && !g_request->results.size()) {
1201 return;
1202 }
1203
1204 warn_user_if_core_client_upgrade_scheduled();
1205
1206 g_wreq->no_jobs_available = false;
1207 if (requesting_work()) {
1208 if (config.locality_scheduling || config.locality_scheduler_fraction || config.enable_assignment) {
1209 have_no_work = false;
1210 } else {
1211 lock_sema();
1212 have_no_work = ssp->no_work(g_pid);
1213 if (have_no_work) {
1214 g_wreq->no_jobs_available = true;
1215 }
1216 unlock_sema();
1217 }
1218 }
1219
1220 // If:
1221 // - there's no work,
1222 // - a config flag is set,
1223 // - client isn't returning results,
1224 // - this isn't an initial RPC,
1225 // - client is requesting work
1226 // then return without accessing the DB.
1227 // This is an efficiency hack for when servers are overloaded
1228 //
1229 if (
1230 have_no_work
1231 && config.nowork_skip
1232 && requesting_work()
1233 && (g_request->results.size() == 0)
1234 && (g_request->hostid != 0)
1235 ) {
1236 g_reply->insert_message("No work available", "low");
1237 g_reply->set_delay(DELAY_NO_WORK_SKIP);
1238 if (!config.msg_to_host && !config.enable_vda) {
1239 log_messages.printf(MSG_NORMAL, "No work - skipping DB access\n");
1240 return;
1241 }
1242 }
1243
1244 // FROM HERE ON DON'T RETURN; "goto leave" instead
1245 // (because ssp->no_work() may have tagged an entry in the work array
1246 // with our process ID)
1247
1248 retval = open_database();
1249 if (retval) {
1250 send_error_message("Server can't open database", config.maintenance_delay);
1251 g_reply->project_is_down = true;
1252 goto leave;
1253 }
1254
1255 retval = authenticate_user();
1256 if (retval) goto leave;
1257 if (g_reply->user.id == 0) {
1258 log_messages.printf(MSG_CRITICAL, "No user ID!\n");
1259 }
1260 initial_host = g_reply->host;
1261 g_reply->host.rpc_seqno = g_request->rpc_seqno;
1262
1263 g_reply->nucleus_only = false;
1264
1265 log_request();
1266
1267 #if 0
1268 // if you need to debug a problem w/ a particular host or user,
1269 // edit the following
1270 //
1271 if (g_reply->user.id == XX || g_reply.host.id == YY) {
1272 config.sched_debug_level = 3;
1273 config.debug_send = true;
1274 ...
1275 }
1276 #endif
1277
1278 // is host blacklisted?
1279 //
1280 if (g_reply->host._max_results_day == -1) {
1281 send_error_message("Not accepting requests from this host", 86400);
1282 goto leave;
1283 }
1284
1285 if (strlen(config.sched_lockfile_dir)) {
1286 int pid_with_lock = lock_sched();
1287 if (pid_with_lock > 0) {
1288 log_messages.printf(MSG_CRITICAL,
1289 "Another scheduler instance [PID=%d] is running for [HOST#%lu]\n",
1290 pid_with_lock, g_reply->host.id
1291 );
1292 } else if (pid_with_lock) {
1293 log_messages.printf(MSG_CRITICAL,
1294 "Error acquiring lock for [HOST#%lu]\n", g_reply->host.id
1295 );
1296 }
1297 if (pid_with_lock) {
1298 send_error_message(
1299 "Another scheduler instance is running for this host", 60
1300 );
1301 goto leave;
1302 }
1303 }
1304
1305 // in deciding whether it's a new day,
1306 // add a random factor (based on host ID)
1307 // to smooth out network traffic over the day
1308 //
1309 retval = rand();
1310 srand(g_reply->host.id);
1311 x = drand()*86400;
1312 srand(retval);
1313 last_rpc_time = g_reply->host.rpc_time;
1314 t = (time_t)(g_reply->host.rpc_time + x);
1315 rpc_time_tm = localtime(&t);
1316 g_request->last_rpc_dayofyear = rpc_time_tm->tm_yday;
1317
1318 t = time(0);
1319 g_reply->host.rpc_time = t;
1320 t += (time_t)x;
1321 rpc_time_tm = localtime(&t);
1322 g_request->current_rpc_dayofyear = rpc_time_tm->tm_yday;
1323
1324 retval = modify_host_struct(g_reply->host);
1325
1326 // write time stats to disk if present
1327 //
1328 if (g_request->have_time_stats_log) {
1329 write_time_stats_log();
1330 }
1331
1332 // look up the client's platform(s) in the DB
1333 //
1334 platform = ssp->lookup_platform(g_request->platform.name);
1335 if (platform) g_request->platforms.list.push_back(platform);
1336
1337 // if primary platform is anonymous, ignore alternate platforms
1338 //
1339 if (strcmp(g_request->platform.name, "anonymous")) {
1340 for (i=0; i<g_request->alt_platforms.size(); i++) {
1341 platform = ssp->lookup_platform(g_request->alt_platforms[i].name);
1342 if (platform) g_request->platforms.list.push_back(platform);
1343 }
1344 }
1345 if (g_request->platforms.list.size() == 0) {
1346 sprintf(buf, "%s %s",
1347 _("This project doesn't support computers of type"),
1348 g_request->platform.name
1349 );
1350 g_reply->insert_message(buf, "notice");
1351 log_messages.printf(MSG_CRITICAL,
1352 "[HOST#%lu] platform '%s' not found\n",
1353 g_reply->host.id, g_request->platform.name
1354 );
1355 g_reply->set_delay(DELAY_PLATFORM_UNSUPPORTED);
1356 goto leave;
1357 }
1358
1359 handle_global_prefs();
1360
1361 read_host_app_versions();
1362 update_n_jobs_today();
1363
1364 handle_results();
1365 handle_file_xfer_results();
1366 if (config.enable_vda) {
1367 handle_vda();
1368 }
1369
1370 // Do this before resending lost jobs
1371 //
1372 if (bad_install_type()) {
1373 ok_to_send_work = false;
1374 }
1375 if (!requesting_work()) {
1376 ok_to_send_work = false;
1377 }
1378 send_work_setup();
1379
1380 if (g_request->have_other_results_list) {
1381 if (ok_to_send_work
1382 && (config.resend_lost_results || g_wreq->resend_lost_results)
1383 && !g_request->results_truncated
1384 ) {
1385 if (resend_lost_work()) {
1386 if (config.debug_send) {
1387 log_messages.printf(MSG_NORMAL,
1388 "[send] Resent lost jobs, don't send more\n"
1389 );
1390 }
1391 ok_to_send_work = false;
1392 }
1393 }
1394 if (config.send_result_abort) {
1395 send_result_abort();
1396 }
1397 }
1398
1399 if (requesting_work()) {
1400 if (!send_code_sign_key(code_sign_key)) {
1401 ok_to_send_work = false;
1402 }
1403
1404 if (have_no_work) {
1405 if (config.debug_send) {
1406 log_messages.printf(MSG_NORMAL,
1407 "[send] No jobs in shmem cache\n"
1408 );
1409 }
1410 }
1411
1412 // if last RPC was within config.min_sendwork_interval, don't send work
1413 //
1414 if (!have_no_work && ok_to_send_work) {
1415 if (config.min_sendwork_interval) {
1416 double diff = dtime() - last_rpc_time;
1417 if (diff < config.min_sendwork_interval) {
1418 ok_to_send_work = false;
1419 log_messages.printf(MSG_NORMAL,
1420 "Not sending work - last request too recent: %f\n", diff
1421 );
1422 sprintf(buf,
1423 "Not sending work - last request too recent: %d sec", (int)diff
1424 );
1425 g_reply->insert_message(buf, "low");
1426
1427 // the 1.01 is in case client's clock
1428 // is slightly faster than ours
1429 //
1430 g_reply->set_delay(1.01*config.min_sendwork_interval);
1431 }
1432 }
1433 if (ok_to_send_work) {
1434 send_work();
1435 }
1436 }
1437 if (g_wreq->no_jobs_available) {
1438 g_reply->insert_message("Project has no tasks available", "low");
1439 }
1440 }
1441
1442
1443 handle_msgs_from_host();
1444 if (config.msg_to_host) {
1445 handle_msgs_to_host();
1446 }
1447
1448 // compute GPU params
1449 //
1450 g_reply->host.p_ngpus = 0;
1451 g_reply->host.p_gpu_fpops = 0;
1452 for (int j=1; j<g_request->coprocs.n_rsc; j++) {
1453 int n = g_request->coprocs.coprocs[j].count;
1454 g_reply->host.p_ngpus += n;
1455 g_reply->host.p_gpu_fpops += n*g_request->coprocs.coprocs[j].peak_flops;
1456 }
1457
1458 update_host_record(initial_host, g_reply->host, g_reply->user);
1459 write_host_app_versions();
1460
1461 leave:
1462 if (!have_no_work) {
1463 ssp->restore_work(g_pid);
1464 }
1465 }
1466
log_incomplete_request()1467 static void log_incomplete_request() {
1468 // BOINC scheduler requests use method POST.
1469 // So method GET means that someone is trying a browser.
1470 //
1471 char *rm=getenv("REQUEST_METHOD");
1472 bool used_get = false;
1473 if (rm && !strcmp(rm, "GET")) {
1474 used_get = true;
1475 }
1476 log_messages.printf(MSG_NORMAL,
1477 "Incomplete request received %sfrom IP %s, auth %s, platform %s, version %d.%d.%d\n",
1478 used_get?"(used GET method - probably a browser) ":"",
1479 get_remote_addr(), g_request->authenticator, g_request->platform.name,
1480 g_request->core_client_major_version, g_request->core_client_minor_version,
1481 g_request->core_client_release
1482 );
1483 }
1484
log_user_messages()1485 static void log_user_messages() {
1486 for (unsigned int i=0; i<g_reply->messages.size(); i++) {
1487 USER_MESSAGE um = g_reply->messages[i];
1488 log_messages.printf(MSG_NORMAL,
1489 "[user_messages] [HOST#%lu] MSG(%s) %s\n",
1490 g_reply->host.id, um.priority.c_str(), um.message.c_str()
1491 );
1492 }
1493 }
1494
handle_request(FILE * fin,FILE * fout,char * code_sign_key)1495 void handle_request(FILE* fin, FILE* fout, char* code_sign_key) {
1496 SCHEDULER_REQUEST sreq;
1497 SCHEDULER_REPLY sreply;
1498 char buf[1024];
1499
1500 g_request = &sreq;
1501 g_reply = &sreply;
1502 g_wreq = &sreply.wreq;
1503
1504 sreply.nucleus_only = true;
1505
1506 log_messages.set_indent_level(1);
1507
1508 MIOFILE mf;
1509 XML_PARSER xp(&mf);
1510 mf.init_file(fin);
1511 const char* p = sreq.parse(xp);
1512 double start_time = dtime();
1513 if (!p){
1514 process_request(code_sign_key);
1515
1516 if ((config.locality_scheduling || config.locality_scheduler_fraction) && !sreply.nucleus_only) {
1517 send_file_deletes();
1518 }
1519 } else {
1520 sprintf(buf, "Error in request message: %s", p);
1521 log_incomplete_request();
1522 sreply.insert_message(buf, "low");
1523 }
1524
1525 if (config.debug_user_messages) {
1526 log_user_messages();
1527 }
1528
1529 sreply.write(fout, sreq);
1530 log_messages.printf(MSG_NORMAL,
1531 "Scheduler ran %.3f seconds\n", dtime()-start_time
1532 );
1533
1534 if (strlen(config.sched_lockfile_dir)) {
1535 unlock_sched();
1536 }
1537 }
1538
1539 const char *BOINC_RCSID_2ac231f9de = "$Id$";
1540