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 #include "config.h"
19 #ifdef _USING_FCGI_
20 #include "boinc_fcgi.h"
21 #else
22 #include <cstdio>
23 #endif
24 #include <cstdlib>
25 #include <cstring>
26 #include <string>
27 #include <vector>
28 #include <ctime>
29 #include <cassert>
30 #include <unistd.h>
31 #include <cmath>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34
35
36 #include "boinc_db.h"
37 #include "common_defs.h"
38 #include "crypt.h"
39 #include "error_numbers.h"
40 #include "filesys.h"
41 #include "md5_file.h"
42 #include "parse.h"
43 #include "sched_util.h"
44 #include "str_replace.h"
45 #include "str_util.h"
46 #include "util.h"
47
48 #include "process_input_template.h"
49
50 #include "backend_lib.h"
51
52 using std::string;
53
54 // the random part of output filenames needs to be hard to guess
55 //
56 static struct random_init {
random_initrandom_init57 random_init() {
58 srand48(getpid() + time(0));
59 }
60 } random_init;
61
read_file(FILE * f,char * buf,int len)62 int read_file(FILE* f, char* buf, int len) {
63 int n = fread(buf, 1, len, f);
64 buf[n] = 0;
65 return 0;
66 }
67
read_filename(const char * path,char * buf,int len)68 int read_filename(const char* path, char* buf, int len) {
69 int retval;
70 #ifndef _USING_FCGI_
71 FILE* f = fopen(path, "r");
72 #else
73 FCGI_FILE *f=FCGI::fopen(path, "r");
74 #endif
75 if (!f) return -1;
76 retval = read_file(f, buf, len);
77 fclose(f);
78 return retval;
79 }
80
81 // initialize an about-to-be-created result, given its WU
82 //
initialize_result(DB_RESULT & result,WORKUNIT & wu)83 static void initialize_result(DB_RESULT& result, WORKUNIT& wu) {
84 result.id = 0;
85 result.create_time = time(0);
86 result.workunitid = wu.id;
87 result.size_class = wu.size_class;
88 if (result.size_class < 0) {
89 result.server_state = RESULT_SERVER_STATE_UNSENT;
90 } else {
91 result.server_state = RESULT_SERVER_STATE_INACTIVE;
92 }
93 result.hostid = 0;
94 result.report_deadline = 0;
95 result.sent_time = 0;
96 result.received_time = 0;
97 result.client_state = 0;
98 result.cpu_time = 0;
99 strcpy(result.xml_doc_out, "");
100 strcpy(result.stderr_out, "");
101 result.outcome = RESULT_OUTCOME_INIT;
102 result.file_delete_state = ASSIMILATE_INIT;
103 result.validate_state = VALIDATE_STATE_INIT;
104 result.claimed_credit = 0;
105 result.granted_credit = 0;
106 result.appid = wu.appid;
107 result.priority = wu.priority;
108 result.batch = wu.batch;
109 }
110
create_result_ti(TRANSITIONER_ITEM & ti,char * result_template_filename,char * result_name_suffix,R_RSA_PRIVATE_KEY & key,SCHED_CONFIG & config_loc,char * query_string,int priority_increase)111 int create_result_ti(
112 TRANSITIONER_ITEM& ti,
113 char* result_template_filename,
114 char* result_name_suffix,
115 R_RSA_PRIVATE_KEY& key,
116 SCHED_CONFIG& config_loc,
117 char* query_string,
118 // if nonzero, write value list here; else do insert
119 int priority_increase
120 ) {
121 WORKUNIT wu;
122
123 // copy relevant fields from TRANSITIONER_ITEM to WORKUNIT
124 //
125 safe_strcpy(wu.name, ti.name);
126 wu.id = ti.id;
127 wu.appid = ti.appid;
128 wu.priority = ti.priority;
129 wu.batch = ti.batch;
130 wu.size_class = ti.size_class;
131 return create_result(
132 wu,
133 result_template_filename,
134 result_name_suffix,
135 key,
136 config_loc,
137 query_string,
138 priority_increase
139 );
140 }
141
142 // Create a new result for the given WU.
143 // This is called from:
144 // - the transitioner
145 // - the scheduler (for assigned jobs)
146 //
create_result(WORKUNIT & wu,char * result_template_filename,char * result_name_suffix,R_RSA_PRIVATE_KEY & key,SCHED_CONFIG & config_loc,char * query_string,int priority_increase)147 int create_result(
148 WORKUNIT& wu,
149 char* result_template_filename,
150 char* result_name_suffix,
151 R_RSA_PRIVATE_KEY& key,
152 SCHED_CONFIG& config_loc,
153 char* query_string,
154 // if nonzero, write value list here; else do insert
155 int priority_increase
156 ) {
157 DB_RESULT result;
158 char base_outfile_name[MAXPATHLEN];
159 char result_template[BLOB_SIZE];
160 int retval;
161
162 initialize_result(result, wu);
163 result.random = lrand48();
164
165 result.priority += priority_increase;
166 sprintf(result.name, "%s_%s", wu.name, result_name_suffix);
167 sprintf(base_outfile_name, "%s_r%ld_", result.name, lrand48());
168 retval = read_filename(
169 result_template_filename, result_template, sizeof(result_template)
170 );
171 if (retval) {
172 fprintf(stderr,
173 "Failed to read result template file '%s': %s\n",
174 result_template_filename, boincerror(retval)
175 );
176 return retval;
177 }
178
179 retval = process_result_template(
180 result_template, key, base_outfile_name, config_loc
181 );
182 if (retval) {
183 fprintf(stderr,
184 "process_result_template() error: %s\n", boincerror(retval)
185 );
186 }
187 if (strlen(result_template) > sizeof(result.xml_doc_in)-1) {
188 fprintf(stderr,
189 "result XML doc is too long: %d bytes, max is %d\n",
190 (int)strlen(result_template), (int)sizeof(result.xml_doc_in)-1
191 );
192 return ERR_BUFFER_OVERFLOW;
193 }
194 strlcpy(result.xml_doc_in, result_template, sizeof(result.xml_doc_in));
195
196 if (query_string) {
197 result.db_print_values(query_string);
198 } else {
199 retval = result.insert();
200 if (retval) {
201 fprintf(stderr, "result.insert(): %s\n", boincerror(retval));
202 return retval;
203 }
204 }
205
206 return 0;
207 }
208
209 // make sure a WU's input files are actually there
210 //
check_files(char ** infiles,int ninfiles,SCHED_CONFIG & config_loc)211 int check_files(char** infiles, int ninfiles, SCHED_CONFIG& config_loc) {
212 int i;
213 char path[MAXPATHLEN];
214
215 for (i=0; i<ninfiles; i++) {
216 dir_hier_path(
217 infiles[i], config_loc.download_dir, config_loc.uldl_dir_fanout, path
218 );
219 if (!boinc_file_exists(path)) {
220 return 1;
221 }
222
223 }
224 return 0;
225 }
226
227 // variant where input files are described by a list of names,
228 // for use by work generators
229 //
create_work(DB_WORKUNIT & wu,const char * _wu_template,const char * result_template_filename,const char * result_template_filepath,const char ** infiles,int ninfiles,SCHED_CONFIG & config_loc,const char * command_line,const char * additional_xml,char * query_string)230 int create_work(
231 DB_WORKUNIT& wu,
232 const char* _wu_template,
233 const char* result_template_filename,
234 const char* result_template_filepath,
235 const char** infiles,
236 int ninfiles,
237 SCHED_CONFIG& config_loc,
238 const char* command_line,
239 const char* additional_xml,
240 char* query_string
241 ) {
242 vector<INFILE_DESC> infile_specs(ninfiles);
243 for (int i=0; i<ninfiles; i++) {
244 infile_specs[i].is_remote = false;
245 safe_strcpy(infile_specs[i].name, infiles[i]);
246 }
247 return create_work2(
248 wu,
249 _wu_template,
250 result_template_filename,
251 result_template_filepath,
252 infile_specs,
253 config_loc,
254 command_line,
255 additional_xml,
256 query_string
257 );
258 }
259
260 // variant where input files are described by INFILE_DESCS,
261 // so you can have remote files etc.
262 //
263 // If query_string is present, don't actually create the job;
264 // instead, append to the query string.
265 // The caller is responsible for doing the query.
266 //
create_work2(DB_WORKUNIT & wu,const char * _wu_template,const char * result_template_filename,const char * result_template_filepath,vector<INFILE_DESC> & infiles,SCHED_CONFIG & config_loc,const char * command_line,const char * additional_xml,char * query_string)267 int create_work2(
268 DB_WORKUNIT& wu,
269 const char* _wu_template,
270 const char* result_template_filename,
271 const char* result_template_filepath,
272 vector<INFILE_DESC> &infiles,
273 SCHED_CONFIG& config_loc,
274 const char* command_line,
275 const char* additional_xml,
276 char* query_string
277 ) {
278 int retval;
279 char _result_template[BLOB_SIZE];
280 char wu_template[BLOB_SIZE];
281
282 #if 0
283 retval = check_files(infiles, ninfiles, config_loc);
284 if (retval) {
285 fprintf(stderr, "Missing input file: %s\n", infiles[0]);
286 return -1;
287 }
288 #endif
289
290 safe_strcpy(wu_template, _wu_template);
291 wu.create_time = time(0);
292 retval = process_input_template(
293 wu, wu_template, infiles, config_loc, command_line, additional_xml
294 );
295 if (retval) {
296 fprintf(stderr, "process_input_template(): %s\n", boincerror(retval));
297 return retval;
298 }
299
300 retval = read_filename(
301 result_template_filepath, _result_template, sizeof(_result_template)
302 );
303 if (retval) {
304 fprintf(stderr,
305 "create_work: can't read result template file %s\n",
306 result_template_filepath
307 );
308 return retval;
309 }
310
311 if (strlen(result_template_filename) > sizeof(wu.result_template_file)-1) {
312 fprintf(stderr,
313 "result template filename is too big: %d bytes, max is %d\n",
314 (int)strlen(result_template_filename),
315 (int)sizeof(wu.result_template_file)-1
316 );
317 return ERR_BUFFER_OVERFLOW;
318 }
319 strlcpy(wu.result_template_file, result_template_filename, sizeof(wu.result_template_file));
320
321 if (wu.rsc_fpops_est == 0) {
322 fprintf(stderr, "no rsc_fpops_est given; can't create job\n");
323 return ERR_NO_OPTION;
324 }
325 if (wu.rsc_fpops_bound == 0) {
326 fprintf(stderr, "no rsc_fpops_bound given; can't create job\n");
327 return ERR_NO_OPTION;
328 }
329 if (wu.rsc_disk_bound == 0) {
330 fprintf(stderr, "no rsc_disk_bound given; can't create job\n");
331 return ERR_NO_OPTION;
332 }
333 if (wu.target_nresults == 0) {
334 fprintf(stderr, "no target_nresults given; can't create job\n");
335 return ERR_NO_OPTION;
336 }
337 if (wu.max_error_results == 0) {
338 fprintf(stderr, "no max_error_results given; can't create job\n");
339 return ERR_NO_OPTION;
340 }
341 if (wu.max_total_results == 0) {
342 fprintf(stderr, "no max_total_results given; can't create job\n");
343 return ERR_NO_OPTION;
344 }
345 if (wu.max_success_results == 0) {
346 fprintf(stderr, "no max_success_results given; can't create job\n");
347 return ERR_NO_OPTION;
348 }
349 if (wu.max_success_results > wu.max_total_results) {
350 fprintf(stderr, "max_success_results > max_total_results; can't create job\n");
351 return ERR_INVALID_PARAM;
352 }
353 if (wu.max_error_results > wu.max_total_results) {
354 fprintf(stderr, "max_error_results > max_total_results; can't create job\n");
355 return ERR_INVALID_PARAM;
356 }
357 if (wu.target_nresults > wu.max_success_results) {
358 fprintf(stderr, "target_nresults > max_success_results; can't create job\n");
359 return ERR_INVALID_PARAM;
360 }
361 if (wu.transitioner_flags) {
362 wu.transition_time = INT_MAX;
363 } else {
364 wu.transition_time = time(0);
365 }
366 if (query_string) {
367 wu.db_print_values(query_string);
368 } else if (wu.id) {
369 retval = wu.update();
370 if (retval) {
371 fprintf(stderr,
372 "create_work: workunit.update() %s\n", boincerror(retval)
373 );
374 return retval;
375 }
376 } else {
377 retval = wu.insert();
378 if (retval) {
379 fprintf(stderr,
380 "create_work: workunit.insert() %s\n", boincerror(retval)
381 );
382 return retval;
383 }
384 wu.id = boinc_db.insert_id();
385 }
386
387 return 0;
388 }
389
390 // STUFF RELATED TO FILE UPLOAD/DOWNLOAD
391
get_file_xml(const char * file_name,vector<const char * > urls,double max_nbytes,double report_deadline,bool generate_upload_certificate,R_RSA_PRIVATE_KEY & key,char * out)392 int get_file_xml(
393 const char* file_name, vector<const char*> urls,
394 double max_nbytes,
395 double report_deadline,
396 bool generate_upload_certificate,
397 R_RSA_PRIVATE_KEY& key,
398 char* out
399 ) {
400 char buf[8192];
401 sprintf(out,
402 "<app>\n"
403 " <name>file_xfer</name>\n"
404 "</app>\n"
405 "<app_version>\n"
406 " <app_name>file_xfer</app_name>\n"
407 " <version_num>0</version_num>\n"
408 "</app_version>\n"
409 "<file_info>\n"
410 " <name>%s</name>\n"
411 " <max_nbytes>%.0f</max_nbytes>\n",
412 file_name,
413 max_nbytes
414 );
415 for (unsigned int i=0; i<urls.size(); i++) {
416 sprintf(buf, " <url>%s</url>\n", urls[i]);
417 strcat(out, buf);
418 }
419 sprintf(buf,
420 "</file_info>\n"
421 "<workunit>\n"
422 " <name>upload_%s</name>\n"
423 " <app_name>file_xfer</app_name>\n"
424 "</workunit>\n"
425 "<result>\n"
426 " <wu_name>upload_%s</wu_name>\n"
427 " <name>upload_%s</name>\n"
428 " <file_ref>\n"
429 " <file_name>%s</file_name>\n"
430 " </file_ref>\n"
431 " <report_deadline>%f</report_deadline>\n"
432 "</result>\n",
433 file_name,
434 file_name,
435 file_name,
436 file_name,
437 report_deadline
438 );
439 strcat(out, buf);
440 if (generate_upload_certificate) {
441 add_signatures(out, key);
442 }
443 return 0;
444 }
445
create_get_file_msg(int host_id,const char * file_name,vector<const char * > urls,double max_nbytes,double report_deadline,bool generate_upload_certificate,R_RSA_PRIVATE_KEY & key)446 int create_get_file_msg(
447 int host_id, const char* file_name, vector<const char*> urls,
448 double max_nbytes,
449 double report_deadline,
450 bool generate_upload_certificate,
451 R_RSA_PRIVATE_KEY& key
452 ) {
453 DB_MSG_TO_HOST mth;
454 int retval;
455
456 mth.clear();
457 mth.create_time = time(0);
458 mth.hostid = host_id;
459 strcpy(mth.variety, "file_xfer");
460 mth.handled = false;
461 get_file_xml(
462 file_name, urls, max_nbytes, report_deadline,
463 generate_upload_certificate, key,
464 mth.xml
465 );
466 retval = mth.insert();
467 if (retval) {
468 fprintf(stderr, "msg_to_host.insert(): %s\n", boincerror(retval));
469 return retval;
470 }
471 return 0;
472 }
473
put_file_xml(const char * file_name,vector<const char * > urls,const char * md5,double nbytes,double report_deadline,char * out)474 int put_file_xml(
475 const char* file_name,
476 vector<const char*> urls, const char* md5, double nbytes,
477 double report_deadline,
478 char* out
479 ) {
480 char buf[8192];
481 sprintf(out,
482 "<app>\n"
483 " <name>file_xfer</name>\n"
484 "</app>\n"
485 "<app_version>\n"
486 " <app_name>file_xfer</app_name>\n"
487 " <version_num>0</version_num>\n"
488 "</app_version>\n"
489 "<file_info>\n"
490 " <name>%s</name>\n",
491 file_name
492 );
493 for (unsigned int i=0; i<urls.size(); i++) {
494 sprintf(buf, " <url>%s</url>\n", urls[i]);
495 strcat(out, buf);
496 }
497 sprintf(buf,
498 " <md5_cksum>%s</md5_cksum>\n"
499 " <nbytes>%.0f</nbytes>\n"
500 " <sticky/>\n"
501 "</file_info>\n"
502 "<workunit>\n"
503 " <name>download_%s</name>\n"
504 " <app_name>file_xfer</app_name>\n"
505 " <file_ref>\n"
506 " <file_name>%s</file_name>\n"
507 " </file_ref>\n"
508 "</workunit>\n"
509 "<result>\n"
510 " <wu_name>download_%s</wu_name>\n"
511 " <name>download_%s</name>\n"
512 " <report_deadline>%f</report_deadline>\n"
513 "</result>\n",
514 md5,
515 nbytes,
516 file_name,
517 file_name,
518 file_name,
519 file_name,
520 report_deadline
521 );
522 strcat(out, buf);
523 return 0;
524 }
525
create_put_file_msg(int host_id,const char * file_name,vector<const char * > urls,const char * md5,double nbytes,double report_deadline)526 int create_put_file_msg(
527 int host_id, const char* file_name,
528 vector<const char*> urls, const char* md5, double nbytes,
529 double report_deadline
530 ) {
531 DB_MSG_TO_HOST mth;
532 int retval;
533 mth.clear();
534 mth.create_time = time(0);
535 mth.hostid = host_id;
536 strcpy(mth.variety, "file_xfer");
537 mth.handled = false;
538 put_file_xml(file_name, urls, md5, nbytes, report_deadline, mth.xml);
539 retval = mth.insert();
540 if (retval) {
541 fprintf(stderr, "msg_to_host.insert(): %s\n", boincerror(retval));
542 return retval;
543 }
544 return 0;
545 }
546
delete_file_xml(const char * file_name,char * out)547 int delete_file_xml(const char* file_name, char* out) {
548 sprintf(out, "<delete_file_info>%s</delete_file_info>\n", file_name);
549 return 0;
550 }
551
create_delete_file_msg(int host_id,const char * file_name)552 int create_delete_file_msg(int host_id, const char* file_name) {
553 DB_MSG_TO_HOST mth;
554 int retval;
555 mth.clear();
556 mth.create_time = time(0);
557 mth.hostid = host_id;
558 mth.handled = false;
559 delete_file_xml(file_name, mth.xml);
560 sprintf(mth.variety, "delete_file");
561 retval = mth.insert();
562 if (retval) {
563 fprintf(stderr, "msg_to_host.insert(): %s\n", boincerror(retval));
564 return retval;
565 }
566 return 0;
567 }
568
569 // cancel jobs in a range of workunit IDs
570 //
cancel_jobs(int min_id,int max_id)571 int cancel_jobs(int min_id, int max_id) {
572 DB_WORKUNIT wu;
573 DB_RESULT result;
574 char set_clause[256], where_clause[256];
575 int retval;
576
577 sprintf(set_clause, "server_state=%d, outcome=%d",
578 RESULT_SERVER_STATE_OVER, RESULT_OUTCOME_DIDNT_NEED
579 );
580 sprintf(where_clause, "server_state<=%d and workunitid >=%d and workunitid<= %d",
581 RESULT_SERVER_STATE_UNSENT, min_id, max_id
582 );
583 retval = result.update_fields_noid(set_clause, where_clause);
584 if (retval) return retval;
585
586 sprintf(set_clause, "error_mask=error_mask|%d, transition_time=%d",
587 WU_ERROR_CANCELLED, (int)(time(0))
588 );
589 sprintf(where_clause, "id>=%d and id<=%d", min_id, max_id);
590 retval = wu.update_fields_noid(set_clause, where_clause);
591 if (retval) return retval;
592 return 0;
593 }
594
595 // cancel a particular job
596 //
cancel_job(DB_WORKUNIT & wu)597 int cancel_job(DB_WORKUNIT& wu) {
598 DB_RESULT result;
599 char set_clause[256], where_clause[256];
600 int retval;
601
602 // cancel unsent results
603 //
604 sprintf(set_clause, "server_state=%d, outcome=%d",
605 RESULT_SERVER_STATE_OVER, RESULT_OUTCOME_DIDNT_NEED
606 );
607 sprintf(where_clause, "server_state<=%d and workunitid=%lu",
608 RESULT_SERVER_STATE_UNSENT, wu.id
609 );
610 retval = result.update_fields_noid(set_clause, where_clause);
611 if (retval) return retval;
612
613 // cancel the workunit
614 //
615 sprintf(set_clause, "error_mask=error_mask|%d, transition_time=%d",
616 WU_ERROR_CANCELLED, (int)(time(0))
617 );
618 retval = wu.update_field(set_clause);
619 if (retval) return retval;
620 return 0;
621 }
622
623 // return the sum of user quotas
624 //
get_total_quota(double & total)625 int get_total_quota(double& total) {
626 DB_USER_SUBMIT us;
627
628 total = 0;
629 while (1) {
630 int retval = us.enumerate("");
631 if (retval == ERR_DB_NOT_FOUND) break;
632 if (retval) {
633 return retval;
634 }
635 total += us.quota;
636 }
637 return 0;
638 }
639
640 // return total project FLOPS (based on recent credit)
641 //
get_project_flops(double & total)642 int get_project_flops(double& total) {
643 DB_APP_VERSION av;
644 char buf[256];
645
646 // compute credit per day
647 //
648 sprintf(buf, "where expavg_time > %f", dtime() - 30*86400);
649 total = 0;
650 while (1) {
651 int retval = av.enumerate(buf);
652 if (retval == ERR_DB_NOT_FOUND) break;
653 if (retval) {
654 return retval;
655 }
656 total += av.expavg_credit;
657 }
658 total /= COBBLESTONE_SCALE; // convert to FLOPs per day
659 total /= 86400; // convert to FLOPs per second
660 return 0;
661 }
662
663 // compute delta to user.logical_start_time given the assumption
664 // that user did flop_count FLOPS of computing
665 //
user_priority_delta(DB_USER_SUBMIT & us,double flop_count,double total_quota,double project_flops)666 double user_priority_delta(
667 DB_USER_SUBMIT& us,
668 double flop_count,
669 // this should be wu.rsc_fpops_est * app.min_avg_pfc
670 // to account for systematic errors in rsc_fpops_est
671 double total_quota,
672 double project_flops
673 ) {
674 if (total_quota == 0) return 0;
675 if (project_flops == 0) return 0;
676
677 double runtime = flop_count / project_flops;
678 double share = us.quota / total_quota;
679 #if 0
680 printf(" project flops %f\n", project_flops);
681 printf(" quota %f, total %f, share %f\n", us.quota, total_quota, share);
682 printf(" runtime %f\n", runtime);
683 printf(" delta %f\n", runtime/share);
684 #endif
685 return runtime/share;
686 }
687
688 const char *BOINC_RCSID_b5f8b10eb5 = "$Id$";
689