Passed
Push — master ( 86fd7b...3fe522 )
by Vitalii
01:34 queued 30s
created

unzip_batch_file()   A

Complexity

Conditions 6
Paths 24

Size

Total Lines 18
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 6
eloc 14
nc 24
nop 2
dl 0
loc 18
rs 9.2222
c 1
b 0
f 1
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 zipped directory with one subdirectory per job,
39
        containing the input file(s) for that job
40
        and an optional file <code>cmdline</code>
41
        containing command-line arguments.
42
        See <a href=https://github.com/BOINC/boinc/wiki/Docker-apps>more 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_submit('OK');
51
    form_end();
52
    page_tail();
53
}
54
55
// unzip batch file into a temp dir; return dir name
56
//
57
function unzip_batch_file($user, $batch_file) {
58
    @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

58
    /** @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...
59
    for ($i=0; $i<1000; $i++) {
60
        $batch_dir = "../../buda_batches/$i";
61
        $batch_dir_name = $i;
62
        $ret = @mkdir($batch_dir);
63
        if ($ret) break;
64
    }
65
    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...
66
    $sb_dir = sandbox_dir($user);
67
    if (!file_exists("$sb_dir/$batch_file")) {
68
        error_page("no batch file $batch_file");
69
    }
70
    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...
71
    if ($ret) {
72
        error_page("unzip error: $ret");
73
    }
74
    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...
75
}
76
77
// check validity of batch dir.
78
// top level should have only infiles (shared)
79
// job dirs should have only remaining infiles and possibly cmdline
80
//
81
// return struct describing the batch, and the md5/size of files
82
//
83
function parse_batch_dir($batch_dir, $variant_desc) {
84
    $input_files = $variant_desc->input_file_names;
85
    sort($input_files);
86
    $shared_files = [];
87
    $shared_file_infos = [];
88
    if (is_dir("$batch_dir/shared_input_files")) {
89
        foreach (scandir("$batch_dir/shared_input_files") as $fname) {
90
            if ($fname[0] == '.') continue;
91
            if (!in_array($fname, $input_files)) {
92
                error_page("$fname is not an input file name");
93
            }
94
            $shared_files[] = $fname;
95
            $shared_file_infos[] = get_file_info("$batch_dir/shared_input_files/$fname");
96
        }
97
    }
98
    $unshared_files = array_diff($input_files, $shared_files);
99
    sort($unshared_files);
100
    $jobs = [];
101
    foreach (scandir($batch_dir) as $fname) {
102
        if ($fname[0] == '.') continue;
103
        if ($fname == 'shared_input_files') continue;
104
        if (!is_dir("$batch_dir/$fname")) {
105
            error_page("$batch_dir/$fname is not a directory");
106
        }
107
        $job_files = [];
108
        $cmdline = '';
109
        foreach(scandir("$batch_dir/$fname") as $f2) {
110
            if ($f2[0] == '.') continue;
111
            if ($f2 == 'cmdline') {
112
                $cmdline = file_get_contents("$batch_dir/$f2");
113
            }
114
            if (!in_array($f2, $unshared_files)) {
115
                error_page("$fname/$f2 is not an input file name");
116
            }
117
            $job_files[] = $f2;
118
        }
119
        if (sort($job_files) != $unshared_files) {
120
            error_page("$fname doesn't have all input files");
121
        }
122
123
        $file_infos = [];
124
        foreach ($unshared_files as $f2) {
125
            $file_infos[] = get_file_info("$batch_dir/$fname/$f2");
126
        }
127
128
        $job = new StdClass;
129
        $job->dir = $fname;
130
        $job->cmdline = $cmdline;
131
        $job->file_infos = $file_infos;
132
        $jobs[] = $job;
133
    }
134
    $batch_desc = new StdClass;
135
    $batch_desc->shared_files = $shared_files;
136
    $batch_desc->shared_file_infos = $shared_file_infos;
137
    $batch_desc->unshared_files = $unshared_files;
138
    $batch_desc->jobs = $jobs;
139
    return $batch_desc;
140
}
141
142
function create_batch($user, $njobs, $boinc_app, $app, $variant) {
143
    $now = time();
144
    $batch_name = sprintf('buda_%d_%d', $user->id, $now);
145
    $description = "$app ($variant)";
146
    $batch_id = BoincBatch::insert(sprintf(
147
        "(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)",
148
        $user->id, $now, $njobs, BATCH_STATE_INIT, $batch_name, $boinc_app->id,
149
        $description
150
    ));
151
    return BoincBatch::lookup_id($batch_id);
152
}
153
154
function stage_input_files($batch_dir, $batch_desc, $batch_id) {
155
    $n = count($batch_desc->shared_files);
156
    $batch_desc->shared_files_phys_names = [];
157
    for ($i=0; $i<$n; $i++) {
158
        $path = sprintf('%s/%s', $batch_dir, $batch_desc->shared_files[$i]);
159
        [$md5, $size] = $batch_desc->shared_file_infos[$i];
160
        $phys_name = sprintf('batch_%d_%s', $batch_id, $md5);
161
        stage_file_aux($path, $md5, $size, $phys_name);
162
        $batch_desc->shared_files_phys_names[] = $phys_name;
163
    }
164
    foreach ($batch_desc->jobs as $job) {
165
        $n = count($batch_desc->unshared_files);
166
        $job->phys_names = [];
167
        for ($i=0; $i<$n; $i++) {
168
            $path = sprintf('%s/%s/%s',
169
                $batch_dir, $job->dir, $batch_desc->unshared_files[$i]
170
            );
171
            [$md5, $size] = $job->file_infos[$i];
172
            $phys_name = sprintf('batch_%d_%s', $batch_id, $md5);
173
            stage_file_aux($path, $md5, $size, $phys_name);
174
            $job->phys_names[] = $phys_name;
175
        }
176
    }
177
}
178
179
function create_jobs(
180
    $variant_desc, $batch_desc, $batch_id, $boinc_app, $batch_dir_name
0 ignored issues
show
Coding Style introduced by
Multi-line function declarations must define one parameter per line
Loading history...
181
) {
182
    // get list of names of app files
183
    //
184
    $app_file_names = $variant_desc->dockerfile_phys;
185
    foreach ($variant_desc->app_files_phys as $pname) {
186
        $app_file_names .= " $pname";
187
    }
188
    $job_cmds = '';
189
    foreach ($batch_desc->jobs as $job) {
190
        $job_cmd = sprintf('--wu_name batch_%d__job_%s', $batch_id, $job->dir);
191
        if ($job->cmdline) {
192
            $job_cmd .= sprintf(' --command_line "%s"', $job->cmdline);
193
        }
194
        $job_cmd .= " $app_file_names";
195
        foreach ($batch_desc->shared_files_phys_names as $x) {
196
            $job_cmd .= " $x";
197
        }
198
        foreach ($job->phys_names as $x) {
199
            $job_cmd .= " $x";
200
        }
201
        $job_cmds .= "$job_cmd\n";
202
    }
203
    $cmd = sprintf(
204
        'cd ../..; bin/create_work --appname %s --batch %d --stdin --command_line "--dockerfile %s --verbose" --wu_template %s --result_template %s',
205
        $boinc_app->name, $batch_id, $variant_desc->dockerfile,
206
        "buda_batches/$batch_dir_name/template_in",
207
        "buda_batches/$batch_dir_name/template_out"
208
    );
209
    $cmd .= sprintf(' > %s 2<&1', "buda_batches/errfile");
210
    $h = popen($cmd, "w");
211
    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...
212
    fwrite($h, $job_cmds);
213
    $ret = pclose($h);
214
    if ($ret) {
215
        echo $cmd;
216
        echo "\n\n";
217
        echo $job_cmds;
218
        echo "\n\n";
219
        readfile("../../buda_batches/errfile");
220
        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...
221
        error_page("create_work failed: $x");
0 ignored issues
show
Unused Code introduced by
error_page('create_work failed: '.$x) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
222
    }
223
}
224
225
///////////////// TEMPLATE CREATION //////////////
226
227
function file_ref_in($fname) {
228
    return(sprintf(
229
'      <file_ref>
230
          <open_name>%s</open_name>
231
          <copy_file/>
232
       </file_ref>
233
',
234
        $fname
235
    ));
236
}
237
function file_info_out($i) {
238
    return sprintf(
239
'    <file_info>
240
        <name><OUTFILE_%d/></name>
241
        <generated_locally/>
242
        <upload_when_present/>
243
        <max_nbytes>5000000</max_nbytes>
244
        <url><UPLOAD_URL/></url>
245
    </file_info>
246
',
247
        $i
248
    );
249
}
250
251
function file_ref_out($i, $fname) {
252
    return sprintf(
253
'        <file_ref>
254
            <file_name><OUTFILE_%d/></file_name>
255
            <open_name>%s</open_name>
256
            <copy_file/>
257
        </file_ref>
258
',      $i, $fname
259
    );
260
}
261
262
// create templates and put them in batch dir
263
//
264
function create_templates($variant_desc, $batch_dir) {
265
    // input template
266
    //
267
    $x = "<input_template>\n";
268
    $ninfiles = 1 + count($variant_desc->input_file_names) + count($variant_desc->app_files);
269
    for ($i=0; $i<$ninfiles; $i++) {
270
        $x .= "   <file_info>\n      <no_delete/>\n   </file_info>\n";
271
    }
272
    $x .= "   <workunit>\n";
273
    $x .= file_ref_in($variant_desc->dockerfile);
274
    foreach ($variant_desc->app_files as $fname) {
275
        $x .= file_ref_in($fname);
276
    }
277
    foreach ($variant_desc->input_file_names as $fname) {
278
        $x .= file_ref_in($fname);
279
    }
280
    $x .= "   </workunit>\n<input_template>\n";
281
    file_put_contents("$batch_dir/template_in", $x);
282
283
    // output template
284
    //
285
    $x = "<output_template>\n";
286
    $i = 0;
287
    foreach ($variant_desc->output_file_names as $fname) {
288
        $x .= file_info_out($i++);
289
    }
290
    $x .= "   <result>\n";
291
    $i = 0;
292
    foreach ($variant_desc->output_file_names as $fname) {
293
        $x .= file_ref_out($i++, $fname);
294
    }
295
    $x .= "   </result>\n</output_template>\n";
296
    file_put_contents("$batch_dir/template_out", $x);
297
}
298
299
function handle_submit($user) {
300
    $boinc_app = BoincApp::lookup("name='buda'");
301
    if (!$boinc_app) {
302
        error_page("No buda app found");
303
    }
304
    $app = get_str('app');
305
    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...
306
    $variant = get_str('variant');
307
    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...
308
    $batch_file = get_str('batch_file');
309
    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...
310
311
    $variant_dir = "../../buda_apps/$app/$variant";
312
    $variant_desc = json_decode(
313
        file_get_contents("$variant_dir/variant.json")
314
    );
315
316
    // unzip batch file into temp dir
317
    $batch_dir_name = unzip_batch_file($user, $batch_file);
318
    $batch_dir = "../../buda_batches/$batch_dir_name";
319
320
    // scan batch dir; validate and return struct
321
    $batch_desc = parse_batch_dir($batch_dir, $variant_desc);
322
323
    create_templates($variant_desc, $batch_dir);
324
325
    $batch = create_batch(
326
        $user, count($batch_desc->jobs), $boinc_app, $app, $variant
327
    );
328
329
    // stage input files and record the physical names
330
    //
331
    stage_input_files($batch_dir, $batch_desc, $batch->id);
332
333
    create_jobs(
334
        $variant_desc, $batch_desc, $batch->id, $boinc_app, $batch_dir_name
335
    );
336
337
    // mark batch as in progress
338
    //
339
    $batch->update(sprintf('state=%d', BATCH_STATE_IN_PROGRESS));
340
341
    // clean up batch dir
342
    //
343
    //system("rm -rf $batch_dir");
344
345
    header("Location: submit.php?action=query_batch&batch_id=$batch->id");
346
}
347
348
$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...
349
$action = get_str('action', true);
350
if ($action == 'submit') {
351
    handle_submit($user);
352
} else {
353
    submit_form($user);
354
}
355
356
?>
357