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("batch = $batch->id");
632
            $batch = get_batch_params($batch, $wus);
633
        }
634
        echo "    <batch>\n";
635
        print_batch_params($batch, $get_cpu_time);
636
        echo "   </batch>\n";
637
    }
638
    echo "</query_batches>\n";
639
}
640
641
function n_outfiles($wu) {
642
    $path = project_dir() . "/$wu->output_template_filename";
643
    $r = simplexml_load_file($path);
644
    return count($r->file_info);
645
}
646
647
// show status of job.
648
// done:
649
// unsent:
650
// in_progress:
651
// error:
652
653
function show_job_details($wu) {
654
    if ($wu->error_mask & WU_ERROR_COULDNT_SEND_RESULT) {
655
        echo "   <error>couldnt_send_result</error>\n";
656
    }
657
    if ($wu->error_mask & WU_ERROR_TOO_MANY_ERROR_RESULTS) {
658
        echo "   <error>too_many_error_results</error>\n";
659
    }
660
    if ($wu->error_mask & WU_ERROR_TOO_MANY_SUCCESS_RESULTS) {
661
        echo "   <error>too_many_success_results</error>\n";
662
    }
663
    if ($wu->error_mask & WU_ERROR_TOO_MANY_TOTAL_RESULTS) {
664
        echo "   <error>too_many_total_results</error>\n";
665
    }
666
    if ($wu->error_mask & WU_ERROR_CANCELLED) {
667
        echo "   <error>cancelled</error>\n";
668
    }
669
    if ($wu->error_mask & WU_ERROR_NO_CANONICAL_RESULT) {
670
        echo "   <error>no_canonical_result</error>\n";
671
    }
672
    $results = BoincResult::enum("workunitid=$wu->id");
673
    $in_progress = 0;
674
    foreach ($results as $r) {
675
        switch ($r->server_state) {
676
        case RESULT_SERVER_STATE_IN_PROGRESS:
677
            $in_progress++;
678
            break;
679
        }
680
        if ($wu->error_mask && ($r->outcome == RESULT_OUTCOME_CLIENT_ERROR)) {
681
            echo "            <exit_status>$r->exit_status</exit_status>\n";
682
        }
683
        if ($r->id == $wu->canonical_resultid) {
684
            echo "            <cpu_time>$r->cpu_time</cpu_time>\n";
685
        }
686
    }
687
    if ($wu->error_mask) {
688
        echo "            <status>error</status>\n";
689
        return;
690
    }
691
692
    if ($wu->canonical_resultid) {
693
        echo "            <status>done</status>\n";
694
    } else {
695
        if ($in_progress > 0) {
696
            echo "            <status>in_progress</status>\n";
697
        } else {
698
            echo "            <status>queued</status>\n";
699
        }
700
    }
701
}
702
703
// return a batch specified by the command, using either ID or name
704
//
705
function get_batch($r) {
706
    $batch = NULL;
707
    if (!empty($r->batch_id)) {
708
        $batch_id = (int)($r->batch_id);
709
        $batch = BoincBatch::lookup_id($batch_id);
710
    } else if (!empty($r->batch_name)) {
711
        $batch_name = (string)($r->batch_name);
712
        $batch_name = BoincDb::escape_string($batch_name);
713
        $batch = BoincBatch::lookup_name($batch_name);
714
    } else {
715
        log_write("batch not specified");
716
        xml_error(-1, "batch not specified");
717
    }
718
    if (!$batch) {
719
        log_write("no such batch");
720
        xml_error(-1, "no such batch");
721
    }
722
    return $batch;
723
}
724
725
function query_batch($r) {
726
    xml_start_tag("query_batch");
727
    $user = check_remote_submit_permissions($r, null);
728
    $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

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

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

967
    $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...
968
    if ($batch->user_id != $user->id) {
969
        log_write("not owner of batch");
970
        xml_error(-1, "not owner of batch");
971
    }
972
    retire_batch($batch);
973
    echo "<success>1</success>
974
        </retire_batch>
975
    ";
976
}
977
978
function handle_set_expire_time($r) {
979
    xml_start_tag("set_expire_time");
980
    $user = check_remote_submit_permissions($r, null);
981
    $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

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

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

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