. // Web RPCs for managing job input files on the server. // // Issues: // // 1) how are files named? // Their name is a function of their MD5. // This eliminates issues related to file immutability // // 2) how do we keep track of the files? // In the MySQL database, in a table called "job_file". // Each row describes a file currently on the server. // In addition, we maintain a table "batch_file_assoc" to record // that a file is used by a particular batch. // (Note: the association could be at the job level instead. // but this way is more efficient if many jobs in a batch use // a particular file.) // // 3) how do we clean up unused files? // A daemon (job_file_deleter) deletes files for which // - the delete date (if given) is in the past, and // - there are no associations to active batches // // 4) what are the RPC operations? // query_files // in: // authenticator // list of MD5s // batch ID (optional) // new delete time (optional) // out: // error message, // or list of files (indices in the MD5 list) not present on server // action: for each MD5 in in the input list: // if present on server // update delete time // create batch/file association // add MD5 to output list // upload_files // in: // authenticator // delete time (optional) // batch ID (optional) // list of MD5s // files (as multipart attachments) // out: // error message, or success // action: // for each file in list // move to project download dir w/ appropriate name // create job_files record // create batch_file_assoc record if needed error_reporting(E_ALL); ini_set('display_errors', true); ini_set('display_startup_errors', true); require_once("../inc/boinc_db.inc"); require_once("../inc/submit_db.inc"); require_once("../inc/dir_hier.inc"); require_once("../inc/xml.inc"); require_once("../inc/submit_util.inc"); function upload_error_description($errno) { switch($errno) { case UPLOAD_ERR_INI_SIZE: return "The uploaded file exceeds upload_max_filesize of php.ini."; break; case UPLOAD_ERR_FORM_SIZE: return "The uploaded file exceeds the MAX_FILE_SIZE specified in the HTML form."; break; case UPLOAD_ERR_PARTIAL: return "The uploaded file was only partially uploaded."; break; case UPLOAD_ERR_NO_FILE: return "No file was uploaded."; break; case UPLOAD_ERR_NO_TMP_DIR: return "Missing a temporary folder."; break; case UPLOAD_ERR_CANT_WRITE: return "Failed to write file to disk."; break; case UPLOAD_ERR_EXTENSION: return "A PHP extension stopped the file upload."; break; } } function query_files($r) { xml_start_tag("query_files"); list($user, $user_submit) = authenticate_user($r, null); $absent_files = array(); $now = time(); $delete_time = (int)$r->delete_time; $batch_id = (int)$r->batch_id; $fanout = parse_config(get_config(), ""); $phys_names= array(); foreach($r->phys_name as $f) { $phys_names[] = (string)$f; } $i = 0; foreach($phys_names as $fname) { $path = dir_hier_path($fname, project_dir() . "/download", $fanout); // if the job_file record is there, // update the delete time first to avoid race condition // with job file deleter // $job_file = BoincJobFile::lookup_name($fname); if ($job_file && $job_file->delete_time < $delete_time) { $retval = $job_file->update("delete_time=$delete_time"); if ($retval) { xml_error(-1, "job_file->update() failed: ".BoincDb::error()); } } if (file_exists($path)) { // create the DB record if needed // if ($job_file) { $jf_id = $job_file->id; } else { $jf_id = BoincJobFile::insert( "(name, create_time, delete_time) values ('$fname', $now, $delete_time)" ); if (!$jf_id) { xml_error(-1, "query_files(): BoincJobFile::insert($fname) failed: ".BoincDb::error()); } } // create batch association if needed // if ($batch_id) { BoincBatchFileAssoc::insert( "(batch_id, job_file_id) values ($batch_id, $jf_id)" ); // this return error if assoc already exists; ignore } } else { if ($job_file) { $ret = $job_file->delete(); if (!$ret) { xml_error(-1, "BoincJobFile::delete() failed: ".BoincDb::error() ); } } $absent_files[] = $i; } $i++; } echo "\n"; foreach ($absent_files as $i) { echo "$i\n"; } echo " "; } function delete_uploaded_files() { foreach ($_FILES as $f) { unlink($f['tmp_name']); } } function upload_files($r) { xml_start_tag("upload_files"); list($user, $user_submit) = authenticate_user($r, null); $fanout = parse_config(get_config(), ""); $delete_time = (int)$r->delete_time; $batch_id = (int)$r->batch_id; //print_r($_FILES); if (count($_FILES) != count($r->phys_name)) { delete_uploaded_files(); xml_error(-1, sprintf("# of uploaded files (%d) doesn't agree with request (%d)", count($_FILES), count($r->phys_name) ) ); } $phys_names = array(); foreach ($r->phys_name as $cs) { $phys_names[] = (string)$cs; } foreach ($_FILES as $f) { $name = $f['name']; $tmp_name = $f['tmp_name']; if ($f['error'] != UPLOAD_ERR_OK) { delete_uploaded_files(); $reason = upload_error_description($f['error']); xml_error(-1, "$name upload failed because: $reason"); } if (!is_uploaded_file($tmp_name)) { delete_uploaded_files(); xml_error(-1, "$name was not uploaded correctly"); } } $i = 0; $now = time(); foreach ($_FILES as $f) { $tmp_name = $f['tmp_name']; $fname = $phys_names[$i]; $path = dir_hier_path($fname, project_dir() . "/download", $fanout); switch(check_download_file($tmp_name, $path)) { case 0: break; case 1: if (!move_uploaded_file($tmp_name, $path)) { xml_error(-1, "could not move $tmp_name to $path"); } touch("$path.md5"); break; case -1: xml_error(-1, "file immutability violation for $fname"); case -2: xml_error(-1, "file operation failed; check permissions in download/*"); } $jf_id = BoincJobFile::insert( "(name, create_time, delete_time) values ('$fname', $now, $delete_time)" ); if (!$jf_id) { xml_error(-1, "BoincJobFile::insert($fname) failed: ".BoincDb::error()); } if ($batch_id) { BoincBatchFileAssoc::insert( "(batch_id, job_file_id) values ($batch_id, $jf_id)" ); } $i++; } echo " "; } if (0) { $r = simplexml_load_string("\n0\n 80bf244b43fb5d39541ea7011883b7e0\n a6037b05afb05f36e6a85a7c5138cbc1\n\n "); submit_batch($r); exit; } if (0) { $r = simplexml_load_string("\n157f96a018b0b2f2b466e2ce3c7f54db\n1\n80bf244b43fb5d39541ea7011883b7e0\na6037b05afb05f36e6a85a7c5138cbc1\n"); upload_files($r); exit; } $request_log = parse_config(get_config(), ""); if ($request_log) { $request_log_dir = parse_config(get_config(), ""); if ($request_log_dir) { $request_log = $request_log_dir . "/" . $request_log; } if ($file = fopen($request_log, "a+")) { fwrite($file, "\n\n" . $_POST['request'] . "\n\n"); fclose($file); } } xml_header(); $req = $_POST['request']; $r = simplexml_load_string($req); if (!$r) { xml_error(-1, "can't parse request message: $req", __FILE__, __LINE__); } switch($r->getName()) { case 'query_files': query_files($r); break; case 'upload_files': upload_files($r); break; default: xml_error(-1, "no such action"); } ?>