Passed
Push — master ( c80735...1e228f )
by Vitalii
01:50 queued 55s
created

make_batch_name()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
// This file is part of BOINC.
3
// http://boinc.berkeley.edu
4
// Copyright (C) 2011 University of California
5
//
6
// BOINC is free software; you can redistribute it and/or modify it
7
// under the terms of the GNU Lesser General Public License
8
// as published by the Free Software Foundation,
9
// either version 3 of the License, or (at your option) any later version.
10
//
11
// BOINC is distributed in the hope that it will be useful,
12
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
// See the GNU Lesser General Public License for more details.
15
//
16
// You should have received a copy of the GNU Lesser General Public License
17
// along with BOINC.  If not, see <http://www.gnu.org/licenses/>.
18
19
// Handler for remote job submission.
20
// See https://github.com/BOINC/boinc/wiki/RemoteJobs
21
22
require_once("../inc/boinc_db.inc");
23
require_once("../inc/submit_db.inc");
24
require_once("../inc/xml.inc");
25
require_once("../inc/dir_hier.inc");
26
require_once("../inc/result.inc");
27
require_once("../inc/sandbox.inc");
28
require_once("../inc/submit_util.inc");
29
30
ini_set("memory_limit", "4G");
31
32
function get_wu($name) {
33
    $name = BoincDb::escape_string($name);
34
    $wu = BoincWorkunit::lookup("name='$name'");
35
    if (!$wu) {
36
        log_write("no job named $name was found");
37
        xml_error(-1, "no job named $name was found");
38
    }
39
    return $wu;
40
}
41
42
function get_submit_app($name) {
43
    $name = BoincDb::escape_string($name);
44
    $app = BoincApp::lookup("name='$name'");
45
    if (!$app) {
46
        log_write("no app named $name was found");
47
        xml_error(-1, "no app named $name was found");
48
    }
49
    return $app;
50
}
51
52
// estimate FLOP count for a batch.
53
// If estimates aren't included in the job descriptions,
54
// use what's in the input template
55
//
56
function batch_flop_count($r, $template) {
57
    $x = 0;
58
    $t = 0;
59
    if ($template) {
60
        $t = (double)$template->workunit->rsc_fpops_est;
61
    }
62
    foreach($r->batch->job as $job) {
63
        $y = (double)$job->rsc_fpops_est;
64
        if ($y) {
65
            $x += $y;
66
        } else if ($t) {
67
            $x += $t;
68
        } else {
69
            log_write("no rsc_fpops_est given for job");
70
            xml_error(-1, "no rsc_fpops_est given for job");
71
        }
72
    }
73
    return $x;
74
}
75
76
// estimate project FLOPS based on recent average credit
77
//
78
function project_flops() {
79
    $x = BoincUser::sum("expavg_credit");
80
    if ($x == 0) $x = 200;
81
    $y = 1e9*$x/200;
82
    return $y;
83
}
84
85
function est_elapsed_time($r, $template) {
86
    // crude estimate: batch FLOPs / project FLOPS
87
    //
88
    return batch_flop_count($r, $template) / project_flops();
89
}
90
91
// if batch-level input template filename was given, read it;
92
// else if standard file (app_in) is present, read it;
93
// else return null
94
// Note: input templates may also be given per job
95
//
96
function read_input_template($app, $r) {
97
    if ((isset($r->batch)) && (isset($r->batch->workunit_template_file)) && ($r->batch->workunit_template_file)) {
98
        $path = project_dir() . "/templates/".$r->batch->workunit_template_file;
99
    } else {
100
        $path = project_dir() . "/templates/$app->name"."_in";
101
    }
102
    if (file_exists($path)) {
103
        $x = simplexml_load_file($path);
104
        if (!$x) {
0 ignored issues
show
introduced by
$x is of type SimpleXMLElement, thus it always evaluated to true.
Loading history...
105
            log_write("couldn't parse input template file $path");
106
            xml_error(-1, "couldn't parse input template file $path");
107
        }
108
        return $x;
109
    } else {
110
        return null;
111
    }
112
}
113
114
function check_max_jobs_in_progress($r, $user_submit) {
115
    if (!$user_submit->max_jobs_in_progress) return;
116
    $query = "select count(*) as total from DBNAME.result, DBNAME.batch where batch.user_id=$userid and result.batch = batch.id and result.server_state<".RESULT_SERVER_STATE_OVER;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $userid seems to be never defined.
Loading history...
117
    $db = BoincDb::get();
118
    $n = $db->get_int($query, 'total');
119
    if ($n === false) return;
120
    if ($n + count($r->batch->job) > $user_submit->max_jobs_in_progress) {
121
        log_write("limit on jobs in progress exceeded");
122
        xml_error(-1, "limit on jobs in progress exceeded");
123
    }
124
}
125
126
function estimate_batch($r) {
127
    xml_start_tag("estimate_batch");
128
    $app = get_submit_app((string)($r->batch->app_name));
129
    list($user, $user_submit) = check_remote_submit_permissions($r, $app);
0 ignored issues
show
Comprehensibility Best Practice introduced by
This list assign is not used and could be removed.
Loading history...
130
131
    $template = read_input_template($app, $r);
132
    $e = est_elapsed_time($r, $template);
133
    echo "<seconds>$e</seconds>
134
        </estimate_batch>
135
    ";
136
}
137
138
// Verify that the number of input files for each job agrees with its template
139
// The arg is the batch-level template, if any.
140
// Jobs may have their own templates.
141
//
142
function validate_batch($jobs, $template) {
143
    $i = 0;
144
    $n = count($template->file_info);
145
    foreach($jobs as $job) {
146
        $m = count($job->input_files);
147
        if ($n != $m) {
148
            log_write("wrong # of input files for job $i: need $n, got $m");
149
            xml_error(-1, "wrong # of input files for job $i: need $n, got $m");
150
        }
151
        $i++;
152
    }
153
}
154
155
$fanout = parse_config(get_config(), "<uldl_dir_fanout>");
156
157
// stage a file, and return the physical name
158
//
159
function stage_file($file, $user) {
160
    global $fanout;
161
    $download_dir = parse_config(get_config(), "<download_dir>");
162
163
    switch ($file->mode) {
164
    case "semilocal":
165
    case "local":
166
        // read the file (from disk or network) to get MD5.
167
        // Copy to download hier, using a physical name based on MD5
168
        //
169
        $md5 = md5_file($file->source);
170
        if (!$md5) {
171
            log_write("Can't get MD5 of file $file->source");
172
            xml_error(-1, "Can't get MD5 of file $file->source");
173
        }
174
        $name = job_file_name($md5);
175
        $path = dir_hier_path($name, $download_dir, $fanout);
176
        if (file_exists($path)) return $name;
177
        if (!copy($file->source, $path)) {
178
            log_write("can't copy file from $file->source to $path");
179
            xml_error(-1, "can't copy file from $file->source to $path");
180
        }
181
        return $name;
182
    case "local_staged":
183
        return $file->source;
184
    case 'sandbox':
185
        $name = sandbox_name_to_phys_name($user, $file->source);
186
        if (!$name) {
187
            xml_error(-1, "sandbox link file $file->source not found");
188
        }
189
        $path = dir_hier_path($name, $download_dir, $fanout);
190
        if (file_exists($path)) return $name;
191
        xml_error(-1, "sandbox physical file $file->source not found");
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
192
    case "inline":
193
        $md5 = md5($file->source);
194
        if (!$md5) {
195
            log_write("Can't get MD5 of inline data");
196
            xml_error(-1, "Can't get MD5 of inline data");
197
        }
198
        $name = job_file_name($md5);
199
        $path = dir_hier_path($name, $download_dir, $fanout);
200
        if (file_exists($path)) return $name;
201
        if (!file_put_contents($path, $file->source)) {
202
            log_write("can't write to file $path");
203
            xml_error(-1, "can't write to file $path");
204
        }
205
        return $name;
206
    }
207
    log_write(-1, "unsupported file mode: $file->mode");
0 ignored issues
show
Unused Code introduced by
The call to log_write() has too many arguments starting with 'unsupported file mode: '.$file->mode. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

207
    /** @scrutinizer ignore-call */ 
208
    log_write(-1, "unsupported file mode: $file->mode");

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
208
    xml_error(-1, "unsupported file mode: $file->mode");
209
}
210
211
// stage all the files
212
//
213
function stage_files(&$jobs, $user) {
214
    foreach($jobs as $job) {
215
        foreach ($job->input_files as $file) {
216
            if ($file->mode != "remote") {
217
                $file->name = stage_file($file, $user);
218
            }
219
        }
220
    }
221
}
222
223
// submit a list of jobs with a single create_work command.
224
//
225
function submit_jobs(
226
    $jobs, $job_params, $app, $batch_id, $priority, $app_version_num,
0 ignored issues
show
Coding Style introduced by
Multi-line function declarations must define one parameter per line
Loading history...
227
    $input_template_filename,        // batch-level; can also specify per job
0 ignored issues
show
Coding Style introduced by
Multi-line function declarations must define one parameter per line
Loading history...
228
    $output_template_filename,
229
    $user
230
) {
231
    global $input_templates, $output_templates;
232
233
    // make a string to pass to create_work;
234
    // one line per job
235
    //
236
    $x = "";
237
    foreach($jobs as $job) {
238
        if ($job->name) {
239
            $x .= " --wu_name $job->name";
240
        }
241
        if ($job->command_line) {
242
            $x .= " --command_line \"$job->command_line\"";
243
        }
244
        if ($job->target_team) {
245
            $x .= " --target_team $job->target_team";
246
        } elseif ($job->target_user) {
247
            $x .= " --target_user $job->target_user";
248
        } elseif ($job->target_host) {
249
            $x .= " --target_host $job->target_host";
250
        }
251
        foreach ($job->input_files as $file) {
252
            if ($file->mode == "remote") {
253
                $x .= " --remote_file $file->url $file->nbytes $file->md5";
254
            } else {
255
                $x .= " $file->name";
256
            }
257
        }
258
        if ($job->input_template) {
259
            $f = $input_templates[$job->input_template_xml];
260
            $x .= " --wu_template $f";
261
        }
262
        if ($job->output_template) {
263
            $f = $output_templates[$job->output_template_xml];
264
            $x .= " --result_template $f";
265
        }
266
        if (isset($job->priority)) {
267
            $x .= " --priority $job->priority";
268
        }
269
        $x .= "\n";
270
    }
271
272
    $cmd = "cd " . project_dir() . "; ./bin/create_work --appname $app->name --batch $batch_id";
273
274
    if ($user->seti_id) {
275
        $cmd .= " --target_user $user->id ";
276
    }
277
    if ($priority !== null) {
278
        $cmd .= " --priority $priority";
279
    }
280
    if ($input_template_filename) {
281
        $cmd .= " --wu_template templates/$input_template_filename";
282
    }
283
    if ($output_template_filename) {
284
        $cmd .= " --result_template templates/$output_template_filename";
285
    }
286
    if ($app_version_num) {
287
        $cmd .= " --app_version_num $app_version_num";
288
    }
289
    if ($job_params->rsc_disk_bound) {
290
        $cmd .= " --rsc_disk_bound $job_params->rsc_disk_bound";
291
    }
292
    if ($job_params->rsc_fpops_est) {
293
        $cmd .= " --rsc_fpops_est $job_params->rsc_fpops_est";
294
    }
295
    if ($job_params->rsc_fpops_bound) {
296
        $cmd .= " --rsc_fpops_bound $job_params->rsc_fpops_bound";
297
    }
298
    if ($job_params->rsc_memory_bound) {
299
        $cmd .= " --rsc_memory_bound $job_params->rsc_memory_bound";
300
    }
301
    if ($job_params->delay_bound) {
302
        $cmd .= " --delay_bound $job_params->delay_bound";
303
    }
304
    $cmd .= " --stdin ";
305
306
    // send stdin/stderr to a temp file
307
    $errfile = sprintf('/tmp/create_work_%d.err', getmypid());
308
    $cmd .= sprintf(' >%s 2>&1', $errfile);
309
310
    $h = popen($cmd, "w");
311
    if ($h === false) {
312
        xml_error(-1, "can't run create_work");
313
    }
314
    fwrite($h, $x);
315
    $ret = pclose($h);
316
    if ($ret) {
317
        $err = file_get_contents($errfile);
318
        unlink($errfile);
319
        xml_error(-1, "create_work failed: $err");
320
    }
321
    unlink($errfile);
322
}
323
324
// lists of arrays for job-level templates;
325
// each maps template to filename
326
//
327
$input_templates = array();
328
$output_templates = array();
329
330
// The job specifies an input template.
331
// Check whether the template is already in our map.
332
// If not, write it to a temp file.
333
//
334
function make_input_template($job) {
335
    global $input_templates;
336
    if (!array_key_exists($job->input_template_xml, $input_templates)) {
337
        $f = tempnam("/tmp", "input_template_");
338
        //echo "writing wt $f\n";
339
        file_put_contents($f, $job->input_template_xml);
340
        $input_templates[$job->input_template_xml] = $f;
341
    //} else {
342
    //    echo "dup wu template\n";
343
    }
344
}
345
346
// same for output templates.
347
// A little different because these have to exist for life of job.
348
// Store them in templates/tmp/, with content-based filenames
349
//
350
function make_output_template($job) {
351
    global $output_templates;
352
    if (!array_key_exists($job->output_template_xml, $output_templates)) {
353
        $m = md5($job->output_template_xml);
354
        $filename = "templates/tmp/$m";
355
        $path = "../../$filename";
356
        if (!file_exists($filename)) {
357
            @mkdir("../../templates/tmp");
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

357
            /** @scrutinizer ignore-unhandled */ @mkdir("../../templates/tmp");

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
358
            file_put_contents($path, $job->output_template_xml);
359
        }
360
        $output_templates[$job->output_template_xml] = $filename;
361
    //} else {
362
    //    echo "dup result template\n";
363
    }
364
}
365
366
// delete per-job WU templates after creating jobs.
367
// (we can't delete result templates)
368
//
369
function delete_input_templates() {
370
    global $input_templates;
371
    foreach ($input_templates as $t => $f) {
372
        unlink($f);
373
    }
374
}
375
376
// convert job list from XML nodes to our own objects
377
//
378
function xml_get_jobs($r) {
379
    $jobs = array();
380
    foreach($r->batch->job as $j) {
381
        $job = new StdClass;
382
        $job->input_files = array();
383
        $job->command_line = (string)$j->command_line;
384
        $job->target_team = (int)$j->target_team;
385
        $job->target_user = (int)$j->target_user;
386
        $job->target_host = (int)$j->target_host;
387
        $job->name = (string)$j->name;
388
        $job->rsc_fpops_est = (double)$j->rsc_fpops_est;
389
        $job->input_template = null;
390
        if ($j->input_template) {
391
            $job->input_template = $j->input_template;
392
            $job->input_template_xml = $j->input_template->asXML();
393
        }
394
        $job->output_template = null;
395
        if ($j->output_template) {
396
            $job->output_template = $j->output_template;
397
            $job->output_template_xml = $j->output_template->asXML();
398
        }
399
        foreach ($j->input_file as $f) {
400
            $file = new StdClass;
401
            $file->mode = (string)$f->mode;
402
            if ($file->mode == "remote") {
403
                $file->url = (string)$f->url;
404
                $file->nbytes = (double)$f->nbytes;
405
                $file->md5 = (string)$f->md5;
406
            } else {
407
                $file->source = (string)$f->source;
408
            }
409
            $job->input_files[] = $file;
410
        }
411
        if (isset($j->priority)) {
412
            $job->priority = (int)$j->priority;
413
        }
414
        $jobs[] = $job;
415
        if ($job->input_template) {
416
            make_input_template($job);
417
        }
418
        if ($job->output_template) {
419
            make_output_template($job);
420
        }
421
    }
422
    return $jobs;
423
}
424
425
// - compute batch FLOP count
426
// - run adjust_user_priorities to increment user_submit.logical_start_time
427
// - return that (use as batch logical end time and job priority)
428
//
429
function logical_end_time($r, $jobs, $user, $app) {
430
    $total_flops = 0;
431
    foreach($jobs as $job) {
432
        //print_r($job);
433
        if ($job->rsc_fpops_est) {
434
            $total_flops += $job->rsc_fpops_est;
435
        } else if ($job->input_template && $job->input_template->workunit->rsc_fpops_est) {
436
            $total_flops += (double) $job->input_template->workunit->rsc_fpops_est;
437
        } else if ($r->batch->job_params->rsc_fpops_est) {
438
            $total_flops += (double) $r->batch->job_params->rsc_fpops_est;
439
        } else {
440
            $x = (double) $template->workunit->rsc_fpops_est;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $template seems to be never defined.
Loading history...
441
            if ($x) {
442
                $total_flops += $x;
443
            } else {
444
                xml_error(-1, "no rsc_fpops_est given");
445
            }
446
        }
447
    }
448
    $cmd = "cd " . project_dir() . "/bin; ./adjust_user_priority --user $user->id --flops $total_flops --app $app->name";
449
    $x = exec($cmd);
450
    if (!is_numeric($x) || (double)$x == 0) {
451
        xml_error(-1, "$cmd returned $x");
452
    }
453
    return (double)$x;
454
}
455
456
function make_batch_name($user, $app) {
457
    return sprintf('%s:%s:%s', $user->name, $app->name, date(DATE_RFC2822));
458
}
459
460
// $r is a simplexml object encoding the request message
461
//
462
function submit_batch($r) {
463
    xml_start_tag("submit_batch");
464
    $app = get_submit_app((string)($r->batch->app_name));
465
    list($user, $user_submit) = check_remote_submit_permissions($r, $app);
466
    $jobs = xml_get_jobs($r);
467
    $template = read_input_template($app, $r);
468
    if ($template) {
469
        validate_batch($jobs, $template);
470
    }
471
    stage_files($jobs, $user);
472
    $njobs = count($jobs);
473
    $now = time();
474
    $app_version_num = (int)($r->batch->app_version_num);
475
476
    // batch may or may not already exist.
477
    // If it does, make sure it's owned by this user
478
    //
479
    $batch_id = (int)($r->batch->batch_id);
480
    if ($batch_id) {
481
        $batch = BoincBatch::lookup_id($batch_id);
482
        if (!$batch) {
483
            log_write("not batch $batch_id");
484
            xml_error(-1, "no batch $batch_id");
485
        }
486
        if ($batch->user_id != $user->id) {
487
            log_write("not owner of batch");
488
            xml_error(-1, "not owner of batch");
489
        }
490
        if ($batch->state != BATCH_STATE_INIT) {
491
            log_write("batch not in init state");
492
            xml_error(-1, "batch not in init state");
493
        }
494
    }
495
496
    // compute a priority for the jobs
497
    //
498
    $priority = null;
499
    $let = 0;
500
    if ($r->batch->allocation_priority) {
501
        $let = logical_end_time($r, $jobs, $user, $app);
502
        $priority = -(int)$let;
503
    } else if (isset($r->batch->priority)) {
504
        $priority = (int)$r->batch->priority;
505
    }
506
507
    if ($batch_id) {
508
        $ret = $batch->update("njobs=$njobs, logical_end_time=$let");
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $batch does not seem to be defined for all execution paths leading up to this point.
Loading history...
509
        if (!$ret) {
510
            log_write("batch update to njobs failed");
511
            xml_error(-1, "batch->update() failed");
512
        }
513
        log_write("adding jobs to existing batch $batch_id");
514
    } else {
515
        $batch_name = (string)($r->batch->batch_name);
516
        if (!$batch_name) {
517
            $batch_name = make_batch_name($user, $app);
518
        }
519
        $batch_name = BoincDb::escape_string($batch_name);
520
        $state = BATCH_STATE_INIT;
521
        $batch_id = BoincBatch::insert(
522
            "(user_id, create_time, logical_start_time, logical_end_time, est_completion_time, njobs, fraction_done, nerror_jobs, state, completion_time, credit_estimate, credit_canonical, credit_total, name, app_id, project_state, description, expire_time) values ($user->id, $now, 0, $let, 0, $njobs, 0, 0, $state, 0, 0, 0, 0, '$batch_name', $app->id, 0, '', 0)"
523
        );
524
        if (!$batch_id) {
525
            log_write("can't create batch");
526
            xml_error(-1, "Can't create batch: ".BoincDb::error());
527
        }
528
        $batch = BoincBatch::lookup_id($batch_id);
529
        log_write("created batch $batch_id");
530
    }
531
532
    $job_params = new StdClass;
533
    $job_params->rsc_disk_bound = (double) $r->batch->job_params->rsc_disk_bound;
534
    $job_params->rsc_fpops_est = (double) $r->batch->job_params->rsc_fpops_est;
535
    $job_params->rsc_fpops_bound = (double) $r->batch->job_params->rsc_fpops_bound;
536
    $job_params->rsc_memory_bound = (double) $r->batch->job_params->rsc_memory_bound;
537
    $job_params->delay_bound = (double) $r->batch->job_params->delay_bound;
538
        // could add quorum-related stuff
539
540
    $input_template_filename = (string) $r->batch->input_template_filename;
541
    $output_template_filename = (string) $r->batch->output_template_filename;
542
        // possibly empty
543
544
    submit_jobs(
545
        $jobs, $job_params, $app, $batch_id, $priority, $app_version_num,
546
        $input_template_filename,
547
        $output_template_filename,
548
        $user
549
    );
550
551
    // set state to IN_PROGRESS only after creating jobs;
552
    // otherwise something else might flag batch as COMPLETE
553
    //
554
    $ret = $batch->update("state= ".BATCH_STATE_IN_PROGRESS);
555
    if (!$ret) {
556
        log_write("batch update to IN_PROGRESS failed");
557
        xml_error(-1, "batch->update() failed");
558
    }
559
    log_write("updated batch state to IN_PROGRESS");
560
561
    echo "<batch_id>$batch_id</batch_id>
562
        </submit_batch>
563
    ";
564
565
    delete_input_templates();
566
}
567
568
function create_batch($r) {
569
    xml_start_tag("create_batch");
570
    $app = get_submit_app((string)($r->app_name));
571
    list($user, $user_submit) = check_remote_submit_permissions($r, $app);
572
    $now = time();
573
    $batch_name = (string)($r->batch_name);
574
    if (!$batch_name) {
575
        $batch_name = make_batch_name($user, $app);
576
    }
577
    $batch_name = BoincDb::escape_string($batch_name);
578
    $expire_time = (double)($r->expire_time);
579
    $state = BATCH_STATE_INIT;
580
    $batch_id = BoincBatch::insert(
581
        "(user_id, create_time, logical_start_time, logical_end_time, est_completion_time, njobs, fraction_done, nerror_jobs, state, completion_time, credit_estimate, credit_canonical, credit_total, name, app_id, project_state, description, expire_time) values ($user->id, $now, 0, 0, 0, 0, 0, 0, $state, 0, 0, 0, 0, '$batch_name', $app->id, 0, '', $expire_time)"
582
    );
583
    if (!$batch_id) {
584
        log_write("Can't create batch: ".BoincDb::error());
585
        xml_error(-1, "Can't create batch: ".BoincDb::error());
586
    }
587
    echo "<batch_id>$batch_id</batch_id>
588
        </create_batch>
589
    ";
590
}
591
592
function print_batch_params($batch, $get_cpu_time) {
593
    $app = BoincApp::lookup_id($batch->app_id);
594
    if (!$app) $app->name = "none";
595
    echo "
596
        <id>$batch->id</id>
597
        <create_time>$batch->create_time</create_time>
598
        <expire_time>$batch->expire_time</expire_time>
599
        <est_completion_time>$batch->est_completion_time</est_completion_time>
600
        <njobs>$batch->njobs</njobs>
601
        <fraction_done>$batch->fraction_done</fraction_done>
602
        <nerror_jobs>$batch->nerror_jobs</nerror_jobs>
603
        <state>$batch->state</state>
604
        <completion_time>$batch->completion_time</completion_time>
605
        <credit_estimate>$batch->credit_estimate</credit_estimate>
606
        <credit_canonical>$batch->credit_canonical</credit_canonical>
607
        <name>$batch->name</name>
608
        <app_name>$app->name</app_name>
609
";
610
    if ($get_cpu_time) {
611
        echo "        <total_cpu_time>".$batch->get_cpu_time()."</total_cpu_time>\n";
612
    }
613
}
614
615
function query_batches($r) {
616
    xml_start_tag("query_batches");
617
    list($user, $user_submit) = check_remote_submit_permissions($r, null);
618
    $batches = BoincBatch::enum("user_id = $user->id");
619
    $get_cpu_time = (int)($r->get_cpu_time);
620
    foreach ($batches as $batch) {
621
        if ($batch->state == BATCH_STATE_RETIRED) continue;
622
        if ($batch->state < BATCH_STATE_COMPLETE) {
623
            $wus = BoincWorkunit::enum("batch = $batch->id");
624
            $batch = get_batch_params($batch, $wus);
625
        }
626
        echo "    <batch>\n";
627
        print_batch_params($batch, $get_cpu_time);
628
        echo "   </batch>\n";
629
    }
630
    echo "</query_batches>\n";
631
}
632
633
function n_outfiles($wu) {
634
    $path = project_dir() . "/$wu->output_template_filename";
635
    $r = simplexml_load_file($path);
636
    return count($r->file_info);
637
}
638
639
// show status of job.
640
// done:
641
// unsent:
642
// in_progress:
643
// error:
644
645
function show_job_details($wu) {
646
    if ($wu->error_mask & WU_ERROR_COULDNT_SEND_RESULT) {
647
        echo "   <error>couldnt_send_result</error>\n";
648
    }
649
    if ($wu->error_mask & WU_ERROR_TOO_MANY_ERROR_RESULTS) {
650
        echo "   <error>too_many_error_results</error>\n";
651
    }
652
    if ($wu->error_mask & WU_ERROR_TOO_MANY_SUCCESS_RESULTS) {
653
        echo "   <error>too_many_success_results</error>\n";
654
    }
655
    if ($wu->error_mask & WU_ERROR_TOO_MANY_TOTAL_RESULTS) {
656
        echo "   <error>too_many_total_results</error>\n";
657
    }
658
    if ($wu->error_mask & WU_ERROR_CANCELLED) {
659
        echo "   <error>cancelled</error>\n";
660
    }
661
    if ($wu->error_mask & WU_ERROR_NO_CANONICAL_RESULT) {
662
        echo "   <error>no_canonical_result</error>\n";
663
    }
664
    $results = BoincResult::enum("workunitid=$wu->id");
665
    $in_progress = 0;
666
    foreach ($results as $r) {
667
        switch ($r->server_state) {
668
        case RESULT_SERVER_STATE_IN_PROGRESS:
669
            $in_progress++;
670
            break;
671
        }
672
        if ($wu->error_mask && ($r->outcome == RESULT_OUTCOME_CLIENT_ERROR)) {
673
            echo "            <exit_status>$r->exit_status</exit_status>\n";
674
        }
675
        if ($r->id == $wu->canonical_resultid) {
676
            echo "            <cpu_time>$r->cpu_time</cpu_time>\n";
677
        }
678
    }
679
    if ($wu->error_mask) {
680
        echo "            <status>error</status>\n";
681
        return;
682
    }
683
684
    if ($wu->canonical_resultid) {
685
        echo "            <status>done</status>\n";
686
    } else {
687
        if ($in_progress > 0) {
688
            echo "            <status>in_progress</status>\n";
689
        } else {
690
            echo "            <status>queued</status>\n";
691
        }
692
    }
693
}
694
695
// return a batch specified by the command, using either ID or name
696
//
697
function get_batch($r) {
698
    $batch = NULL;
699
    if (!empty($r->batch_id)) {
700
        $batch_id = (int)($r->batch_id);
701
        $batch = BoincBatch::lookup_id($batch_id);
702
    } else if (!empty($r->batch_name)) {
703
        $batch_name = (string)($r->batch_name);
704
        $batch_name = BoincDb::escape_string($batch_name);
705
        $batch = BoincBatch::lookup_name($batch_name);
706
    } else {
707
        log_write("batch not specified");
708
        xml_error(-1, "batch not specified");
709
    }
710
    if (!$batch) {
711
        log_write("no such batch");
712
        xml_error(-1, "no such batch");
713
    }
714
    return $batch;
715
}
716
717
function query_batch($r) {
718
    xml_start_tag("query_batch");
719
    list($user, $user_submit) = check_remote_submit_permissions($r, null);
720
    $batch = get_batch($r);
721
    if ($batch->user_id != $user->id) {
722
        log_write("not owner of batch");
723
        xml_error(-1, "not owner of batch");
724
    }
725
726
    $wus = BoincWorkunit::enum("batch = $batch->id", "order by id");
0 ignored issues
show
Unused Code introduced by
The call to BoincWorkunit::enum() has too many arguments starting with 'order by id'. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

726
    /** @scrutinizer ignore-call */ 
727
    $wus = BoincWorkunit::enum("batch = $batch->id", "order by id");

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
727
    $batch = get_batch_params($batch, $wus);
728
    $get_cpu_time = (int)($r->get_cpu_time);
729
    $get_job_details = (int)($r->get_job_details);
730
    print_batch_params($batch, $get_cpu_time);
731
    foreach ($wus as $wu) {
732
        echo "        <job>
733
        <id>$wu->id</id>
734
        <name>$wu->name</name>
735
        <canonical_instance_id>$wu->canonical_resultid</canonical_instance_id>
736
";
737
        // does anyone need this?
738
        //
739
        if (0) {
740
            $n_outfiles = n_outfiles($wu);
741
            echo "     <n_outfiles>$n_outfiles</n_outfiles>\n";
742
        }
743
744
        if ($get_job_details) {
745
            show_job_details($wu);
746
        }
747
        echo "        </job>\n";
748
    }
749
    echo "</query_batch>\n";
750
}
751
752
function results_sent($wu) {
753
    return BoincResult::count("workunitid=$wu->id and sent_time>0");
754
}
755
756
// variant for Condor, which doesn't care about job instances
757
// and refers to batches by name
758
//
759
function query_batch2($r) {
760
    xml_start_tag("query_batch2");
761
    list($user, $user_submit) = check_remote_submit_permissions($r, null);
762
    $batch_names = $r->batch_name;
763
    $batches = array();
764
    foreach ($batch_names as $b) {
765
        $batch_name = (string)$b;
766
        $batch_name = BoincDb::escape_string($batch_name);
767
        $batch = BoincBatch::lookup_name($batch_name);
768
        if (!$batch) {
769
            log_write("no batch named $batch_name");
770
            xml_error(-1, "no batch named $batch_name");
771
        }
772
        if ($batch->user_id != $user->id) {
773
            log_write("not owner of $batch_name");
774
            xml_error(-1, "not owner of $batch_name");
775
        }
776
        $batches[] = $batch;
777
    }
778
779
    $min_mod_time = (double)$r->min_mod_time;
780
    if ($min_mod_time) {
781
        $mod_time_clause = "and mod_time > FROM_UNIXTIME($min_mod_time)";
782
    } else {
783
        $mod_time_clause = "";
784
    }
785
786
    $t = dtime();
787
    echo "<server_time>$t</server_time>\n";
788
    foreach ($batches as $batch) {
789
        $wus = BoincWorkunit::enum("batch = $batch->id $mod_time_clause");
790
        echo "   <batch_size>".count($wus)."</batch_size>\n";
791
792
        // job status is:
793
        // DONE if done
794
        // ERROR if error
795
        // IN_PROGRESS if at least one instance sent
796
        // QUEUED if no instances sent
797
        foreach ($wus as $wu) {
798
            if ($wu->canonical_resultid) {
799
                $status = "DONE";
800
            } else if ($wu->error_mask) {
801
                $status = "ERROR";
802
            } else if (results_sent($wu) > 0) {
803
                $status = "IN_PROGRESS";
804
            } else {
805
                $status = "UNSENT";
806
            }
807
            echo
808
"    <job>
809
        <job_name>$wu->name</job_name>
810
        <status>$status</status>
811
    </job>
812
";
813
        }
814
    }
815
    echo "</query_batch2>\n";
816
}
817
818
function query_job($r) {
819
    xml_start_tag("query_job");
820
    list($user, $user_submit) = check_remote_submit_permissions($r, null);
821
    $job_id = (int)($r->job_id);
822
    $wu = BoincWorkunit::lookup_id($job_id);
823
    if (!$wu) {
824
        log_write("no such job");
825
        xml_error(-1, "no such job");
826
    }
827
    $batch = BoincBatch::lookup_id($wu->batch);
828
    if ($batch->user_id != $user->id) {
829
        log_write("not owner");
830
        xml_error(-1, "not owner");
831
    }
832
    $results = BoincResult::enum("workunitid=$job_id");
833
    foreach ($results as $result) {
834
        echo "    <instance>
835
        <name>$result->name</name>
836
        <id>$result->id</id>
837
        <state>".state_string($result)."</state>
838
";
839
        if ($result->server_state == 5) {   // over?
840
            $paths = get_outfile_paths($result);
841
            foreach($paths as $path) {
842
                if (is_file($path)) {
843
                    $size = filesize($path);
844
                    echo "        <outfile>
845
            <size>$size</size>
846
        </outfile>
847
";
848
                }
849
            }
850
        }
851
        echo "</instance>\n";
852
    }
853
    echo "</query_job>\n";
854
}
855
856
// the following for Condor.
857
// If the job has a canonical instance, return info about it.
858
// Otherwise find an instance that completed
859
// (possibly crashed) and return its info.
860
//
861
function query_completed_job($r) {
862
    xml_start_tag("query_completed_job");
863
    list($user, $user_submit) = check_remote_submit_permissions($r, null);
864
    $job_name = (string)($r->job_name);
865
    $job_name = BoincDb::escape_string($job_name);
866
    $wu = BoincWorkunit::lookup("name='$job_name'");
867
    if (!$wu) {
868
        log_write("no such job");
869
        xml_error(-1, "no such job");
870
    }
871
    $batch = BoincBatch::lookup_id($wu->batch);
872
    if ($batch->user_id != $user->id) {
873
        log_write("not owner");
874
        xml_error(-1, "not owner");
875
    }
876
877
    echo "<completed_job>\n";
878
    echo "   <error_mask>$wu->error_mask</error_mask>\n";
879
    if ($wu->canonical_resultid) {
880
        $result = BoincResult::lookup_id($wu->canonical_resultid);
881
        echo "   <canonical_resultid>$wu->canonical_resultid</canonical_resultid>\n";
882
    } else {
883
        $results = BoincResult::enum("workunitid=$job_id");
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $job_id seems to be never defined.
Loading history...
884
        foreach ($results as $r) {
0 ignored issues
show
introduced by
$r is overwriting one of the parameters of this function.
Loading history...
885
            switch($r->outcome) {
886
            case 1:
887
            case 3:
888
            case 6:
889
                $result = $r;
890
                break;
891
            }
892
        }
893
        if ($result) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.
Loading history...
894
            echo "   <error_resultid>$result->id</error_resultid>\n";
895
        }
896
    }
897
    if ($result) {
898
        echo "   <exit_status>$result->exit_status</exit_status>\n";
899
        echo "   <elapsed_time>$result->elapsed_time</elapsed_time>\n";
900
        echo "   <cpu_time>$result->cpu_time</cpu_time>\n";
901
        echo "   <stderr_out><![CDATA[\n";
902
        echo htmlspecialchars($result->stderr_out);
903
        echo "   ]]></stderr_out>\n";
904
    }
905
    echo "</completed_job>
906
        </query_completed_job>
907
    ";
908
}
909
910
function handle_abort_batch($r) {
911
    xml_start_tag("abort_batch");
912
    list($user, $user_submit) = check_remote_submit_permissions($r, null);
913
    $batch = get_batch($r);
914
    if ($batch->user_id != $user->id) {
915
        log_write("not owner");
916
        xml_error(-1, "not owner");
917
    }
918
    abort_batch($batch);
919
    echo "<success>1</success>
920
        </abort_batch>
921
    ";
922
}
923
924
// handle the abort of jobs possibly belonging to different batches
925
//
926
function handle_abort_jobs($r) {
927
    xml_start_tag("abort_jobs");
928
    list($user, $user_submit) = check_remote_submit_permissions($r, null);
929
    $batch = null;
930
    foreach ($r->job_name as $job_name) {
931
        $job_name = BoincDb::escape_string($job_name);
932
        $wu = BoincWorkunit::lookup("name='$job_name'");
933
        if (!$wu) {
934
            log_write("no job $job_name");
935
            xml_error(-1, "no job $job_name");
936
        }
937
        if (!$wu->batch) {
938
            log_write("job $job_name is not part of a batch");
939
            xml_error(-1, "job $job_name is not part of a batch");
940
        }
941
        if (!$batch || $wu->batch != $batch->id) {
942
            $batch = BoincBatch::lookup_id($wu->batch);
943
        }
944
        if (!$batch || $batch->user_id != $user->id) {
945
            log_write("not owner of batch");
946
            xml_error(-1, "not owner of batch");
947
        }
948
        echo "<aborted $job_name>\n";
949
        abort_workunit($wu);
950
    }
951
    echo "<success>1</success>
952
        </abort_jobs>
953
    ";
954
}
955
956
function handle_retire_batch($r) {
957
    xml_start_tag("retire_batch");
958
    list($user, $user_submit) = check_remote_submit_permissions($r, null);
959
    $batch = get_batch($r);
960
    if ($batch->user_id != $user->id) {
961
        log_write("not owner of batch");
962
        xml_error(-1, "not owner of batch");
963
    }
964
    retire_batch($batch);
965
    echo "<success>1</success>
966
        </retire_batch>
967
    ";
968
}
969
970
function handle_set_expire_time($r) {
971
    xml_start_tag("set_expire_time");
972
    list($user, $user_submit) = check_remote_submit_permissions($r, null);
973
    $batch = get_batch($r);
974
    if ($batch->user_id != $user->id) {
975
        log_write("not owner of batch");
976
        xml_error(-1, "not owner of batch");
977
    }
978
    $expire_time = (double)($r->expire_time);
979
    if ($batch->update("expire_time=$expire_time")) {
980
        echo "<success>1</success>";
981
    } else {
982
        log_write("batch update failed");
983
        xml_error(-1, "batch update failed");
984
    }
985
    echo "</set_expire_time>\n";
986
}
987
988
function get_templates($r) {
989
    xml_start_tag("get_templates");
990
    $app_name = (string)($r->app_name);
991
    if ($app_name) {
992
        $app = get_submit_app($app_name);
993
    } else {
994
        $job_name = (string)($r->job_name);
995
        $wu = get_wu($job_name);
996
        $app = BoincApp::lookup_id($wu->appid);
997
    }
998
999
    list($user, $user_submit) = check_remote_submit_permissions($r, $app);
0 ignored issues
show
Comprehensibility Best Practice introduced by
This list assign is not used and could be removed.
Loading history...
1000
    $in = file_get_contents(project_dir() . "/templates/".$app->name."_in");
1001
    $out = file_get_contents(project_dir() . "/templates/".$app->name."_out");
1002
    if ($in === false || $out === false) {
1003
        log_write("template file missing");
1004
        xml_error(-1, "template file missing");
1005
    }
1006
    echo "<templates>\n$in\n$out\n</templates>
1007
        </get_templates>
1008
    ";
1009
}
1010
1011
function ping($r) {
1012
    xml_start_tag("ping");
1013
    BoincDb::get();     // errors out if DB down or web disabled
1014
    echo "<success>1</success>
1015
        </ping>
1016
    ";
1017
}
1018
1019
if (0) {
1020
$r = simplexml_load_string("
1021
<query_batch2>
1022
    <authenticator>x</authenticator>
1023
    <batch_name>batch_30</batch_name>
1024
    <batch_name>batch_31</batch_name>
1025
</query_batch2>
1026
");
1027
query_batch2($r);
1028
exit;
1029
}
1030
1031
if (0) {
1032
$r = simplexml_load_string("
1033
<query_batch>
1034
    <authenticator>x</authenticator>
1035
    <batch_id>54</batch_id>
1036
</query_batch>
1037
");
1038
query_batch($r);
1039
exit;
1040
}
1041
1042
if (0) {
1043
$r = simplexml_load_string("
1044
<query_job>
1045
    <authenticator>x</authenticator>
1046
    <job_id>312173</job_id>
1047
</query_job>
1048
");
1049
query_job($r);
1050
exit;
1051
}
1052
1053
if (0) {
1054
$r = simplexml_load_string("
1055
<estimate_batch>
1056
    <authenticator>x</authenticator>
1057
    <batch>
1058
    <app_name>remote_test</app_name>
1059
    <batch_name>Aug 6 batch 4</batch_name>
1060
    <job>
1061
        <rsc_fpops_est>19000000000</rsc_fpops_est>
1062
        <command_line>--t 19</command_line>
1063
        <input_file>
1064
            <mode>remote</mode>
1065
            <source>https://google.com/</source>
1066
        </input_file>
1067
    </job>
1068
    </batch>
1069
</estimate_batch>
1070
");
1071
estimate_batch($r);
1072
exit;
1073
}
1074
1075
// optionally write request message (XML) to log file
1076
//
1077
$request_log = parse_config(get_config(), "<remote_submit_request_log>");
1078
if ($request_log) {
1079
    $log_dir = parse_config(get_config(), "<log_dir>");
1080
    $request_log = $log_dir . "/" . $request_log;
1081
    if ($file = fopen($request_log, "a")) {
1082
        fwrite($file, "\n<submit_rpc_handler date=\"" . date(DATE_ATOM) . "\">\n" . $_POST['request'] . "\n</submit_rpc_handler>\n");
1083
        fclose($file);
1084
    }
1085
}
1086
1087
xml_header();
1088
if (0) {
1089
    $req = file_get_contents("submit_req.xml");
1090
} else {
1091
    $req = $_POST['request'];
1092
}
1093
$r = simplexml_load_string($req);
1094
if (!$r) {
0 ignored issues
show
introduced by
$r is of type SimpleXMLElement, thus it always evaluated to true.
Loading history...
1095
    log_write("----- RPC request: can't parse request message: $req");
1096
    xml_error(-1, "can't parse request message: $req");
1097
}
1098
1099
log_write("----- Handling RPC; command ".$r->getName());
1100
1101
switch ($r->getName()) {
1102
    case 'abort_batch': handle_abort_batch($r); break;
1103
    case 'abort_jobs': handle_abort_jobs($r); break;
1104
    case 'create_batch': create_batch($r); break;
1105
    case 'estimate_batch': estimate_batch($r); break;
1106
    case 'get_templates': get_templates($r); break;
1107
    case 'ping': ping($r); break;
1108
    case 'query_batch': query_batch($r); break;
1109
    case 'query_batch2': query_batch2($r); break;
1110
    case 'query_batches': query_batches($r); break;
1111
    case 'query_job': query_job($r); break;
1112
    case 'query_completed_job': query_completed_job($r); break;
1113
    case 'retire_batch': handle_retire_batch($r); break;
1114
    case 'set_expire_time': handle_set_expire_time($r); break;
1115
    case 'submit_batch': submit_batch($r); break;
0 ignored issues
show
Bug introduced by
The call to submit_batch() has too few arguments starting with app. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1115
    case 'submit_batch': /** @scrutinizer ignore-call */ submit_batch($r); break;

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1116
    default:
1117
        log_write("bad command");
1118
        xml_error(-1, "bad command: ".$r->getName());
1119
        break;
1120
}
1121
1122
log_write("RPC done");
1123
1124
?>
1125