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 | // interface for: |
||||||
20 | // - viewing details of BUDA science apps and variants |
||||||
21 | // - managing these if user has permission |
||||||
22 | // |
||||||
23 | // in the following, 'app' means BUDA science app |
||||||
24 | // and 'variant' means a variant of one of these (e.g. CPU, GPU) |
||||||
25 | |||||||
26 | require_once('../inc/util.inc'); |
||||||
27 | require_once('../inc/sandbox.inc'); |
||||||
28 | require_once('../inc/keywords.inc'); |
||||||
29 | require_once('../inc/submit_util.inc'); |
||||||
30 | require_once('../inc/buda.inc'); |
||||||
31 | |||||||
32 | display_errors(); |
||||||
33 | |||||||
34 | $buda_root = "../../buda_apps"; |
||||||
35 | |||||||
36 | // scan BUDA apps and variants, and write a file 'buda_plan_classes' |
||||||
37 | // in the project dir with list of plan classes |
||||||
38 | // |
||||||
39 | function write_plan_class_file() { |
||||||
40 | $pcs = []; |
||||||
41 | global $buda_root; |
||||||
42 | $apps = get_buda_apps(); |
||||||
43 | foreach ($apps as $app) { |
||||||
44 | $vars = get_buda_variants($app); |
||||||
45 | $pcs = array_merge($pcs, $vars); |
||||||
46 | } |
||||||
47 | $pcs = array_unique($pcs); |
||||||
48 | file_put_contents( |
||||||
49 | "../../buda_plan_classes", |
||||||
50 | implode("\n", $pcs)."\n" |
||||||
51 | ); |
||||||
52 | } |
||||||
53 | |||||||
54 | // show list of BUDA apps and variants, |
||||||
55 | // w/ buttons for adding and deleting |
||||||
56 | // |
||||||
57 | function app_list($notice=null) { |
||||||
58 | global $buda_root; |
||||||
59 | if (!is_dir($buda_root)) { |
||||||
60 | mkdir($buda_root); |
||||||
61 | } |
||||||
62 | page_head('Manage BUDA apps'); |
||||||
63 | if ($notice) { |
||||||
64 | echo "$notice <p>\n"; |
||||||
65 | } |
||||||
66 | text_start(); |
||||||
67 | echo " |
||||||
68 | <p>BUDA lets you submit Docker jobs using a web interface. |
||||||
69 | <a href=https://github.com/BOINC/boinc/wiki/BUDA-overview>Learn more</a>. |
||||||
70 | <p> |
||||||
71 | <h3>BUDA science apps</h3> |
||||||
72 | "; |
||||||
73 | |||||||
74 | $apps = get_buda_apps(); |
||||||
75 | foreach ($apps as $app) { |
||||||
76 | show_app($app); |
||||||
77 | } |
||||||
78 | echo '<hr>'; |
||||||
79 | show_button_small('buda.php?action=app_form', 'Add science app'); |
||||||
80 | text_end(); |
||||||
81 | page_tail(); |
||||||
82 | } |
||||||
83 | |||||||
84 | function show_app($dir) { |
||||||
85 | global $buda_root; |
||||||
86 | $desc = null; |
||||||
87 | $desc_path = "$buda_root/$dir/desc.json"; |
||||||
88 | $desc = json_decode(file_get_contents($desc_path)); |
||||||
89 | echo '<hr>'; |
||||||
90 | echo sprintf('<h3>%s</h3>', $desc->long_name); |
||||||
91 | echo sprintf('<a href=buda.php?action=app_details&name=%s>Details</a>', |
||||||
92 | $desc->name |
||||||
93 | ); |
||||||
94 | $vars = get_buda_variants($dir); |
||||||
95 | if ($vars) { |
||||||
96 | echo "<p>Variants:<ul>"; |
||||||
97 | foreach ($vars as $var) { |
||||||
98 | echo sprintf( |
||||||
99 | '<li><a href=buda.php?action=variant_view&app=%s&variant=%s>%s</a>', |
||||||
100 | $dir, $var, $var |
||||||
101 | ); |
||||||
102 | } |
||||||
103 | echo '</ul>'; |
||||||
104 | } else { |
||||||
105 | echo '<p>No variants'; |
||||||
106 | } |
||||||
107 | echo "<p>"; |
||||||
108 | } |
||||||
109 | |||||||
110 | function file_row($app, $variant, $dir, $f) { |
||||||
111 | [$md5, $size] = parse_info_file("$dir/.md5/$f"); |
||||||
112 | table_row( |
||||||
113 | "<a href=buda.php?action=view_file&app=$app&variant=$variant&fname=$f>$f</a>", |
||||||
114 | $size, |
||||||
115 | $md5 |
||||||
116 | ); |
||||||
117 | } |
||||||
118 | |||||||
119 | function variant_view() { |
||||||
120 | global $buda_root, $manage_access; |
||||||
121 | $app = get_str('app'); |
||||||
122 | if (!is_valid_filename($app)) die('bad arg'); |
||||||
0 ignored issues
–
show
|
|||||||
123 | $variant = get_str('variant'); |
||||||
124 | if (!is_valid_filename($variant)) die('bad arg'); |
||||||
0 ignored issues
–
show
|
|||||||
125 | page_head("BUDA: variant '$variant' of science app '$app'"); |
||||||
126 | $dir = "$buda_root/$app/$variant"; |
||||||
127 | $desc = json_decode(file_get_contents("$dir/variant.json")); |
||||||
128 | echo "<h3>Files</h3>"; |
||||||
129 | start_table(); |
||||||
130 | table_header('Dockerfile', 'size', 'md5'); |
||||||
131 | file_row($app, $variant, $dir, $desc->dockerfile); |
||||||
132 | table_header('App files', '', ''); |
||||||
133 | foreach ($desc->app_files as $f) { |
||||||
134 | file_row($app, $variant, $dir, $f); |
||||||
135 | } |
||||||
136 | table_header('Auto-generated files', '', ''); |
||||||
137 | file_row($app, $variant, $dir, 'template_in'); |
||||||
138 | file_row($app, $variant, $dir, 'template_out'); |
||||||
139 | file_row($app, $variant, $dir, 'variant.json'); |
||||||
140 | end_table(); |
||||||
141 | echo '<hr>'; |
||||||
142 | |||||||
143 | start_table(); |
||||||
144 | row2( |
||||||
145 | 'Input filenames:', |
||||||
146 | implode(',', $desc->input_file_names) |
||||||
147 | ); |
||||||
148 | row2( |
||||||
149 | 'Output filenames:', |
||||||
150 | implode(',', $desc->output_file_names) |
||||||
151 | ); |
||||||
152 | if (!empty($desc->max_total)) { |
||||||
153 | row2('Max total instances per job:', $desc->max_total); |
||||||
154 | } else { |
||||||
155 | row2('Max total instances per job:', '1'); |
||||||
156 | } |
||||||
157 | if (!empty($desc->min_nsuccess)) { |
||||||
158 | row2('Target successful instances per job:', $desc->min_nsuccess); |
||||||
159 | } else { |
||||||
160 | row2('Target successful instances per job:', '1'); |
||||||
161 | } |
||||||
162 | if (!empty($desc->max_delay_days)) { |
||||||
163 | row2('Max job turnaround time, days:', $desc->max_delay_days); |
||||||
164 | } else { |
||||||
165 | row2('Max job turnaround time, days:', '7'); |
||||||
166 | } |
||||||
167 | end_table(); |
||||||
168 | |||||||
169 | if ($manage_access) { |
||||||
170 | echo '<p>'; |
||||||
171 | show_button_small( |
||||||
172 | "buda.php?action=variant_form&app=$app&variant=$variant", |
||||||
173 | 'Edit variant' |
||||||
174 | ); |
||||||
175 | echo '<p>'; |
||||||
176 | show_button( |
||||||
177 | "buda.php?action=variant_delete&app=$app&variant=$variant", |
||||||
178 | 'Delete variant', |
||||||
179 | null, |
||||||
180 | 'btn btn-xs btn-warning' |
||||||
181 | ); |
||||||
182 | } |
||||||
183 | page_tail(); |
||||||
184 | } |
||||||
185 | |||||||
186 | // form for creating/editing app variant. |
||||||
187 | // |
||||||
188 | function variant_form($user) { |
||||||
189 | $sbitems = sandbox_select_items($user); |
||||||
190 | $app = get_str('app'); |
||||||
191 | if (!is_valid_filename($app)) die('bad arg'); |
||||||
0 ignored issues
–
show
|
|||||||
192 | $variant = get_str('variant', true); |
||||||
193 | |||||||
194 | if ($variant) { |
||||||
195 | global $buda_root; |
||||||
196 | $variant_dir = "$buda_root/$app/$variant"; |
||||||
197 | $variant_desc = json_decode( |
||||||
198 | file_get_contents("$variant_dir/variant.json") |
||||||
199 | ); |
||||||
200 | if (!$variant_desc) error_page('no such variant'); |
||||||
201 | page_head_select2("Edit variant $variant of BUDA app $app"); |
||||||
202 | } else { |
||||||
203 | $variant_desc = new StdClass; |
||||||
204 | $variant_desc->dockerfile = ''; |
||||||
205 | $variant_desc->app_files = []; |
||||||
206 | $variant_desc->input_file_names = []; |
||||||
207 | $variant_desc->output_file_names = []; |
||||||
208 | $variant_desc->min_nsuccess = 1; |
||||||
209 | $variant_desc->max_total = 1; |
||||||
210 | $variant_desc->max_delay_days = 7; |
||||||
211 | page_head_select2("Create a variant of BUDA app $app"); |
||||||
212 | } |
||||||
213 | echo " |
||||||
214 | Details are <a href=https://github.com/BOINC/boinc/wiki/BUDA-job-submission#adding-a-variant>here</a>. |
||||||
215 | "; |
||||||
216 | $sb = '<br><small>From your <a href=sandbox.php>file sandbox</a></small>'; |
||||||
217 | $pc = '<br><small>Specify |
||||||
218 | <a href=https://github.com/BOINC/boinc/wiki/AppPlan>GPU and other requirements</a>'; |
||||||
219 | form_start('buda.php'); |
||||||
220 | form_input_hidden('app', $app); |
||||||
221 | form_input_hidden('action', 'variant_action'); |
||||||
222 | if ($variant) { |
||||||
223 | form_input_hidden('variant', $variant); |
||||||
224 | form_input_hidden('edit', 'true'); |
||||||
225 | } else { |
||||||
226 | form_input_text("Plan class$pc", 'variant', $variant); |
||||||
227 | } |
||||||
228 | form_select("Dockerfile$sb", 'dockerfile', $sbitems, $variant_desc->dockerfile); |
||||||
229 | form_select2_multi("Application files$sb", 'app_files', $sbitems, $variant_desc->app_files); |
||||||
230 | form_input_text( |
||||||
231 | 'Input file names<br><small>Space-separated</small>', |
||||||
232 | 'input_file_names', |
||||||
233 | implode(' ', $variant_desc->input_file_names) |
||||||
234 | ); |
||||||
235 | form_input_text( |
||||||
236 | 'Output file names<br><small>Space-separated</small>', |
||||||
237 | 'output_file_names', |
||||||
238 | implode(' ', $variant_desc->output_file_names) |
||||||
239 | ); |
||||||
240 | form_input_text( |
||||||
241 | 'Run at most this many total instances of each job', |
||||||
242 | 'max_total', |
||||||
243 | $variant_desc->max_total |
||||||
244 | ); |
||||||
245 | form_input_text( |
||||||
246 | 'Get this many successful instances of each job |
||||||
247 | <br><small>(subject to the above limit)</small> |
||||||
248 | ', |
||||||
249 | 'min_nsuccess', |
||||||
250 | $variant_desc->min_nsuccess |
||||||
251 | ); |
||||||
252 | form_input_text( |
||||||
253 | 'Max job turnaround time, days', |
||||||
254 | 'max_delay_days', |
||||||
255 | $variant_desc->max_delay_days |
||||||
256 | ); |
||||||
257 | form_submit('OK'); |
||||||
258 | form_end(); |
||||||
259 | page_tail(); |
||||||
260 | } |
||||||
261 | |||||||
262 | function buda_file_phys_name($app, $variant, $md5) { |
||||||
263 | return sprintf('buda_%s_%s_%s', $app, $variant, $md5); |
||||||
264 | } |
||||||
265 | |||||||
266 | // copy file from sandbox to variant dir, and stage to download hier |
||||||
267 | // |
||||||
268 | function copy_and_stage_file($user, $fname, $dir, $app, $variant) { |
||||||
269 | copy_sandbox_file($user, $fname, $dir); |
||||||
270 | [$md5, $size] = parse_info_file("$dir/.md5/$fname"); |
||||||
271 | $phys_name = buda_file_phys_name($app, $variant, $md5); |
||||||
272 | stage_file_aux("$dir/$fname", $md5, $size, $phys_name); |
||||||
273 | return $phys_name; |
||||||
274 | } |
||||||
275 | |||||||
276 | // create templates and put them in variant dir |
||||||
277 | // |
||||||
278 | function create_templates($variant, $variant_desc, $dir) { |
||||||
279 | // input template |
||||||
280 | // |
||||||
281 | $x = "<input_template>\n"; |
||||||
282 | $ninfiles = 1 + count($variant_desc->input_file_names) + count($variant_desc->app_files); |
||||||
283 | for ($i=0; $i<$ninfiles; $i++) { |
||||||
284 | $x .= " <file_info>\n <sticky/>\n <no_delete/>\n <executable/>\n </file_info>\n"; |
||||||
285 | } |
||||||
286 | $x .= " <workunit>\n"; |
||||||
287 | $x .= file_ref_in($variant_desc->dockerfile); |
||||||
288 | foreach ($variant_desc->app_files as $fname) { |
||||||
289 | $x .= file_ref_in($fname); |
||||||
290 | } |
||||||
291 | foreach ($variant_desc->input_file_names as $fname) { |
||||||
292 | $x .= file_ref_in($fname); |
||||||
293 | } |
||||||
294 | if (strstr($variant, 'cpu')) { |
||||||
295 | $x .= " <plan_class></plan_class>\n"; |
||||||
296 | } else { |
||||||
297 | $x .= " <plan_class>$variant</plan_class>\n"; |
||||||
298 | } |
||||||
299 | |||||||
300 | // replication params |
||||||
301 | // |
||||||
302 | $x .= sprintf(" <target_nresults>%d</target_nresults>\n", |
||||||
303 | $variant_desc->min_nsuccess |
||||||
304 | ); |
||||||
305 | $x .= sprintf(" <min_quorum>%d</min_quorum>\n", |
||||||
306 | $variant_desc->min_nsuccess |
||||||
307 | ); |
||||||
308 | $x .= sprintf(" <max_total_results>%d</max_total_results>\n", |
||||||
309 | $variant_desc->max_total |
||||||
310 | ); |
||||||
311 | |||||||
312 | $x .= sprintf(" <max_delay>%f</max_delay>\n", |
||||||
313 | $variant_desc->max_delay_days * 86400. |
||||||
314 | ); |
||||||
315 | |||||||
316 | $x .= " </workunit>\n<input_template>\n"; |
||||||
317 | file_put_contents("$dir/template_in", $x); |
||||||
318 | |||||||
319 | // output template |
||||||
320 | // |
||||||
321 | $x = "<output_template>\n"; |
||||||
322 | $i = 0; |
||||||
323 | foreach ($variant_desc->output_file_names as $fname) { |
||||||
324 | $x .= file_info_out($i++); |
||||||
325 | } |
||||||
326 | $x .= " <result>\n"; |
||||||
327 | $i = 0; |
||||||
328 | foreach ($variant_desc->output_file_names as $fname) { |
||||||
329 | $x .= file_ref_out($i++, $fname); |
||||||
330 | } |
||||||
331 | $x .= " </result>\n</output_template>\n"; |
||||||
332 | file_put_contents("$dir/template_out", $x); |
||||||
333 | } |
||||||
334 | |||||||
335 | // create variant |
||||||
336 | // |
||||||
337 | function variant_action($user) { |
||||||
338 | global $buda_root; |
||||||
339 | $variant = get_str('variant'); |
||||||
340 | if (!$variant) $variant = 'cpu'; |
||||||
341 | if (!is_valid_filename($variant)) { |
||||||
342 | error_page(filename_rules()); |
||||||
343 | } |
||||||
344 | $app = get_str('app'); |
||||||
345 | if (!is_valid_filename($app)) die('bad arg'); |
||||||
0 ignored issues
–
show
|
|||||||
346 | $dockerfile = get_str('dockerfile'); |
||||||
347 | if (!is_valid_filename($dockerfile)) { |
||||||
348 | error_page("Invalid dockerfile name: ".filename_rules()); |
||||||
349 | } |
||||||
350 | $app_files = get_array('app_files'); |
||||||
351 | foreach ($app_files as $fname) { |
||||||
352 | if (!is_valid_filename($fname)) { |
||||||
353 | error_page("Invalid app file name: ".filename_rules()); |
||||||
354 | } |
||||||
355 | } |
||||||
356 | $min_nsuccess = get_int('min_nsuccess'); |
||||||
357 | if ($min_nsuccess <= 0) { |
||||||
358 | error_page('Must specify a positive number of successful instances.'); |
||||||
359 | } |
||||||
360 | $max_total = get_int('max_total'); |
||||||
361 | if ($max_total <= 0) { |
||||||
362 | error_page('Must specify a positive max number of instances.'); |
||||||
363 | } |
||||||
364 | if ($min_nsuccess > $max_total) { |
||||||
365 | error_page('Target # of successful instances must be <= max total'); |
||||||
366 | } |
||||||
367 | $max_delay_days = get_str('max_delay_days'); |
||||||
368 | if (!is_numeric($max_delay_days)) { |
||||||
369 | error_page('Must specify max delay'); |
||||||
370 | } |
||||||
371 | $max_delay_days = floatval($max_delay_days); |
||||||
372 | if ($max_delay_days <= 0) { |
||||||
373 | error_page('Must specify positive max delay'); |
||||||
374 | } |
||||||
375 | |||||||
376 | $input_file_names = get_str('input_file_names', true); |
||||||
377 | if ($input_file_names) { |
||||||
378 | $input_file_names = explode(' ', $input_file_names); |
||||||
379 | foreach ($input_file_names as $fname) { |
||||||
380 | if (!is_valid_filename($fname)) { |
||||||
381 | error_page("Invalid input file name: ".filename_rules()); |
||||||
382 | } |
||||||
383 | } |
||||||
384 | } else { |
||||||
385 | $input_file_names = []; |
||||||
386 | } |
||||||
387 | |||||||
388 | $output_file_names = get_str('output_file_names', true); |
||||||
389 | if ($output_file_names) { |
||||||
390 | $output_file_names = explode(' ', $output_file_names); |
||||||
391 | foreach ($output_file_names as $fname) { |
||||||
392 | if (!is_valid_filename($fname)) { |
||||||
393 | error_page("Invalid output file name: ".filename_rules()); |
||||||
394 | } |
||||||
395 | } |
||||||
396 | } else { |
||||||
397 | $output_file_names = []; |
||||||
398 | } |
||||||
399 | |||||||
400 | $dir = "$buda_root/$app/$variant"; |
||||||
401 | if (get_str('edit', true)) { |
||||||
402 | system("rm -r $dir"); |
||||||
403 | } else { |
||||||
404 | if (file_exists($dir)) { |
||||||
405 | error_page("Variant '$variant' already exists."); |
||||||
406 | } |
||||||
407 | } |
||||||
408 | mkdir($dir); |
||||||
409 | mkdir("$dir/.md5"); |
||||||
410 | |||||||
411 | // collect variant params into a struct |
||||||
412 | // |
||||||
413 | $desc = new StdClass; |
||||||
414 | $desc->dockerfile = $dockerfile; |
||||||
415 | $desc->app_files = $app_files; |
||||||
416 | $desc->input_file_names = $input_file_names; |
||||||
417 | $desc->output_file_names = $output_file_names; |
||||||
418 | $desc->min_nsuccess = $min_nsuccess; |
||||||
419 | $desc->max_total = $max_total; |
||||||
420 | $desc->max_delay_days = $max_delay_days; |
||||||
421 | |||||||
422 | // copy files from sandbox to variant dir |
||||||
423 | // |
||||||
424 | $pname = copy_and_stage_file($user, $dockerfile, $dir, $app, $variant); |
||||||
425 | $desc->dockerfile_phys = $pname; |
||||||
426 | $desc->app_files_phys = []; |
||||||
427 | foreach ($app_files as $fname) { |
||||||
428 | $pname = copy_and_stage_file($user, $fname, $dir, $app, $variant); |
||||||
429 | $desc->app_files_phys[] = $pname; |
||||||
430 | } |
||||||
431 | |||||||
432 | // write variant params to a JSON file |
||||||
433 | // |
||||||
434 | file_put_contents( |
||||||
435 | "$dir/variant.json", |
||||||
436 | json_encode($desc, JSON_PRETTY_PRINT) |
||||||
437 | ); |
||||||
438 | |||||||
439 | create_templates($variant, $desc, $dir); |
||||||
440 | |||||||
441 | // Note: we don't currently allow indirect file access. |
||||||
442 | // If we did, we'd need to create job.toml to mount project dir |
||||||
443 | |||||||
444 | app_list("Variant $variant added for app $app."); |
||||||
445 | } |
||||||
446 | |||||||
447 | function variant_delete() { |
||||||
448 | global $buda_root; |
||||||
449 | $app = get_str('app'); |
||||||
450 | if (!is_valid_filename($app)) die('bad arg'); |
||||||
0 ignored issues
–
show
|
|||||||
451 | $variant = get_str('variant'); |
||||||
452 | if (!is_valid_filename($variant)) die('bad arg'); |
||||||
0 ignored issues
–
show
|
|||||||
453 | $confirmed = get_str('confirmed', true); |
||||||
454 | if ($confirmed) { |
||||||
455 | $dir = "$buda_root/$app/$variant"; |
||||||
456 | if (!file_exists($dir)) error_page('no such variant'); |
||||||
457 | // delete staged files |
||||||
458 | // |
||||||
459 | foreach (scandir("$dir/.md5") as $fname) { |
||||||
460 | if ($fname[0] == '.') continue; |
||||||
461 | [$md5, $size] = parse_info_file("$dir/.md5/$fname"); |
||||||
462 | $phys_name = buda_file_phys_name($app, $variant, $md5); |
||||||
463 | $phys_path = download_hier_path($phys_name); |
||||||
464 | unlink($phys_path); |
||||||
465 | unlink("$phys_path.md5"); |
||||||
466 | } |
||||||
467 | system("rm -r $buda_root/$app/$variant", $ret); |
||||||
468 | if ($ret) { |
||||||
469 | error_page("delete failed"); |
||||||
470 | } |
||||||
471 | $notice = "Variant $variant of app $app removed."; |
||||||
472 | app_list($notice); |
||||||
473 | } else { |
||||||
474 | page_head("Confirm"); |
||||||
475 | echo "<p>Are you sure you want to delete variant $variant of BUDA app $app? <p>"; |
||||||
476 | show_button( |
||||||
477 | "buda.php?action=variant_delete&app=$app&variant=$variant&confirmed=yes", |
||||||
478 | "Yes" |
||||||
479 | ); |
||||||
480 | page_tail(); |
||||||
481 | } |
||||||
482 | } |
||||||
483 | |||||||
484 | function app_delete() { |
||||||
485 | global $buda_root; |
||||||
486 | $app = get_str('app'); |
||||||
487 | if (!is_valid_filename($app)) die('bad arg'); |
||||||
0 ignored issues
–
show
|
|||||||
488 | $confirmed = get_str('confirmed', true); |
||||||
489 | if ($confirmed) { |
||||||
490 | $dir = "$buda_root/$app"; |
||||||
491 | if (!file_exists($dir)) error_page('no such app'); |
||||||
492 | $vars = get_buda_variants($app); |
||||||
493 | if ($vars) { |
||||||
494 | error_page("You must delete all variants first."); |
||||||
495 | } |
||||||
496 | system("rm $buda_root/$app/desc.json", $ret); |
||||||
497 | system("rmdir $buda_root/$app", $ret); |
||||||
498 | if ($ret) { |
||||||
499 | error_page('delete failed'); |
||||||
500 | } |
||||||
501 | $notice = "App $app removed."; |
||||||
502 | app_list($notice); |
||||||
503 | } else { |
||||||
504 | page_head('Confirm'); |
||||||
505 | echo "<p>Are you sure you want to delete BUDA science app $app? <p>"; |
||||||
506 | show_button( |
||||||
507 | "buda.php?action=app_delete&app=$app&confirmed=yes", |
||||||
508 | "Yes" |
||||||
509 | ); |
||||||
510 | page_tail(); |
||||||
511 | } |
||||||
512 | } |
||||||
513 | |||||||
514 | function app_form($desc=null) { |
||||||
515 | page_head_select2($desc?"Edit BUDA app $desc->name":'Create BUDA app'); |
||||||
516 | form_start('buda.php'); |
||||||
517 | form_input_hidden('action', 'app_action'); |
||||||
518 | if ($desc) { |
||||||
519 | form_input_hidden('edit_name', $desc->name); |
||||||
520 | form_input_hidden('user_id', $desc->user_id); |
||||||
521 | form_input_hidden('create_time', $desc->create_time); |
||||||
522 | } else { |
||||||
523 | form_input_text('Internal name<br><small>No spaces</small>', 'name'); |
||||||
524 | } |
||||||
525 | form_input_text('User-visible name', 'long_name', |
||||||
526 | $desc?$desc->long_name:null |
||||||
527 | ); |
||||||
528 | form_input_textarea( |
||||||
529 | 'Description<br><small>... of what the app does and of the research goals</small>', |
||||||
530 | 'description', |
||||||
531 | $desc?$desc->description:null |
||||||
532 | ); |
||||||
533 | form_select2_multi('Science keywords', |
||||||
534 | 'sci_kw', |
||||||
535 | keyword_select_options(KW_CATEGORY_SCIENCE), |
||||||
536 | $desc?$desc->sci_kw:null |
||||||
537 | ); |
||||||
538 | form_input_text( |
||||||
539 | 'URL of web page describing app', |
||||||
540 | 'url', |
||||||
541 | !empty($desc->url)?$desc->url:'' |
||||||
542 | ); |
||||||
543 | // don't include location keywords; |
||||||
544 | // various people may submit jobs to this app |
||||||
545 | form_submit('OK'); |
||||||
546 | form_end(); |
||||||
547 | page_tail(); |
||||||
548 | } |
||||||
549 | |||||||
550 | function app_action($user) { |
||||||
551 | global $buda_root; |
||||||
552 | $edit_name = get_str('edit_name', true); |
||||||
553 | $desc = new StdClass; |
||||||
554 | if ($edit_name) { |
||||||
555 | $dir = "$buda_root/$edit_name"; |
||||||
556 | $name = $edit_name; |
||||||
557 | $desc->user_id = get_int('user_id'); |
||||||
558 | $desc->create_time = get_int('create_time'); |
||||||
559 | } else { |
||||||
560 | $name = get_str('name'); |
||||||
561 | if (!is_valid_filename($name)) { |
||||||
562 | error_page(filename_rules()); |
||||||
563 | } |
||||||
564 | $dir = "$buda_root/$name"; |
||||||
565 | if (file_exists($dir)) { |
||||||
566 | error_page("App $name already exists."); |
||||||
567 | } |
||||||
568 | mkdir($dir); |
||||||
569 | $desc->user_id = $user->id; |
||||||
570 | $desc->create_time = time(); |
||||||
571 | } |
||||||
572 | $desc->name = $name; |
||||||
573 | $desc->long_name = get_str('long_name'); |
||||||
574 | $desc->description = get_str('description'); |
||||||
575 | $desc->sci_kw = array_map('intval', get_array('sci_kw')); |
||||||
576 | file_put_contents("$dir/desc.json", json_encode($desc, JSON_PRETTY_PRINT)); |
||||||
577 | header("Location: buda.php"); |
||||||
578 | } |
||||||
579 | |||||||
580 | function view_file() { |
||||||
581 | global $buda_root; |
||||||
582 | $app = get_str('app'); |
||||||
583 | if (!is_valid_filename($app)) die('bad arg'); |
||||||
0 ignored issues
–
show
|
|||||||
584 | $variant = get_str('variant'); |
||||||
585 | if (!is_valid_filename($variant)) die('bad arg'); |
||||||
0 ignored issues
–
show
|
|||||||
586 | $fname = get_str('fname'); |
||||||
587 | if (!is_valid_filename($fname)) die('bad arg'); |
||||||
0 ignored issues
–
show
|
|||||||
588 | echo "<pre>\n"; |
||||||
589 | $x = file_get_contents("$buda_root/$app/$variant/$fname"); |
||||||
590 | echo htmlspecialchars($x); |
||||||
591 | echo "</pre>\n"; |
||||||
592 | } |
||||||
593 | |||||||
594 | function handle_app_edit() { |
||||||
595 | global $buda_root; |
||||||
596 | $name = get_str('name'); |
||||||
597 | app_form(get_buda_desc($name)); |
||||||
598 | } |
||||||
599 | |||||||
600 | function app_details() { |
||||||
601 | global $buda_root, $manage_access; |
||||||
602 | $name = get_str('name'); |
||||||
603 | $desc = get_buda_desc($name); |
||||||
604 | if (!$desc) error_page("no desc file $path"); |
||||||
605 | page_head("BUDA app: $desc->long_name"); |
||||||
606 | start_table(); |
||||||
607 | row2('Internal name', $desc->name); |
||||||
608 | $user = BoincUser::lookup_id($desc->user_id); |
||||||
609 | row2('Creator', |
||||||
610 | sprintf('<a href=show_user.php?userid=%d>%s</a>', |
||||||
611 | $user->id, |
||||||
612 | $user->name |
||||||
613 | ) |
||||||
614 | ); |
||||||
615 | row2('Created', date_str($desc->create_time)); |
||||||
616 | row2('Description', $desc->description); |
||||||
617 | row2('Science keywords', kw_array_to_str($desc->sci_kw)); |
||||||
618 | if ($manage_access) { |
||||||
619 | row2('', |
||||||
620 | button_text_small( |
||||||
621 | sprintf('buda.php?action=%s&name=%s', 'app_edit', $desc->name), |
||||||
622 | 'Edit app info' |
||||||
623 | ) |
||||||
624 | ); |
||||||
625 | } |
||||||
626 | $vars = get_buda_variants($name); |
||||||
627 | if ($vars) { |
||||||
628 | $x = []; |
||||||
629 | foreach ($vars as $var) { |
||||||
630 | $x[] = sprintf('<a href=buda.php?action=variant_view&app=%s&variant=%s>%s</a>', |
||||||
631 | $name, $var, $var |
||||||
632 | ); |
||||||
633 | } |
||||||
634 | row2('Variants', implode('<p>', $x)); |
||||||
635 | if ($manage_access) { |
||||||
636 | row2('', |
||||||
637 | button_text_small( |
||||||
638 | "buda.php?action=variant_form&app=$name", |
||||||
639 | 'Add variant' |
||||||
640 | ) |
||||||
641 | ); |
||||||
642 | } |
||||||
643 | } else if ($manage_access) { |
||||||
644 | row2('Variants', |
||||||
645 | button_text_small( |
||||||
646 | "buda.php?action=variant_form&app=$name", |
||||||
647 | 'Add variant' |
||||||
648 | ) |
||||||
649 | ); |
||||||
650 | row2('', |
||||||
651 | button_text( |
||||||
652 | "buda.php?action=app_delete&app=$name", |
||||||
653 | "Delete app", |
||||||
654 | null, |
||||||
655 | 'btn btn-xs btn-warning' |
||||||
656 | ) |
||||||
657 | ); |
||||||
658 | } |
||||||
659 | end_table(); |
||||||
660 | page_tail(); |
||||||
661 | } |
||||||
662 | |||||||
663 | // Users with manage access to BUDA can add/delete apps and variants. |
||||||
664 | // Others can just view. |
||||||
665 | // Might want to refine this at some point |
||||||
666 | |||||||
667 | $user = get_logged_in_user(); |
||||||
0 ignored issues
–
show
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 The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||||
668 | $buda_app = BoincApp::lookup("name='buda'"); |
||||||
669 | if (!$buda_app) error_page('no buda app'); |
||||||
670 | $manage_access = has_manage_access($user, $buda_app->id); |
||||||
671 | |||||||
672 | $action = get_str('action', true); |
||||||
673 | switch ($action) { |
||||||
674 | case 'app_edit': |
||||||
675 | if (!$manage_access) error_page('no access'); |
||||||
676 | handle_app_edit(); break; |
||||||
677 | case 'app_form': |
||||||
678 | if (!$manage_access) error_page('no access'); |
||||||
679 | app_form(); break; |
||||||
680 | case 'app_action': |
||||||
681 | if (!$manage_access) error_page('no access'); |
||||||
682 | app_action($user); break; |
||||||
683 | case 'app_details': |
||||||
684 | app_details(); break; |
||||||
685 | case 'app_delete': |
||||||
686 | if (!$manage_access) error_page('no access'); |
||||||
687 | app_delete(); break; |
||||||
688 | case 'variant_view': |
||||||
689 | variant_view($user); break; |
||||||
0 ignored issues
–
show
The call to
variant_view() has too many arguments starting with $user .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
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. ![]() |
|||||||
690 | case 'variant_form': |
||||||
691 | if (!$manage_access) error_page('no access'); |
||||||
692 | variant_form($user); break; |
||||||
693 | case 'variant_action': |
||||||
694 | if (!$manage_access) error_page('no access'); |
||||||
695 | variant_action($user); |
||||||
696 | write_plan_class_file(); |
||||||
697 | break; |
||||||
698 | case 'variant_delete': |
||||||
699 | if (!$manage_access) error_page('no access'); |
||||||
700 | variant_delete(); |
||||||
701 | write_plan_class_file(); |
||||||
702 | break; |
||||||
703 | case 'view_file': |
||||||
704 | view_file(); break; |
||||||
705 | case null: |
||||||
706 | app_list(); break; |
||||||
707 | default: |
||||||
0 ignored issues
–
show
|
|||||||
708 | error_page("unknown action"); |
||||||
709 | } |
||||||
710 | |||||||
711 | ?> |
||||||
712 |
In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.