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