Passed
Pull Request — master (#5918)
by David
13:04
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
    $variant = get_str('variant');
34
35
    $desc = "<br><small>
36
        A zipped directory with one subdirectory per job,
37
        containing the input file(s) for that job
38
        and an optional file <code>cmdline</code>
39
        containing command-line arguments.
40
        See <a href=https://github.com/BOINC/boinc/wiki/Docker-apps>more details</a></small>.
41
    ";
42
    page_head("Submit jobs to $app ($variant)");
43
    form_start('buda_submit.php');
44
    form_input_hidden('action', 'submit');
45
    form_input_hidden('app', $app);
46
    form_input_hidden('variant', $variant);
47
    form_select("Batch zip file $desc", 'batch_file', $sbitems_zip);
48
    form_submit('OK');
49
    form_end();
50
    page_tail();
51
}
52
53
// unzip batch file into a temp dir; return dir name
54
//
55
function unzip_batch_file($user, $batch_file) {
56
    @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

56
    /** @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...
57
    for ($i=0; $i<1000; $i++) {
58
        $batch_dir = "../../buda_batches/$i";
59
        $batch_dir_name = $i;
60
        $ret = @mkdir($batch_dir);
61
        if ($ret) break;
62
    }
63
    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...
64
    $sb_dir = sandbox_dir($user);
65
    if (!file_exists("$sb_dir/$batch_file")) {
66
        error_page("no batch file $batch_file");
67
    }
68
    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...
69
    if ($ret) {
70
        error_page("unzip error: $ret");
71
    }
72
    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...
73
}
74
75
// check validity of batch dir.
76
// top level should have only infiles (shared)
77
// job dirs should have only remaining infiles and possibly cmdline
78
//
79
// return struct describing the batch, and the md5/size of files
80
//
81
function parse_batch_dir($batch_dir, $variant_desc) {
82
    $input_files = $variant_desc->input_file_names;
83
    sort($input_files);
84
    $shared_files = [];
85
    $shared_file_infos = [];
86
    if (is_dir("$batch_dir/shared_input_files")) {
87
        foreach (scandir("$batch_dir/shared_input_files") as $fname) {
88
            if ($fname[0] == '.') continue;
89
            if (!in_array($fname, $input_files)) {
90
                error_page("$fname is not an input file name");
91
            }
92
            $shared_files[] = $fname;
93
            $shared_file_infos[] = get_file_info("$batch_dir/shared_input_files/$fname");
94
        }
95
    }
96
    $unshared_files = array_diff($input_files, $shared_files);
97
    sort($unshared_files);
98
    $jobs = [];
99
    foreach (scandir($batch_dir) as $fname) {
100
        if ($fname[0] == '.') continue;
101
        if ($fname == 'shared_input_files') continue;
102
        if (!is_dir("$batch_dir/$fname")) {
103
            error_page("$batch_dir/$fname is not a directory");
104
        }
105
        $job_files = [];
106
        $cmdline = '';
107
        foreach(scandir("$batch_dir/$fname") as $f2) {
108
            if ($f2[0] == '.') continue;
109
            if ($f2 == 'cmdline') {
110
                $cmdline = file_get_contents("$batch_dir/$f2");
111
            }
112
            if (!in_array($f2, $unshared_files)) {
113
                error_page("$fname/$f2 is not an input file name");
114
            }
115
            $job_files[] = $f2;
116
        }
117
        if (sort($job_files) != $unshared_files) {
118
            error_page("$fname doesn't have all input files");
119
        }
120
121
        $file_infos = [];
122
        foreach ($unshared_files as $f2) {
123
            $file_infos[] = get_file_info("$batch_dir/$fname/$f2");
124
        }
125
126
        $job = new StdClass;
127
        $job->dir = $fname;
128
        $job->cmdline = $cmdline;
129
        $job->file_infos = $file_infos;
130
        $jobs[] = $job;
131
    }
132
    $batch_desc = new StdClass;
133
    $batch_desc->shared_files = $shared_files;
134
    $batch_desc->shared_file_infos = $shared_file_infos;
135
    $batch_desc->unshared_files = $unshared_files;
136
    $batch_desc->jobs = $jobs;
137
    return $batch_desc;
138
}
139
140
function create_batch($user, $njobs, $boinc_app, $app, $variant) {
141
    $now = time();
142
    $batch_name = sprintf('buda_%d_%d', $user->id, $now);
143
    $description = "$app ($variant)";
144
    $batch_id = BoincBatch::insert(sprintf(
145
        "(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)",
146
        $user->id, $now, $njobs, BATCH_STATE_INIT, $batch_name, $boinc_app->id,
147
        $description
148
    ));
149
    return BoincBatch::lookup_id($batch_id);
150
}
151
152
function stage_input_files($batch_dir, $batch_desc, $batch_id) {
153
    $n = count($batch_desc->shared_files);
154
    $batch_desc->shared_files_phys_names = [];
155
    for ($i=0; $i<$n; $i++) {
156
        $path = sprintf('%s/%s', $batch_dir, $batch_desc->shared_files[$i]);
157
        [$md5, $size] = $batch_desc->shared_file_infos[$i];
158
        $phys_name = sprintf('batch_%d_%s', $batch_id, $md5);
159
        stage_file_aux($path, $md5, $size, $phys_name);
160
        $batch_desc->shared_files_phys_names[] = $phys_name;
161
    }
162
    foreach ($batch_desc->jobs as $job) {
163
        $n = count($batch_desc->unshared_files);
164
        $job->phys_names = [];
165
        for ($i=0; $i<$n; $i++) {
166
            $path = sprintf('%s/%s/%s',
167
                $batch_dir, $job->dir, $batch_desc->unshared_files[$i]
168
            );
169
            [$md5, $size] = $job->file_infos[$i];
170
            $phys_name = sprintf('batch_%d_%s', $batch_id, $md5);
171
            stage_file_aux($path, $md5, $size, $phys_name);
172
            $job->phys_names[] = $phys_name;
173
        }
174
    }
175
}
176
177
function create_jobs(
178
    $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...
179
) {
180
    // get list of names of app files
181
    //
182
    $app_file_names = $variant_desc->dockerfile_phys;
183
    foreach ($variant_desc->app_files_phys as $pname) {
184
        $app_file_names .= " $pname";
185
    }
186
    $job_cmds = '';
187
    foreach ($batch_desc->jobs as $job) {
188
        $job_cmd = sprintf('--wu_name batch_%d__job_%s', $batch_id, $job->dir);
189
        if ($job->cmdline) {
190
            $job_cmd .= sprintf(' --command_line "%s"', $job->cmdline);
191
        }
192
        $job_cmd .= " $app_file_names";
193
        foreach ($batch_desc->shared_files_phys_names as $x) {
194
            $job_cmd .= " $x";
195
        }
196
        foreach ($job->phys_names as $x) {
197
            $job_cmd .= " $x";
198
        }
199
        $job_cmds .= "$job_cmd\n";
200
    }
201
    $cmd = sprintf(
202
        'cd ../..; bin/create_work --appname %s --batch %d --stdin --command_line "--dockerfile %s --verbose" --wu_template %s --result_template %s',
203
        $boinc_app->name, $batch_id, $variant_desc->dockerfile,
204
        "buda_batches/$batch_dir_name/template_in",
205
        "buda_batches/$batch_dir_name/template_out"
206
    );
207
    $cmd .= sprintf(' > %s 2<&1', "buda_batches/errfile");
208
    $h = popen($cmd, "w");
209
    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...
210
    fwrite($h, $job_cmds);
211
    $ret = pclose($h);
212
    if ($ret) {
213
        echo $cmd;
214
        echo "\n\n";
215
        echo $job_cmds;
216
        echo "\n\n";
217
        readfile("../../buda_batches/errfile");
218
        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...
219
        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...
220
    }
221
}
222
223
///////////////// TEMPLATE CREATION //////////////
224
225
function file_ref_in($fname) {
226
    return(sprintf(
227
'      <file_ref>
228
          <open_name>%s</open_name>
229
          <copy_file/>
230
       </file_ref>
231
',
232
        $fname
233
    ));
234
}
235
function file_info_out($i) {
236
    return sprintf(
237
'    <file_info>
238
        <name><OUTFILE_%d/></name>
239
        <generated_locally/>
240
        <upload_when_present/>
241
        <max_nbytes>5000000</max_nbytes>
242
        <url><UPLOAD_URL/></url>
243
    </file_info>
244
',
245
        $i
246
    );
247
}
248
249
function file_ref_out($i, $fname) {
250
    return sprintf(
251
'        <file_ref>
252
            <file_name><OUTFILE_%d/></file_name>
253
            <open_name>%s</open_name>
254
            <copy_file/>
255
        </file_ref>
256
',      $i, $fname
257
    );
258
}
259
260
// create templates and put them in batch dir
261
//
262
function create_templates($variant_desc, $batch_dir) {
263
    // input template
264
    //
265
    $x = "<input_template>\n";
266
    $ninfiles = 1 + count($variant_desc->input_file_names) + count($variant_desc->app_files);
267
    for ($i=0; $i<$ninfiles; $i++) {
268
        $x .= "   <file_info>\n      <no_delete/>\n   </file_info>\n";
269
    }
270
    $x .= "   <workunit>\n";
271
    $x .= file_ref_in($variant_desc->dockerfile);
272
    foreach ($variant_desc->app_files as $fname) {
273
        $x .= file_ref_in($fname);
274
    }
275
    foreach ($variant_desc->input_file_names as $fname) {
276
        $x .= file_ref_in($fname);
277
    }
278
    $x .= "   </workunit>\n<input_template>\n";
279
    file_put_contents("$batch_dir/template_in", $x);
280
281
    // output template
282
    //
283
    $x = "<output_template>\n";
284
    $i = 0;
285
    foreach ($variant_desc->output_file_names as $fname) {
286
        $x .= file_info_out($i++);
287
    }
288
    $x .= "   <result>\n";
289
    $i = 0;
290
    foreach ($variant_desc->output_file_names as $fname) {
291
        $x .= file_ref_out($i++, $fname);
292
    }
293
    $x .= "   </result>\n</output_template>\n";
294
    file_put_contents("$batch_dir/template_out", $x);
295
}
296
297
function handle_submit($user) {
298
    $boinc_app = BoincApp::lookup("name='buda'");
299
    if (!$boinc_app) {
300
        error_page("No buda app found");
301
    }
302
    $app = get_str('app');
303
    $variant = get_str('variant');
304
    $batch_file = get_str('batch_file');
305
306
    $variant_dir = "../../buda_apps/$app/$variant";
307
    $variant_desc = json_decode(
308
        file_get_contents("$variant_dir/variant.json")
309
    );
310
311
    // unzip batch file into temp dir
312
    $batch_dir_name = unzip_batch_file($user, $batch_file);
313
    $batch_dir = "../../buda_batches/$batch_dir_name";
314
315
    // scan batch dir; validate and return struct
316
    $batch_desc = parse_batch_dir($batch_dir, $variant_desc);
317
318
    create_templates($variant_desc, $batch_dir);
319
320
    $batch = create_batch(
321
        $user, count($batch_desc->jobs), $boinc_app, $app, $variant
322
    );
323
324
    // stage input files and record the physical names
325
    //
326
    stage_input_files($batch_dir, $batch_desc, $batch->id);
327
328
    create_jobs(
329
        $variant_desc, $batch_desc, $batch->id, $boinc_app, $batch_dir_name
330
    );
331
332
    // mark batch as in progress
333
    //
334
    $batch->update(sprintf('state=%d', BATCH_STATE_IN_PROGRESS));
335
336
    // clean up batch dir
337
    //
338
    //system("rm -rf $batch_dir");
339
340
    header("Location: submit.php?action=query_batch&batch_id=$batch->id");
341
}
342
343
$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...
344
$action = get_str('action', true);
345
if ($action == 'submit') {
346
    handle_submit($user);
347
} else {
348
    submit_form($user);
349
}
350
351
?>
352