// This file is part of BOINC. // http://boinc.berkeley.edu // Copyright (C) 2008 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License // as published by the Free Software Foundation, // either version 3 of the License, or (at your option) any later version. // // BOINC is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with BOINC. If not, see . // GUI RPC server side (the actual RPCs) #ifdef _WIN32 #include "boinc_win.h" #endif #ifdef __APPLE__ #include #endif #ifndef _WIN32 #include "config.h" #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SYS_SOCKET_H #include #endif #include #include #include #include #include #include #endif #include "str_util.h" #include "client_state.h" #include "util.h" #include "error_numbers.h" #include "parse.h" #include "network.h" #include "filesys.h" #include "file_names.h" #include "client_msgs.h" #include "client_state.h" using std::string; using std::vector; static void auth_failure(MIOFILE& fout) { fout.printf("\n"); } void GUI_RPC_CONN::handle_auth1(MIOFILE& fout) { sprintf(nonce, "%f", dtime()); fout.printf("%s\n", nonce); } int GUI_RPC_CONN::handle_auth2(char* buf, MIOFILE& fout) { char nonce_hash[256], nonce_hash_correct[256], buf2[256]; if (!parse_str(buf, "", nonce_hash, 256)) { auth_failure(fout); return ERR_AUTHENTICATOR; } sprintf(buf2, "%s%s", nonce, gstate.gui_rpcs.password); md5_block((const unsigned char*)buf2, (int)strlen(buf2), nonce_hash_correct); if (strcmp(nonce_hash, nonce_hash_correct)) { auth_failure(fout); return ERR_AUTHENTICATOR; } fout.printf("\n"); auth_needed = false; return 0; } // client passes its version, but ignore it for now // static void handle_exchange_versions(MIOFILE& fout) { fout.printf( "\n" " %d\n" " %d\n" " %d\n" "\n", BOINC_MAJOR_VERSION, BOINC_MINOR_VERSION, BOINC_RELEASE ); } static void handle_get_simple_gui_info(MIOFILE& fout) { unsigned int i; fout.printf("\n"); for (i=0; iwrite_state(fout, true); } gstate.write_tasks_gui(fout); fout.printf("\n"); } static void handle_get_project_status(MIOFILE& fout) { unsigned int i; fout.printf("\n"); for (i=0; iwrite_state(fout, true); } fout.printf("\n"); } static void handle_get_disk_usage(MIOFILE& fout) { unsigned int i; double size, d_boinc, d_allowed; fout.printf("\n"); get_filesystem_info(gstate.host_info.d_total, gstate.host_info.d_free); dir_size(".", d_boinc, false); dir_size("locale", size, false); d_boinc += size; #ifdef __APPLE__ if (gstate.launched_by_manager) { // If launched by Manager, get Manager's size on disk ProcessSerialNumber managerPSN; FSRef ourFSRef; char path[1024]; double manager_size = 0.0; OSStatus err; err = GetProcessForPID(getppid(), &managerPSN); if (! err) err = GetProcessBundleLocation(&managerPSN, &ourFSRef); if (! err) err = FSRefMakePath (&ourFSRef, (UInt8*)path, sizeof(path)); if (! err) dir_size(path, manager_size, true); if (! err) d_boinc += manager_size; } #endif d_allowed = gstate.allowed_disk_usage(); fout.printf( "%f\n" "%f\n" "%f\n" "%f\n", gstate.host_info.d_total, gstate.host_info.d_free, d_boinc, d_allowed ); for (i=0; i\n" " %s\n" " %f\n" "\n", p->master_url, size ); } fout.printf("\n"); } static PROJECT* get_project(char* buf, MIOFILE& fout) { string url; if (!parse_str(buf, "", url)) { fout.printf("Missing project URL\n"); return 0; } PROJECT* p = gstate.lookup_project(url.c_str()); if (!p) { fout.printf("No such project\n"); return 0 ; } return p; } static void handle_result_show_graphics(char* buf, MIOFILE& fout) { string result_name; GRAPHICS_MSG gm; ACTIVE_TASK* atp; if (match_tag(buf, "")) { gm.mode = MODE_FULLSCREEN; } else if (match_tag(buf, "")) { gm.mode = MODE_HIDE_GRAPHICS; } else { gm.mode = MODE_WINDOW; } parse_str(buf, "", gm.window_station, sizeof(gm.window_station)); parse_str(buf, "", gm.desktop, sizeof(gm.desktop)); parse_str(buf, "", gm.display, sizeof(gm.display)); if (parse_str(buf, "", result_name)) { PROJECT* p = get_project(buf, fout); if (!p) { fout.printf("No such project\n"); return; } RESULT* rp = gstate.lookup_result(p, result_name.c_str()); if (!rp) { fout.printf("No such result\n"); return; } atp = gstate.lookup_active_task_by_result(rp); if (!atp) { fout.printf("no such result\n"); return; } atp->request_graphics_mode(gm); } else { for (unsigned int i=0; ischeduler_state != CPU_SCHED_SCHEDULED) continue; atp->request_graphics_mode(gm); } } fout.printf("\n"); } static void handle_project_op(char* buf, MIOFILE& fout, const char* op) { PROJECT* p = get_project(buf, fout); if (!p) { fout.printf("no such project\n"); return; } gstate.set_client_state_dirty("Project modified by user"); if (!strcmp(op, "reset")) { gstate.request_schedule_cpus("project reset by user"); gstate.request_work_fetch("project reset by user"); gstate.reset_project(p, false); } else if (!strcmp(op, "suspend")) { p->suspended_via_gui = true; gstate.request_schedule_cpus("project suspended by user"); gstate.request_work_fetch("project suspended by user"); } else if (!strcmp(op, "resume")) { p->suspended_via_gui = false; gstate.request_schedule_cpus("project resumed by user"); gstate.request_work_fetch("project resumed by user"); } else if (!strcmp(op, "detach")) { if (p->attached_via_acct_mgr) { msg_printf(p, MSG_USER_ERROR, "This project must be detached using the account manager web site." ); fout.printf("must detach using account manager"); return; } gstate.detach_project(p); gstate.request_schedule_cpus("project detached by user"); gstate.request_work_fetch("project detached by user"); } else if (!strcmp(op, "update")) { p->sched_rpc_pending = RPC_REASON_USER_REQ; p->min_rpc_time = 0; gstate.request_work_fetch("project updated by user"); } else if (!strcmp(op, "nomorework")) { p->dont_request_more_work = true; } else if (!strcmp(op, "allowmorework")) { p->dont_request_more_work = false; gstate.request_work_fetch("project allowed to fetch work by user"); } else if (!strcmp(op, "detach_when_done")) { p->detach_when_done = true; p->dont_request_more_work = true; } else if (!strcmp(op, "dont_detach_when_done")) { p->detach_when_done = false; p->dont_request_more_work = false; } fout.printf("\n"); } static void handle_set_run_mode(char* buf, MIOFILE& fout) { double duration = 0; int mode; parse_double(buf, "", duration); if (match_tag(buf, "Missing mode\n"); return; } gstate.run_mode.set(mode, duration); fout.printf("\n"); } static void handle_set_network_mode(char* buf, MIOFILE& fout) { double duration = 0; int mode; parse_double(buf, "", duration); if (match_tag(buf, "Missing mode\n"); return; } // user is turning network on/off explicitly, // so disable the "5 minute grace period" mechanism // gstate.gui_rpcs.time_of_last_rpc_needing_network = 0; gstate.network_mode.set(mode, duration); fout.printf("\n"); } static void handle_run_benchmarks(char* , MIOFILE& fout) { gstate.start_cpu_benchmarks(); fout.printf("\n"); } static void handle_set_proxy_settings(char* buf, MIOFILE& fout) { MIOFILE in; in.init_buf_read(buf); gstate.proxy_info.parse(in); gstate.set_client_state_dirty("Set proxy settings RPC"); fout.printf("\n"); gstate.show_proxy_info(); // tell running apps to reread app_info file (for F@h) // gstate.active_tasks.request_reread_app_info(); } static void handle_get_proxy_settings(char* , MIOFILE& fout) { gstate.proxy_info.write(fout); } // params: // [ n ] // return only msgs with seqno > n; if absent or zero, return all // static void handle_get_messages(char* buf, MIOFILE& fout) { int seqno=0, i, j; unsigned int k; MESSAGE_DESC* mdp; parse_int(buf, "", seqno); // messages are stored in descreasing seqno, // i.e. newer ones are at the head of the vector. // compute j = index of first message to return // j = (int)message_descs.size()-1; for (k=0; kseqno <= seqno) { j = k-1; break; } } fout.printf("\n"); for (i=j; i>=0; i--) { mdp = message_descs[i]; fout.printf( "\n" " %s\n" " %d\n" " %d\n" " \n%s\n\n" " \n", mdp->project_name, mdp->priority, mdp->seqno, mdp->message.c_str(), mdp->timestamp ); fout.printf("\n"); } fout.printf("\n"); } // // XXX // XXX // // static void handle_file_transfer_op(char* buf, MIOFILE& fout, const char* op) { string filename; PROJECT* p = get_project(buf, fout); if (!p) { fout.printf("No such project\n"); return; } if (!parse_str(buf, "", filename)) { fout.printf("Missing filename\n"); return; } FILE_INFO* f = gstate.lookup_file_info(p, filename.c_str()); if (!f) { fout.printf("No such file\n"); return; } PERS_FILE_XFER* pfx = f->pers_file_xfer; if (!pfx) { fout.printf("No such transfer waiting\n"); return; } if (!strcmp(op, "retry")) { pfx->next_request_time = 0; // leave file-level backoff mode f->project->file_xfer_succeeded(pfx->is_upload); // and leave project-level backoff mode } else if (!strcmp(op, "abort")) { f->pers_file_xfer->abort(); } else { fout.printf("unknown op\n"); return; } gstate.set_client_state_dirty("File transfer RPC"); fout.printf("\n"); } static void handle_result_op(char* buf, MIOFILE& fout, const char* op) { RESULT* rp; char result_name[256]; ACTIVE_TASK* atp; PROJECT* p = get_project(buf, fout); if (!p) { fout.printf("No such project\n"); return; } if (!parse_str(buf, "", result_name, sizeof(result_name))) { fout.printf("Missing result name\n"); return; } rp = gstate.lookup_result(p, result_name); if (!rp) { fout.printf("no such result\n"); return; } if (!strcmp(op, "abort")) { atp = gstate.lookup_active_task_by_result(rp); if (atp) { atp->abort_task(ERR_ABORTED_VIA_GUI, "aborted by user"); } else { rp->abort_inactive(ERR_ABORTED_VIA_GUI); } gstate.request_work_fetch("result aborted by user"); } else if (!strcmp(op, "suspend")) { rp->suspended_via_gui = true; gstate.request_work_fetch("result suspended by user"); } else if (!strcmp(op, "resume")) { rp->suspended_via_gui = false; } gstate.request_schedule_cpus("result suspended, resumed or aborted by user"); gstate.set_client_state_dirty("Result RPC"); fout.printf("\n"); } static void handle_get_host_info(char*, MIOFILE& fout) { gstate.host_info.write(fout, false); } static void handle_get_screensaver_tasks(MIOFILE& fout) { unsigned int i; ACTIVE_TASK* atp; fout.printf( "\n" " %d\n", gstate.suspend_reason ); for (i=0; itask_state() == PROCESS_EXECUTING) || ((atp->task_state() == PROCESS_SUSPENDED) && (gstate.suspend_reason & SUSPEND_REASON_CPU_USAGE_LIMIT))) { atp->result->write_gui(fout); } } fout.printf("\n"); } static void handle_quit(char*, MIOFILE& fout) { gstate.requested_exit = true; fout.printf("\n"); } static void handle_acct_mgr_info(char*, MIOFILE& fout) { fout.printf( "\n" " %s\n" " %s\n" " %s\n" "\n", gstate.acct_mgr_info.acct_mgr_url, gstate.acct_mgr_info.acct_mgr_name, strlen(gstate.acct_mgr_info.login_name)?"":"" ); } static void handle_get_statistics(char*, MIOFILE& fout) { fout.printf("\n"); for (std::vector::iterator i=gstate.projects.begin(); i!=gstate.projects.end();++i ) { (*i)->write_statistics(fout,true); } fout.printf("\n"); } static void handle_get_cc_status(GUI_RPC_CONN* gr, MIOFILE& fout) { fout.printf( "\n" " %d\n" " %d\n" " %d\n" " %d\n" " %d\n" " %d\n" " %d\n" " %d\n" " %f\n" " %f\n" " %d\n" " %ds\n", net_status.network_status(), gstate.acct_mgr_info.password_error?1:0, gstate.suspend_reason, gstate.network_suspend_reason, gstate.run_mode.get_current(), gstate.network_mode.get_current(), gstate.run_mode.get_perm(), gstate.network_mode.get_perm(), gstate.run_mode.delay(), gstate.network_mode.delay(), config.disallow_attach?1:0, config.simple_gui_only?1:0 ); if (gr->au_mgr_state == AU_MGR_QUIT_REQ) { fout.printf( " 1\n" ); gr->au_mgr_state = AU_MGR_QUIT_SENT; } fout.printf( "\n" ); } static void handle_network_available(char*, MIOFILE& fout) { net_status.network_available(); fout.printf("\n"); } static void handle_get_project_init_status(char*, MIOFILE& fout) { fout.printf( "\n" " %s\n" " %s\n" " %s\n" "\n", gstate.project_init.url, gstate.project_init.name, strlen(gstate.project_init.account_key)?"":"" ); } void GUI_RPC_CONN::handle_get_project_config(char* buf, MIOFILE& fout) { string url; parse_str(buf, "", url); canonicalize_master_url(url); get_project_config_op.do_rpc(url); fout.printf("\n"); } void GUI_RPC_CONN::handle_get_project_config_poll(char*, MIOFILE& fout) { if (get_project_config_op.error_num) { fout.printf( "\n" " %d\n" "\n", get_project_config_op.error_num ); } else { fout.printf("%s", get_project_config_op.reply.c_str()); } } void GUI_RPC_CONN::handle_lookup_account(char* buf, MIOFILE& fout) { ACCOUNT_IN ai; ai.parse(buf); if (!ai.url.size() || !ai.email_addr.size() || !ai.passwd_hash.size()) { fout.printf("missing URL, email address, or password\n"); return; } lookup_account_op.do_rpc(ai); fout.printf("\n"); } void GUI_RPC_CONN::handle_lookup_account_poll(char*, MIOFILE& fout) { if (lookup_account_op.error_num) { fout.printf( "\n" " %d\n" "\n", lookup_account_op.error_num ); } else { fout.printf("%s", lookup_account_op.reply.c_str()); } } void GUI_RPC_CONN::handle_create_account(char* buf, MIOFILE& fout) { ACCOUNT_IN ai; ai.parse(buf); create_account_op.do_rpc(ai); fout.printf("\n"); } void GUI_RPC_CONN::handle_create_account_poll(char*, MIOFILE& fout) { if (create_account_op.error_num) { fout.printf( "\n" " %d\n" "\n", create_account_op.error_num ); } else { fout.printf("%s", create_account_op.reply.c_str()); } } static void handle_project_attach(char* buf, MIOFILE& fout) { string url, authenticator, project_name; bool use_config_file = false; bool already_attached = false; unsigned int i; int retval; // Get URL/auth from project_init.xml? // if (parse_bool(buf, "use_config_file", use_config_file)) { if (!strlen(gstate.project_init.url)) { fout.printf("Missing URL\n"); return; } if (!strlen(gstate.project_init.account_key)) { fout.printf("Missing authenticator\n"); return; } url = gstate.project_init.url; authenticator = gstate.project_init.account_key; } else { if (!parse_str(buf, "", url)) { fout.printf("Missing URL\n"); return; } if (!parse_str(buf, "", authenticator)) { fout.printf("Missing authenticator\n"); return; } if (authenticator.empty()) { fout.printf("Missing authenticator\n"); return; } parse_str(buf, "", project_name); } for (i=0; imaster_url) already_attached = true; } if (already_attached) { fout.printf("Already attached to project\n"); return; } // clear messages from previous attach to project. // gstate.project_attach.messages.clear(); gstate.project_attach.error_num = gstate.add_project( url.c_str(), authenticator.c_str(), project_name.c_str(), false ); // if project_init.xml refers to this project, // delete the file, otherwise we'll just // reattach the next time the core client starts // if (!strcmp(url.c_str(), gstate.project_init.url)) { retval = gstate.project_init.remove(); if (retval) { msg_printf(NULL, MSG_INTERNAL_ERROR, "Can't delete project init file: %s", boincerror(retval) ); } } fout.printf("\n"); } static void handle_project_attach_poll(char*, MIOFILE& fout) { unsigned int i; fout.printf( "\n" ); for (i=0; i%s\n", gstate.project_attach.messages[i].c_str() ); } fout.printf( " %d\n", gstate.project_attach.error_num ); fout.printf( "\n" ); } static void handle_acct_mgr_rpc(char* buf, MIOFILE& fout) { std::string url, name, password; std::string password_hash, name_lc; bool use_config_file = false; bool bad_arg = false; if (!parse_bool(buf, "use_config_file", use_config_file)) { if (!parse_str(buf, "", url)) bad_arg = true; if (!parse_str(buf, "", name)) bad_arg = true; if (!parse_str(buf, "", password)) bad_arg = true; if (!bad_arg) { name_lc = name; downcase_string(name_lc); password_hash = md5_string(password+name_lc); } } else { if (!strlen(gstate.acct_mgr_info.acct_mgr_url) || !strlen(gstate.acct_mgr_info.acct_mgr_url) || !strlen(gstate.acct_mgr_info.acct_mgr_url)) { bad_arg = true; } else { url = gstate.acct_mgr_info.acct_mgr_url; name = gstate.acct_mgr_info.login_name; password_hash = gstate.acct_mgr_info.password_hash; } } if (bad_arg) { fout.printf("bad arg\n"); } else { gstate.acct_mgr_op.do_rpc(url, name, password_hash, true); fout.printf("\n"); } } static void handle_acct_mgr_rpc_poll(char*, MIOFILE& fout) { fout.printf( "\n" ); if (gstate.acct_mgr_op.error_str.size()) { fout.printf( " %s\n", gstate.acct_mgr_op.error_str.c_str() ); } fout.printf( " %d\n", gstate.acct_mgr_op.error_num ); fout.printf( "\n" ); } static void handle_get_newer_version(MIOFILE& fout) { fout.printf("%s\n", gstate.newer_version.c_str() ); } static void handle_get_global_prefs_file(MIOFILE& fout) { GLOBAL_PREFS p; bool found; int retval = p.parse_file( GLOBAL_PREFS_FILE_NAME, gstate.main_host_venue, found ); if (retval) { fout.printf("%d\n", retval); return; } p.write(fout); } static void handle_get_global_prefs_working(MIOFILE& fout) { gstate.global_prefs.write(fout); } static void handle_get_global_prefs_override(MIOFILE& fout) { string s; int retval = read_file_string(GLOBAL_PREFS_OVERRIDE_FILE, s); if (!retval) { strip_whitespace(s); fout.printf("%s\n", s.c_str()); } else { fout.printf("no prefs override file\n"); } } static void handle_set_global_prefs_override(char* buf, MIOFILE& fout) { char *p, *q=0; int retval = ERR_XML_PARSE; // strip off outer tags // p = strstr(buf, "\n"); if (p) { p += strlen("\n"); q = strstr(p, "\n" " %d\n" "\n", retval ); } static void handle_get_cc_config(MIOFILE& fout) { string s; int retval = read_file_string(CONFIG_FILE, s); if (!retval) { strip_whitespace(s); fout.printf("%s\n", s.c_str()); } } static void read_all_projects_list_file(MIOFILE& fout) { string s; int retval = read_file_string(ALL_PROJECTS_LIST_FILENAME, s); if (!retval) { strip_whitespace(s); fout.printf("%s\n", s.c_str()); } } static int set_debt(XML_PARSER& xp) { bool is_tag; char tag[256], url[256]; double short_term_debt = 0.0, long_term_debt = 0.0; bool got_std=false, got_ltd=false; strcpy(url, ""); while (!xp.get(tag, sizeof(tag), is_tag)) { if (!strcmp(tag, "/project")) { if (!strlen(url)) return ERR_XML_PARSE; canonicalize_master_url(url); PROJECT* p = gstate.lookup_project(url); if (!p) return ERR_NOT_FOUND; if (got_std) p->short_term_debt = short_term_debt; if (got_ltd) p->long_term_debt = long_term_debt; return 0; } if (xp.parse_str(tag, "master_url", url, sizeof(url))) continue; if (xp.parse_double(tag, "short_term_debt", short_term_debt)) { got_std = true; continue; } if (xp.parse_double(tag, "long_term_debt", long_term_debt)) { got_ltd = true; continue; } if (log_flags.unparsed_xml) { msg_printf(NULL, MSG_INFO, "[unparsed_xml] set_debt: unrecognized %s", tag ); } xp.skip_unexpected(tag, log_flags.unparsed_xml, "set_debt"); } return 0; } static void handle_set_debts(char* buf, MIOFILE& fout) { MIOFILE in; XML_PARSER xp(&in); bool is_tag; char tag[256]; int retval; in.init_buf_read(buf); while (!xp.get(tag, sizeof(tag), is_tag)) { if (!is_tag) continue; if (!strcmp(tag, "boinc_gui_rpc_request")) continue; if (!strcmp(tag, "set_debts")) continue; if (!strcmp(tag, "/set_debts")) { fout.printf("\n"); gstate.set_client_state_dirty("set_debt RPC"); return; } if (!strcmp(tag, "project")) { retval = set_debt(xp); if (retval) { fout.printf("%d\n", retval); return; } continue; } if (log_flags.unparsed_xml) { msg_printf(NULL, MSG_INFO, "[unparsed_xml] handle_set_debts: unrecognized %s", tag ); } xp.skip_unexpected(tag, log_flags.unparsed_xml, "handle_set_debts"); } fout.printf("No end tag\n"); } static void handle_set_cc_config(char* buf, MIOFILE& fout) { char *p, *q=0; int retval = ERR_XML_PARSE; // strip off outer tags // p = strstr(buf, "\n"); if (p) { p += strlen("\n"); q = strstr(p, "\n" " %d\n" "\n", retval ); } int GUI_RPC_CONN::handle_rpc() { char request_msg[4096]; int n, retval=0; MIOFILE mf; MFILE m; char* p; mf.init_mfile(&m); // read the request message in one read() // so that the core client won't hang because // of malformed request msgs // #ifdef _WIN32 n = recv(sock, request_msg, 4095, 0); #else n = read(sock, request_msg, 4095); #endif if (n <= 0) return ERR_READ; request_msg[n-1] = 0; // replace 003 with NULL if (log_flags.guirpc_debug) { msg_printf(0, MSG_INFO, "[guirpc_debug] GUI RPC Command = '%s'\n", request_msg ); } mf.printf("\n"); if (match_tag(request_msg, "\n"); gstate.write_tasks_gui(mf); mf.printf("\n"); } else if (match_tag(request_msg, "")) { handle_get_newer_version(mf); } else if (match_tag(request_msg, "")) { mf.printf("\n"); gstate.read_global_prefs(); gstate.request_schedule_cpus("Preferences override"); gstate.request_work_fetch("Preferences override"); } else if (match_tag(request_msg, "")) { mf.printf("\n"); read_config_file(); gstate.request_schedule_cpus("Core client configuration"); gstate.request_work_fetch("Core client configuration"); } else if (match_tag(request_msg, "")) { read_all_projects_list_file(mf); } else if (match_tag(request_msg, "")) { handle_get_project_config(request_msg, mf); } else if (match_tag(request_msg, "")) { handle_lookup_account(request_msg, mf); } else if (match_tag(request_msg, "")) { handle_create_account(request_msg, mf); } else if (match_tag(request_msg, "")) { handle_project_attach(request_msg, mf); } else if (match_tag(request_msg, "")) { handle_acct_mgr_rpc(request_msg, mf); } else if (match_tag(request_msg, "unrecognized op\n"); gstate.gui_rpcs.time_of_last_rpc_needing_network = saved_time; } } mf.printf("\n\003"); m.get_buf(p, n); if (p) { send(sock, p, n, 0); p[n-1]=0; // replace 003 with NULL if (log_flags.guirpc_debug) { if (n > 50) p[50] = 0; msg_printf(0, MSG_INFO, "[guirpc_debug] GUI RPC reply: '%s'\n", p ); } free(p); } return retval; } const char *BOINC_RCSID_7bf15dcb49="$Id$";