submit_jobs()   F
last analyzed

Complexity

Conditions 24
Paths > 20000

Size

Total Lines 100
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 24
eloc 61
nc 1576960
nop 9
dl 0
loc 100
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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, "job not found: ".htmlspecialchars($name));
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, "app not found: ".htmlspecialchars($name));
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 ".htmlspecialchars($path));
107
        }
108
        return $x;
109
    } else {
110
        return null;
111
    }
112
}
113
114
// if this batch would exceed user job limit, error out
115
//
116
function check_max_jobs_in_progress($r, $user) {
117
    $us = BoincUserSubmit::lookup_userid($user->id);
118
    if (!$us->max_jobs_in_progress) return;
119
    $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 does not exist. Did you maybe mean $user?
Loading history...
120
    $db = BoincDb::get();
121
    $n = $db->get_int($query, 'total');
122
    if ($n === false) return;
123
    if ($n + count($r->batch->job) > $us->max_jobs_in_progress) {
124
        log_write("limit on jobs in progress exceeded");
125
        xml_error(-1, "limit on jobs in progress exceeded");
126
    }
127
}
128
129
function estimate_batch($r) {
130
    xml_start_tag("estimate_batch");
131
    $app = get_submit_app((string)($r->batch->app_name));
132
    $user = check_remote_submit_permissions($r, $app);
133
134
    $template = read_input_template($app, $r);
135
    $e = est_elapsed_time($r, $template);
136
    echo "<seconds>$e</seconds>
137
        </estimate_batch>
138
    ";
139
}
140
141
// Verify that the number of input files for each job agrees with its template
142
// The arg is the batch-level template, if any.
143
// Jobs may have their own templates.
144
//
145
function validate_batch($jobs, $template) {
146
    $i = 0;
147
    $n = count($template->file_info);
148
    foreach($jobs as $job) {
149
        $m = count($job->input_files);
150
        if ($n != $m) {
151
            log_write("wrong # of input files for job $i: need $n, got $m");
152
            xml_error(-1, "wrong # of input files for job $i: need $n, got $m");
153
        }
154
        $i++;
155
    }
156
}
157
158
$fanout = parse_config(get_config(), "<uldl_dir_fanout>");
159
160
// stage a file, and return the physical name
161
//
162
function stage_file($file, $user) {
163
    global $fanout;
164
    $download_dir = parse_config(get_config(), "<download_dir>");
165
166
    switch ($file->mode) {
167
    case "semilocal":
168
    case "local":
169
        // read the file (from disk or network) to get MD5.
170
        // Copy to download hier, using a physical name based on MD5
171
        //
172
        $md5 = md5_file($file->source);
173
        if (!$md5) {
174
            log_write("Can't get MD5 of file $file->source");
175
            xml_error(-1, "Can't get MD5 of file $file->source");
176
        }
177
        $name = job_file_name($md5);
178
        $path = dir_hier_path($name, $download_dir, $fanout);
179
        if (file_exists($path)) return $name;
180
        if (!copy($file->source, $path)) {
181
            log_write("can't copy file from $file->source to $path");
182
            xml_error(-1, "can't copy file from $file->source to $path");
183
        }
184
        return $name;
185
    case "local_staged":
186
        return $file->source;
187
    case 'sandbox':
188
        [$md5, $size] = sandbox_parse_info_file($user, $file->source);
189
        if (!$md5) {
190
            xml_error(-1, "sandbox link file $file->source not found");
191
        }
192
        $phys_name = job_file_name($md5);
193
        $path = sandbox_path($user, $file->source);
194
        stage_file_aux($path, $md5, $size, $phys_name);
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
195
    case "inline":
196
        $md5 = md5($file->source);
197
        if (!$md5) {
198
            log_write("Can't get MD5 of inline data");
199
            xml_error(-1, "Can't get MD5 of inline data");
200
        }
201
        $name = job_file_name($md5);
202
        $path = dir_hier_path($name, $download_dir, $fanout);
203
        if (file_exists($path)) return $name;
204
        if (!file_put_contents($path, $file->source)) {
205
            log_write("can't write to file $path");
206
            xml_error(-1, "can't write to file $path");
207
        }
208
        return $name;
209
    }
210
    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

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

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

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

731
    $batch = /** @scrutinizer ignore-call */ get_batch($r);

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...
732
    if ($batch->user_id != $user->id) {
733
        log_write("not owner of batch");
734
        xml_error(-1, "not owner of batch");
735
    }
736
737
    $wus = BoincWorkunit::enum_fields(
738
        'id, name, rsc_fpops_est, canonical_credit, canonical_resultid, error_mask',
739
        "batch = $batch->id", 'order by id'
740
    );
741
    $batch = get_batch_params($batch, $wus);
742
    $get_cpu_time = (int)($r->get_cpu_time);
743
    $get_job_details = (int)($r->get_job_details);
744
    print_batch_params($batch, $get_cpu_time);
745
    foreach ($wus as $wu) {
746
        echo "        <job>
747
        <id>$wu->id</id>
748
        <name>$wu->name</name>
749
        <canonical_instance_id>$wu->canonical_resultid</canonical_instance_id>
750
";
751
        if ($get_job_details) {
752
            show_job_details($wu);
753
        }
754
        echo "        </job>\n";
755
    }
756
    echo "</query_batch>\n";
757
}
758
759
function results_sent($wu) {
760
    return BoincResult::count("workunitid=$wu->id and sent_time>0");
761
}
762
763
// variant for Condor, which doesn't care about job instances
764
// and refers to batches by name
765
//
766
function query_batch2($r) {
767
    xml_start_tag("query_batch2");
768
    $user = check_remote_submit_permissions($r, null);
769
    $batch_names = $r->batch_name;
770
    $batches = array();
771
    foreach ($batch_names as $b) {
772
        $batch_name = (string)$b;
773
        $batch_name = BoincDb::escape_string($batch_name);
774
        $batch = BoincBatch::lookup_name($batch_name);
775
        if (!$batch) {
776
            log_write("no batch named $batch_name");
777
            xml_error(-1, "no batch named $batch_name");
778
        }
779
        if ($batch->user_id != $user->id) {
780
            log_write("not owner of $batch_name");
781
            xml_error(-1, "not owner of $batch_name");
782
        }
783
        $batches[] = $batch;
784
    }
785
786
    $min_mod_time = (double)$r->min_mod_time;
787
    if ($min_mod_time) {
788
        $mod_time_clause = "and mod_time > FROM_UNIXTIME($min_mod_time)";
789
    } else {
790
        $mod_time_clause = "";
791
    }
792
793
    $t = dtime();
794
    echo "<server_time>$t</server_time>\n";
795
    foreach ($batches as $batch) {
796
        $wus = BoincWorkunit::enum("batch = $batch->id $mod_time_clause");
797
        echo "   <batch_size>".count($wus)."</batch_size>\n";
798
799
        // job status is:
800
        // DONE if done
801
        // ERROR if error
802
        // IN_PROGRESS if at least one instance sent
803
        // QUEUED if no instances sent
804
        foreach ($wus as $wu) {
805
            if ($wu->canonical_resultid) {
806
                $status = "DONE";
807
            } else if ($wu->error_mask) {
808
                $status = "ERROR";
809
            } else if (results_sent($wu) > 0) {
810
                $status = "IN_PROGRESS";
811
            } else {
812
                $status = "UNSENT";
813
            }
814
            echo
815
"    <job>
816
        <job_name>$wu->name</job_name>
817
        <status>$status</status>
818
    </job>
819
";
820
        }
821
    }
822
    echo "</query_batch2>\n";
823
}
824
825
function query_job($r) {
826
    xml_start_tag("query_job");
827
    $user = check_remote_submit_permissions($r, null);
828
    $job_id = (int)($r->job_id);
829
    $wu = BoincWorkunit::lookup_id($job_id);
830
    if (!$wu) {
831
        log_write("no such job");
832
        xml_error(-1, "no such job");
833
    }
834
    $batch = BoincBatch::lookup_id($wu->batch);
835
    if ($batch->user_id != $user->id) {
836
        log_write("not owner");
837
        xml_error(-1, "not owner");
838
    }
839
    $results = BoincResult::enum("workunitid=$job_id");
840
    foreach ($results as $result) {
841
        echo "    <instance>
842
        <name>$result->name</name>
843
        <id>$result->id</id>
844
        <state>".state_string($result)."</state>
845
";
846
        if ($result->server_state == 5) {   // over?
847
            $paths = get_outfile_paths($result);
848
            foreach($paths as $path) {
849
                if (is_file($path)) {
850
                    $size = filesize($path);
851
                    echo "        <outfile>
852
            <size>$size</size>
853
        </outfile>
854
";
855
                }
856
            }
857
        }
858
        echo "</instance>\n";
859
    }
860
    echo "</query_job>\n";
861
}
862
863
// the following for Condor.
864
// If the job has a canonical instance, return info about it.
865
// Otherwise find an instance that completed
866
// (possibly crashed) and return its info.
867
//
868
function query_completed_job($r) {
869
    xml_start_tag("query_completed_job");
870
    $user = check_remote_submit_permissions($r, null);
871
    $job_name = (string)($r->job_name);
872
    $job_name = BoincDb::escape_string($job_name);
873
    $wu = BoincWorkunit::lookup("name='$job_name'");
874
    if (!$wu) {
875
        log_write("no such job");
876
        xml_error(-1, "no such job");
877
    }
878
    $batch = BoincBatch::lookup_id($wu->batch);
879
    if ($batch->user_id != $user->id) {
880
        log_write("not owner");
881
        xml_error(-1, "not owner");
882
    }
883
884
    echo "<completed_job>\n";
885
    echo "   <error_mask>$wu->error_mask</error_mask>\n";
886
    if ($wu->canonical_resultid) {
887
        $result = BoincResult::lookup_id($wu->canonical_resultid);
888
        echo "   <canonical_resultid>$wu->canonical_resultid</canonical_resultid>\n";
889
    } else {
890
        $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...
891
        foreach ($results as $r) {
0 ignored issues
show
introduced by
$r is overwriting one of the parameters of this function.
Loading history...
892
            switch($r->outcome) {
893
            case 1:
894
            case 3:
895
            case 6:
896
                $result = $r;
897
                break;
898
            }
899
        }
900
        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...
901
            echo "   <error_resultid>$result->id</error_resultid>\n";
902
        }
903
    }
904
    if ($result) {
905
        echo "   <exit_status>$result->exit_status</exit_status>\n";
906
        echo "   <elapsed_time>$result->elapsed_time</elapsed_time>\n";
907
        echo "   <cpu_time>$result->cpu_time</cpu_time>\n";
908
        echo "   <stderr_out><![CDATA[\n";
909
        echo htmlspecialchars($result->stderr_out);
910
        echo "   ]]></stderr_out>\n";
911
    }
912
    echo "</completed_job>
913
        </query_completed_job>
914
    ";
915
}
916
917
function handle_abort_batch($r) {
918
    xml_start_tag("abort_batch");
919
    $user = check_remote_submit_permissions($r, null);
920
    $batch = get_batch($r);
0 ignored issues
show
Unused Code introduced by
The call to get_batch() has too many arguments starting with $r. ( Ignorable by Annotation )

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

920
    $batch = /** @scrutinizer ignore-call */ get_batch($r);

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...
921
    if ($batch->user_id != $user->id) {
922
        log_write("not owner");
923
        xml_error(-1, "not owner");
924
    }
925
    abort_batch($batch);
926
    echo "<success>1</success>
927
        </abort_batch>
928
    ";
929
}
930
931
// handle the abort of jobs possibly belonging to different batches
932
//
933
function handle_abort_jobs($r) {
934
    xml_start_tag("abort_jobs");
935
    $user = check_remote_submit_permissions($r, null);
936
    $batch = null;
937
    foreach ($r->job_name as $job_name) {
938
        $job_name = BoincDb::escape_string($job_name);
939
        $wu = BoincWorkunit::lookup("name='$job_name'");
940
        if (!$wu) {
941
            log_write("no job $job_name");
942
            xml_error(-1, "no job $job_name");
943
        }
944
        if (!$wu->batch) {
945
            log_write("job $job_name is not part of a batch");
946
            xml_error(-1, "job $job_name is not part of a batch");
947
        }
948
        if (!$batch || $wu->batch != $batch->id) {
949
            $batch = BoincBatch::lookup_id($wu->batch);
950
        }
951
        if (!$batch || $batch->user_id != $user->id) {
952
            log_write("not owner of batch");
953
            xml_error(-1, "not owner of batch");
954
        }
955
        echo "<aborted $job_name>\n";
956
        abort_workunit($wu);
957
    }
958
    echo "<success>1</success>
959
        </abort_jobs>
960
    ";
961
}
962
963
function handle_retire_batch($r) {
964
    xml_start_tag("retire_batch");
965
    $user = check_remote_submit_permissions($r, null);
966
    $batch = get_batch($r);
0 ignored issues
show
Unused Code introduced by
The call to get_batch() has too many arguments starting with $r. ( Ignorable by Annotation )

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

966
    $batch = /** @scrutinizer ignore-call */ get_batch($r);

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...
967
    if ($batch->user_id != $user->id) {
968
        log_write("not owner of batch");
969
        xml_error(-1, "not owner of batch");
970
    }
971
    retire_batch($batch);
972
    echo "<success>1</success>
973
        </retire_batch>
974
    ";
975
}
976
977
function handle_set_expire_time($r) {
978
    xml_start_tag("set_expire_time");
979
    $user = check_remote_submit_permissions($r, null);
980
    $batch = get_batch($r);
0 ignored issues
show
Unused Code introduced by
The call to get_batch() has too many arguments starting with $r. ( Ignorable by Annotation )

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

980
    $batch = /** @scrutinizer ignore-call */ get_batch($r);

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...
981
    if ($batch->user_id != $user->id) {
982
        log_write("not owner of batch");
983
        xml_error(-1, "not owner of batch");
984
    }
985
    $expire_time = (double)($r->expire_time);
986
    if ($batch->update("expire_time=$expire_time")) {
987
        echo "<success>1</success>";
988
    } else {
989
        log_write("batch update failed");
990
        xml_error(-1, "batch update failed");
991
    }
992
    echo "</set_expire_time>\n";
993
}
994
995
function get_templates($r) {
996
    xml_start_tag("get_templates");
997
    $app_name = (string)($r->app_name);
998
    if ($app_name) {
999
        $app = get_submit_app($app_name);
1000
    } else {
1001
        $job_name = (string)($r->job_name);
1002
        $wu = get_wu($job_name);
1003
        $app = BoincApp::lookup_id($wu->appid);
1004
    }
1005
1006
    $user = check_remote_submit_permissions($r, $app);
1007
    $in = file_get_contents(project_dir() . "/templates/".$app->name."_in");
1008
    $out = file_get_contents(project_dir() . "/templates/".$app->name."_out");
1009
    if ($in === false || $out === false) {
1010
        log_write("template file missing");
1011
        xml_error(-1, "template file missing");
1012
    }
1013
    echo "<templates>\n$in\n$out\n</templates>
1014
        </get_templates>
1015
    ";
1016
}
1017
1018
function ping($r) {
1019
    xml_start_tag("ping");
1020
    BoincDb::get();     // errors out if DB down or web disabled
1021
    echo "<success>1</success>
1022
        </ping>
1023
    ";
1024
}
1025
1026
if (0) {
1027
$r = simplexml_load_string("
1028
<query_batch2>
1029
    <authenticator>x</authenticator>
1030
    <batch_name>batch_30</batch_name>
1031
    <batch_name>batch_31</batch_name>
1032
</query_batch2>
1033
");
1034
query_batch2($r);
1035
exit;
1036
}
1037
1038
if (0) {
1039
$r = simplexml_load_string("
1040
<query_batch>
1041
    <authenticator>x</authenticator>
1042
    <batch_id>54</batch_id>
1043
</query_batch>
1044
");
1045
query_batch($r);
1046
exit;
1047
}
1048
1049
if (0) {
1050
$r = simplexml_load_string("
1051
<query_job>
1052
    <authenticator>x</authenticator>
1053
    <job_id>312173</job_id>
1054
</query_job>
1055
");
1056
query_job($r);
1057
exit;
1058
}
1059
1060
if (0) {
1061
$r = simplexml_load_string("
1062
<estimate_batch>
1063
    <authenticator>x</authenticator>
1064
    <batch>
1065
    <app_name>remote_test</app_name>
1066
    <batch_name>Aug 6 batch 4</batch_name>
1067
    <job>
1068
        <rsc_fpops_est>19000000000</rsc_fpops_est>
1069
        <command_line>--t 19</command_line>
1070
        <input_file>
1071
            <mode>remote</mode>
1072
            <source>https://google.com/</source>
1073
        </input_file>
1074
    </job>
1075
    </batch>
1076
</estimate_batch>
1077
");
1078
estimate_batch($r);
1079
exit;
1080
}
1081
1082
xml_header();
1083
if (0) {
1084
    $req = file_get_contents("req");
1085
} else {
1086
    $req = $_POST['request'];
1087
}
1088
1089
// optionally write request message (XML) to log file
1090
//
1091
$request_log = parse_config(get_config(), "<remote_submit_request_log>");
1092
if ($request_log) {
1093
    $log_dir = parse_config(get_config(), "<log_dir>");
1094
    $request_log = $log_dir . "/" . $request_log;
1095
    if ($file = fopen($request_log, "a")) {
1096
        fwrite($file, "\n<submit_rpc_handler date=\"" . date(DATE_ATOM) . "\">\n" . $req . "\n</submit_rpc_handler>\n");
1097
        fclose($file);
1098
    }
1099
}
1100
1101
$r = simplexml_load_string($req);
1102
if (!$r) {
0 ignored issues
show
introduced by
$r is of type SimpleXMLElement, thus it always evaluated to true.
Loading history...
1103
    log_write("----- RPC request: can't parse request message: $req");
1104
    xml_error(-1, "can't parse request message: ".htmlspecialchars($req));
1105
}
1106
1107
log_write("----- Handling RPC; command ".$r->getName());
1108
1109
switch ($r->getName()) {
1110
    case 'abort_batch': handle_abort_batch($r); break;
1111
    case 'abort_jobs': handle_abort_jobs($r); break;
1112
    case 'create_batch': create_batch($r); break;
0 ignored issues
show
Bug introduced by
The call to create_batch() has too few arguments starting with njobs. ( Ignorable by Annotation )

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

1112
    case 'create_batch': /** @scrutinizer ignore-call */ create_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...
1113
    case 'estimate_batch': estimate_batch($r); break;
1114
    case 'get_templates': get_templates($r); break;
1115
    case 'ping': ping($r); break;
1116
    case 'query_batch': query_batch($r); break;
1117
    case 'query_batch2': query_batch2($r); break;
1118
    case 'query_batches': query_batches($r); break;
1119
    case 'query_job': query_job($r); break;
1120
    case 'query_completed_job': query_completed_job($r); break;
1121
    case 'retire_batch': handle_retire_batch($r); break;
1122
    case 'set_expire_time': handle_set_expire_time($r); break;
1123
    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

1123
    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...
1124
    default:
1125
        log_write("bad command");
1126
        xml_error(-1, "bad command");
1127
        break;
1128
}
1129
1130
log_write("RPC done");
1131
1132
?>
1133