Passed
Push — master ( 57e0bd...949aa5 )
by
unknown
16:34 queued 06:25
created

handle_submit()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 45
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 23
c 2
b 0
f 0
nc 4
nop 1
dl 0
loc 45
rs 9.552
1
<?php
2
// This file is part of BOINC.
3
// https://boinc.berkeley.edu
4
// Copyright (C) 2024 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
// web interface for submitting BUDA jobs
20
21
require_once('../inc/util.inc');
22
require_once('../inc/submit_util.inc');
23
require_once('../inc/sandbox.inc');
24
25
display_errors();
26
27
function submit_form($user) {
28
    $sbitems_zip = sandbox_select_items($user, '/.zip$/');
29
    if (!$sbitems_zip) {
30
        error_page("No .zip files in your sandbox.");
31
    }
32
    $app = get_str('app');
33
    if (!is_valid_filename($app)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
34
    $variant = get_str('variant');
35
    if (!is_valid_filename($variant)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
36
37
    $desc = "<br><small>
38
        A zip file with one directory per job.
39
        Each directory contains the input file(s) for that job
40
        and an optional file <code>cmdline</code>
41
        containing command-line arguments.
42
        <a href=https://github.com/BOINC/boinc/wiki/BUDA-job-submission>Details</a></small>.
43
    ";
44
    page_head("Submit jobs to $app ($variant)");
45
    form_start('buda_submit.php');
46
    form_input_hidden('action', 'submit');
47
    form_input_hidden('app', $app);
48
    form_input_hidden('variant', $variant);
49
    form_select("Batch zip file $desc", 'batch_file', $sbitems_zip);
50
    form_input_text('Command-line arguments', 'cmdline');
51
    form_checkbox(
52
        "Enabled debugging output <br><small>Write Docker commands and output to stderr</small>.",
53
        'wrapper_verbose'
54
    );
55
    form_submit('OK');
56
    form_end();
57
    page_tail();
58
}
59
60
// unzip batch file into a temp dir; return dir name
61
//
62
function unzip_batch_file($user, $batch_file) {
63
    @mkdir("../../buda_batches");
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

63
    /** @scrutinizer ignore-unhandled */ @mkdir("../../buda_batches");

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...
64
    for ($i=0; $i<1000; $i++) {
65
        $batch_dir = "../../buda_batches/$i";
66
        $batch_dir_name = $i;
67
        $ret = @mkdir($batch_dir);
68
        if ($ret) break;
69
    }
70
    if (!$ret) error_page("can't create batch dir");
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ret does not seem to be defined for all execution paths leading up to this point.
Loading history...
71
    $sb_dir = sandbox_dir($user);
72
    if (!file_exists("$sb_dir/$batch_file")) {
73
        error_page("no batch file $batch_file");
74
    }
75
    system("cd $batch_dir; unzip $sb_dir/$batch_file > /dev/null", $ret);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $batch_dir does not seem to be defined for all execution paths leading up to this point.
Loading history...
76
    if ($ret) {
77
        error_page("unzip error: $ret");
78
    }
79
    return $batch_dir_name;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $batch_dir_name does not seem to be defined for all execution paths leading up to this point.
Loading history...
80
}
81
82
// Scan a batch dir.
83
// Check its validity:
84
// - optional dir 'shared_input_files' has shared input files
85
// - other dirs (job dirs) can have only remaining infiles and possibly cmdline
86
//
87
// Return a structure describing its contents, and the md5/size of files
88
//
89
function parse_batch_dir($batch_dir, $variant_desc) {
90
    $input_files = $variant_desc->input_file_names;
91
    sort($input_files);
92
    $shared_files = [];
93
    $shared_file_infos = [];
94
    if (is_dir("$batch_dir/shared_input_files")) {
95
        foreach (scandir("$batch_dir/shared_input_files") as $fname) {
96
            if ($fname[0] == '.') continue;
97
            if (!in_array($fname, $input_files)) {
98
                error_page("$fname is not an input file name");
99
            }
100
            $shared_files[] = $fname;
101
            $shared_file_infos[] = get_file_info("$batch_dir/shared_input_files/$fname");
102
        }
103
    }
104
    $unshared_files = array_diff($input_files, $shared_files);
105
    sort($unshared_files);
106
    $jobs = [];
107
    foreach (scandir($batch_dir) as $fname) {
108
        if ($fname[0] == '.') continue;
109
        if ($fname == 'shared_input_files') continue;
110
        if (!is_dir("$batch_dir/$fname")) {
111
            error_page("$batch_dir/$fname is not a directory");
112
        }
113
        $job_files = [];
114
        $cmdline = '';
115
        foreach(scandir("$batch_dir/$fname") as $f2) {
116
            if ($f2[0] == '.') continue;
117
            if ($f2 == 'cmdline') {
118
                $cmdline = trim(file_get_contents("$batch_dir/$fname/cmdline"));
119
                continue;
120
            }
121
            if (!in_array($f2, $unshared_files)) {
122
                error_page("$fname/$f2 is not an input file name");
123
            }
124
            $job_files[] = $f2;
125
        }
126
        if (sort($job_files) != $unshared_files) {
127
            error_page("$fname doesn't have all input files");
128
        }
129
130
        $file_infos = [];
131
        foreach ($unshared_files as $f2) {
132
            $file_infos[] = get_file_info("$batch_dir/$fname/$f2");
133
        }
134
135
        $job = new StdClass;
136
        $job->dir = $fname;
137
        $job->cmdline = $cmdline;
138
        $job->file_infos = $file_infos;
139
        $jobs[] = $job;
140
    }
141
    $batch_desc = new StdClass;
142
    $batch_desc->shared_files = $shared_files;
143
    $batch_desc->shared_file_infos = $shared_file_infos;
144
    $batch_desc->unshared_files = $unshared_files;
145
    $batch_desc->jobs = $jobs;
146
    return $batch_desc;
147
}
148
149
function create_batch($user, $njobs, $app, $variant) {
150
    global $buda_boinc_app;
151
    $now = time();
152
    $batch_name = sprintf('buda_%d_%d', $user->id, $now);
153
    $description = "$app ($variant)";
154
    $batch_id = BoincBatch::insert(sprintf(
155
        "(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 (%d, %d, 0, 0, 0, %d, 0, 0, %d, 0, 0, 0, 0, '%s', %d, 0, '%s', 0)",
156
        $user->id, $now, $njobs, BATCH_STATE_INIT, $batch_name, $buda_boinc_app->id,
157
        $description
158
    ));
159
    return BoincBatch::lookup_id($batch_id);
160
}
161
162
function stage_input_files($batch_dir, $batch_desc, $batch_id) {
163
    $n = count($batch_desc->shared_files);
164
    $batch_desc->shared_files_phys_names = [];
165
    for ($i=0; $i<$n; $i++) {
166
        $path = sprintf('%s/%s', $batch_dir, $batch_desc->shared_files[$i]);
167
        [$md5, $size] = $batch_desc->shared_file_infos[$i];
168
        $phys_name = sprintf('batch_%d_%s', $batch_id, $md5);
169
        stage_file_aux($path, $md5, $size, $phys_name);
170
        $batch_desc->shared_files_phys_names[] = $phys_name;
171
    }
172
    foreach ($batch_desc->jobs as $job) {
173
        $n = count($batch_desc->unshared_files);
174
        $job->phys_names = [];
175
        for ($i=0; $i<$n; $i++) {
176
            $path = sprintf('%s/%s/%s',
177
                $batch_dir, $job->dir, $batch_desc->unshared_files[$i]
178
            );
179
            [$md5, $size] = $job->file_infos[$i];
180
            $phys_name = sprintf('batch_%d_%s', $batch_id, $md5);
181
            stage_file_aux($path, $md5, $size, $phys_name);
182
            $job->phys_names[] = $phys_name;
183
        }
184
    }
185
}
186
187
// run bin/create_work to create the jobs.
188
// Use --stdin, where each job is described by a line
189
//
190
function create_jobs(
191
    $app, $variant, $variant_desc,
0 ignored issues
show
Coding Style introduced by
Multi-line function declarations must define one parameter per line
Loading history...
192
    $batch_desc, $batch_id, $batch_dir_name,
0 ignored issues
show
Coding Style introduced by
Multi-line function declarations must define one parameter per line
Loading history...
193
    $wrapper_verbose, $cmdline
0 ignored issues
show
Coding Style introduced by
Multi-line function declarations must define one parameter per line
Loading history...
194
) {
195
    global $buda_boinc_app;
196
197
    // get list of physical names of app files
198
    //
199
    $app_file_names = $variant_desc->dockerfile_phys;
200
    foreach ($variant_desc->app_files_phys as $pname) {
201
        $app_file_names .= " $pname";
202
    }
203
204
    // make per-job lines to pass as stdin
205
    //
206
    $job_cmds = '';
207
    foreach ($batch_desc->jobs as $job) {
208
        $job_cmd = sprintf('--wu_name batch_%d__job_%s', $batch_id, $job->dir);
209
        if ($job->cmdline) {
210
            $job_cmd .= sprintf(' --command_line "%s"', $job->cmdline);
211
        }
212
        $job_cmd .= " $app_file_names";
213
        foreach ($batch_desc->shared_files_phys_names as $x) {
214
            $job_cmd .= " $x";
215
        }
216
        foreach ($job->phys_names as $x) {
217
            $job_cmd .= " $x";
218
        }
219
        $job_cmds .= "$job_cmd\n";
220
    }
221
    $cw_cmdline = sprintf('"--dockerfile %s %s %s"',
222
        $variant_desc->dockerfile,
223
        $wrapper_verbose?'--verbose':'',
224
        $cmdline
225
    );
226
    $cmd = sprintf(
227
        'cd ../..; bin/create_work --appname %s --batch %d --stdin --command_line %s --wu_template %s --result_template %s',
228
        $buda_boinc_app->name, $batch_id,
229
        $cw_cmdline,
230
        "buda_apps/$app/$variant/template_in",
231
        "buda_apps/$app/$variant/template_out"
232
    );
233
    $cmd .= sprintf(' > %s 2<&1', "buda_batches/errfile");
234
235
    $h = popen($cmd, "w");
236
    if (!$h) error_page('create_work launch failed');
0 ignored issues
show
introduced by
$h is of type resource, thus it always evaluated to false.
Loading history...
237
    fwrite($h, $job_cmds);
238
    $ret = pclose($h);
239
    if ($ret) {
240
        echo "<pre>create_work failed.\n";
241
        echo "command: $cmd\n\n";
242
        echo "job lines:\n$job_cmds\n\n";
243
        echo "error file:\n";
244
        readfile("../../buda_batches/errfile");
245
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
246
    }
247
}
248
249
function handle_submit($user) {
250
    $app = get_str('app');
251
    if (!is_valid_filename($app)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
252
    $variant = get_str('variant');
253
    if (!is_valid_filename($variant)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
254
    $batch_file = get_str('batch_file');
255
    if (!is_valid_filename($batch_file)) die('bad arg');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
256
    $wrapper_verbose = get_str('wrapper_verbose', true);
257
    $cmdline = get_str('cmdline');
258
259
    $variant_dir = "../../buda_apps/$app/$variant";
260
    $variant_desc = json_decode(
261
        file_get_contents("$variant_dir/variant.json")
262
    );
263
264
    // unzip batch file into temp dir
265
    $batch_dir_name = unzip_batch_file($user, $batch_file);
266
    $batch_dir = "../../buda_batches/$batch_dir_name";
267
268
    // scan batch dir; validate and return struct
269
    $batch_desc = parse_batch_dir($batch_dir, $variant_desc);
270
271
    $batch = create_batch(
272
        $user, count($batch_desc->jobs), $app, $variant
273
    );
274
275
    // stage input files and record the physical names
276
    //
277
    stage_input_files($batch_dir, $batch_desc, $batch->id);
278
279
    create_jobs(
280
        $app, $variant, $variant_desc,
281
        $batch_desc, $batch->id, $batch_dir_name,
282
        $wrapper_verbose, $cmdline
283
    );
284
285
    // mark batch as in progress
286
    //
287
    $batch->update(sprintf('state=%d', BATCH_STATE_IN_PROGRESS));
288
289
    // clean up batch dir
290
    //
291
    //system("rm -rf $batch_dir");
292
293
    header("Location: submit.php?action=query_batch&batch_id=$batch->id");
294
}
295
296
$user = get_logged_in_user();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $user is correct as get_logged_in_user() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
297
$buda_boinc_app = BoincApp::lookup("name='buda'");
298
if (!$buda_boinc_app) error_page('no buda app');
299
if (!has_submit_access($user, $buda_boinc_app->id)) {
300
    error_page('no access');
301
}
302
$action = get_str('action', true);
303
if ($action == 'submit') {
304
    handle_submit($user);
305
} else {
306
    submit_form($user);
307
}
308
309
?>
310