// Berkeley Open Infrastructure for Network Computing // http://boinc.berkeley.edu // Copyright (C) 2005 University of California // // This 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 2.1 of the License, or (at your option) any later version. // // This software 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. // // To view the GNU Lesser General Public License visit // http://www.gnu.org/copyleft/lesser.html // or write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // initialization and starting of applications #include "cpp.h" #ifdef _WIN32 #include "boinc_win.h" #else #include "config.h" #if HAVE_SYS_TIME_H #include #endif #if HAVE_SYS_RESOURCE_H #include #endif #if HAVE_SYS_IPC_H #include #endif #if HAVE_SYS_WAIT_H #include #endif #include #include #include #endif #ifdef __EMX__ #include #endif #if (defined (__APPLE__) && (defined(__i386__) || defined(__x86_64__))) #include #include #include #include #endif #if(!defined (_WIN32) && !defined (__EMX__)) #include #endif using std::vector; #include "filesys.h" #include "error_numbers.h" #include "util.h" #include "str_util.h" #include "shmem.h" #include "client_msgs.h" #include "client_state.h" #include "file_names.h" #include "base64.h" #include "sandbox.h" #include "app.h" // Goes through an array of strings, and prints each string // #ifndef _WIN32 static void debug_print_argv(char** argv) { int i; msg_printf(0, MSG_INFO, "[task_debug] Arguments:"); for (i=0; argv[i]; i++) { msg_printf(0, MSG_INFO, "[task_debug] argv[%d]: %s\n", i, argv[i] ); } } #endif // create a file (new_link) which contains an XML // reference to existing file. // static int make_link(const char *existing, const char *new_link) { FILE *fp; fp = boinc_fopen(new_link, "w"); if (!fp) return ERR_FOPEN; fprintf(fp, "%s\n", existing); fclose(fp); #ifdef SANDBOX return set_to_project_group(new_link); #endif return 0; } int ACTIVE_TASK::link_user_files() { PROJECT* project = wup->project; unsigned int i; FILE_REF fref; FILE_INFO* fip; char link_path[1024], buf[256], file_path[1024]; int retval; for (i=0; iuser_files.size(); i++) { fref = project->user_files[i]; fip = fref.file_info; if (fip->status != FILE_PRESENT) continue; get_pathname(fip, file_path, sizeof(file_path)); sprintf(link_path, "%s/%s", slot_dir, strlen(fref.open_name)?fref.open_name:fip->name); sprintf(buf, "../../%s", file_path); retval = make_link(buf, link_path); if (retval) return retval; } return 0; } // make a unique key for core/app shared memory segment // int ACTIVE_TASK::get_shmem_seg_name() { #ifdef _WIN32 int i = 0; char szSharedMemoryName[256]; HANDLE hSharedMemoryHandle = 0; for (i=0; i<1024; i++) { sprintf(szSharedMemoryName, "%sboinc_%d", SHM_PREFIX, i); hSharedMemoryHandle = create_shmem(szSharedMemoryName, 1024, NULL, true); if (hSharedMemoryHandle) break; } if (!hSharedMemoryHandle) { return ERR_SHMGET; } detach_shmem(hSharedMemoryHandle, NULL); sprintf(szSharedMemoryName, "boinc_%d", i); strcpy(shmem_seg_name, szSharedMemoryName); #else char init_data_path[256]; #ifndef __EMX__ // shmem_seg_name is not used with mmap() shared memory if (app_version->api_major_version() >= 6) { shmem_seg_name = -1; return 0; } #endif sprintf(init_data_path, "%s/%s", slot_dir, INIT_DATA_FILE); // ftok() only works if there's a file at the given location // FILE* f = boinc_fopen(init_data_path, "w"); if (f) fclose(f); shmem_seg_name = ftok(init_data_path, 1); if (shmem_seg_name == -1) return ERR_SHMEM_NAME; #endif return 0; } // write the app init file. // This is done before starting the app, // and when project prefs have changed during app execution // int ACTIVE_TASK::write_app_init_file() { APP_INIT_DATA aid; FILE *f; char init_data_path[256], project_dir[256], project_path[256]; int retval; memset(&aid, 0, sizeof(aid)); aid.major_version = BOINC_MAJOR_VERSION; aid.minor_version = BOINC_MINOR_VERSION; aid.release = BOINC_RELEASE; aid.app_version = app_version->version_num; safe_strcpy(aid.app_name, wup->app->name); safe_strcpy(aid.symstore, wup->project->symstore); safe_strcpy(aid.acct_mgr_url, gstate.acct_mgr_info.acct_mgr_url); safe_strcpy(aid.user_name, wup->project->user_name); safe_strcpy(aid.team_name, wup->project->team_name); if (wup->project->project_specific_prefs.length()) { aid.project_preferences = strdup(wup->project->project_specific_prefs.c_str()); } get_project_dir(wup->project, project_dir, sizeof(project_dir)); relative_to_absolute(project_dir, project_path); strcpy(aid.project_dir, project_path); relative_to_absolute("", aid.boinc_dir); strcpy(aid.authenticator, wup->project->authenticator); aid.slot = slot; strcpy(aid.wu_name, wup->name); aid.user_total_credit = wup->project->user_total_credit; aid.user_expavg_credit = wup->project->user_expavg_credit; aid.host_total_credit = wup->project->host_total_credit; aid.host_expavg_credit = wup->project->host_expavg_credit; double rrs = gstate.runnable_resource_share(); if (rrs) { aid.resource_share_fraction = wup->project->resource_share/rrs; } else { aid.resource_share_fraction = 1; } aid.rsc_fpops_est = wup->rsc_fpops_est; aid.rsc_fpops_bound = wup->rsc_fpops_bound; aid.rsc_memory_bound = wup->rsc_memory_bound; aid.rsc_disk_bound = wup->rsc_disk_bound; aid.checkpoint_period = gstate.global_prefs.disk_interval; aid.fraction_done_update_period = DEFAULT_FRACTION_DONE_UPDATE_PERIOD; aid.fraction_done_start = 0; aid.fraction_done_end = 1; #ifdef _WIN32 strcpy(aid.shmem_seg_name, shmem_seg_name); #else aid.shmem_seg_name = shmem_seg_name; #endif // wu_cpu_time is the CPU time at start of session, // not the checkpoint CPU time // At the start of an episode these are equal, but not in the middle! // aid.wu_cpu_time = episode_start_cpu_time; sprintf(init_data_path, "%s/%s", slot_dir, INIT_DATA_FILE); f = boinc_fopen(init_data_path, "w"); if (!f) { msg_printf(wup->project, MSG_INTERNAL_ERROR, "Failed to open init file %s", init_data_path ); return ERR_FOPEN; } aid.host_info = gstate.host_info; aid.global_prefs = gstate.global_prefs; aid.proxy_info = gstate.proxy_info; retval = write_init_data_file(f, aid); fclose(f); return retval; } // set up a 'symbolic link' in the slot dir to the given file // (or copy the file to slot dir) // static int setup_file( WORKUNIT* wup, FILE_INFO* fip, FILE_REF& fref, char* file_path, char* slot_dir, bool input ) { char link_path[256], buf[256]; int retval; sprintf(link_path, "%s/%s", slot_dir, strlen(fref.open_name)?fref.open_name:fip->name ); sprintf(buf, "../../%s", file_path ); if (fref.copy_file) { if (input) { retval = boinc_copy(file_path, link_path); if (retval) { msg_printf(wup->project, MSG_INTERNAL_ERROR, "Can't copy %s to %s", file_path, link_path ); return retval; } } return 0; } // if anonymous platform, link may already be there // if (input && wup->project->anonymous_platform && boinc_file_exists(link_path)) { return 0; } retval = make_link(buf, link_path); if (retval) { msg_printf(wup->project, MSG_INTERNAL_ERROR, "Can't link %s to %s", file_path, link_path ); return retval; } return 0; } int ACTIVE_TASK::copy_output_files() { char slotfile[256], projfile[256]; unsigned int i; for (i=0; ioutput_files.size(); i++) { FILE_REF& fref = result->output_files[i]; if (!fref.copy_file) continue; FILE_INFO* fip = fref.file_info; sprintf(slotfile, "%s/%s", slot_dir, fref.open_name); get_pathname(fip, projfile, sizeof(projfile)); int retval = boinc_rename(slotfile, projfile); if (retval) { msg_printf(wup->project, MSG_INTERNAL_ERROR, "Can't rename output file %s", fip->name ); } } return 0; } // Start a task in a slot directory. // This includes setting up soft links, // passing preferences, and starting the process // // Current dir is top-level BOINC dir // // postcondition: // If any error occurs // ACTIVE_TASK::task_state is PROCESS_COULDNT_START // report_result_error() is called // else // ACTIVE_TASK::task_state is PROCESS_EXECUTING // int ACTIVE_TASK::start(bool first_time) { char exec_name[256], file_path[256], buf[256], exec_path[256]; unsigned int i; FILE_REF fref; FILE_INFO* fip; int retval; #ifdef _WIN32 std::string cmd_line; #endif if (first_time && log_flags.task) { msg_printf(result->project, MSG_INFO, "Starting %s", result->name ); } if (log_flags.cpu_sched) { msg_printf(result->project, MSG_INFO, "[cpu_sched] Starting %s%s", result->name, first_time?" (initial)":"(resume)" ); } if (wup->project->verify_files_on_app_start) { fip=0; retval = gstate.input_files_available(result, true, &fip); if (retval) { if (fip) { snprintf( buf, sizeof(buf), "Input file %s missing or invalid: %d", fip->name, retval ); } else { strcpy(buf, "Input file missing or invalid"); } goto error; } } if (first_time) { checkpoint_cpu_time = 0; checkpoint_wall_time = gstate.now; } current_cpu_time = checkpoint_cpu_time; episode_start_cpu_time = checkpoint_cpu_time; debt_interval_start_cpu_time = checkpoint_cpu_time; graphics_request_queue.init(result->name); // reset message queues process_control_queue.init(result->name); if (!app_client_shm.shm) { retval = get_shmem_seg_name(); if (retval) { sprintf(buf, "Can't get shared memory segment name: %s", boincerror(retval) ); goto error; } } // this must go AFTER creating shmem name, // since the shmem name is part of the file // retval = write_app_init_file(); if (retval) { strcpy(buf, "Can't write init file"); goto error; } // set up applications files // strcpy(exec_name, ""); for (i=0; iapp_files.size(); i++) { fref = app_version->app_files[i]; fip = fref.file_info; get_pathname(fip, file_path, sizeof(file_path)); if (fref.main_program) { if (is_image_file(fip->name)) { sprintf(buf, "Main program %s is an image file", fip->name); retval = ERR_NO_SIGNATURE; goto error; } if (!fip->executable && !wup->project->anonymous_platform) { sprintf(buf, "Main program %s is not executable", fip->name); retval = ERR_NO_SIGNATURE; goto error; } safe_strcpy(exec_name, fip->name); safe_strcpy(exec_path, file_path); } // anonymous platform may use different files than // when the result was started // if (first_time || wup->project->anonymous_platform) { retval = setup_file(wup, fip, fref, file_path, slot_dir, true); if (retval) { strcpy(buf, "Can't link input file"); goto error; } } } if (!strlen(exec_name)) { strcpy(buf, "No main program specified"); retval = ERR_NOT_FOUND; goto error; } // set up input, output files // if (first_time) { for (i=0; iinput_files.size(); i++) { fref = wup->input_files[i]; fip = fref.file_info; get_pathname(fref.file_info, file_path, sizeof(file_path)); retval = setup_file(wup, fip, fref, file_path, slot_dir, true); if (retval) { strcpy(buf, "Can't link input file"); goto error; } } for (i=0; ioutput_files.size(); i++) { fref = result->output_files[i]; if (fref.copy_file) continue; fip = fref.file_info; get_pathname(fref.file_info, file_path, sizeof(file_path)); retval = setup_file(wup, fip, fref, file_path, slot_dir, false); if (retval) { strcpy(buf, "Can't link output file"); goto error; } } } link_user_files(); if (gstate.exit_before_start) { exit(0); } #ifdef _WIN32 PROCESS_INFORMATION process_info; STARTUPINFO startup_info; char slotdirpath[256]; char error_msg[1024]; memset(&process_info, 0, sizeof(process_info)); memset(&startup_info, 0, sizeof(startup_info)); startup_info.cb = sizeof(startup_info); startup_info.dwFlags=STARTF_FORCEOFFFEEDBACK; // suppress 2-sec rotating hourglass cursor on startup // create shared mem segment if needed // if (!app_client_shm.shm) { sprintf(buf, "%s%s", SHM_PREFIX, shmem_seg_name); shm_handle = create_shmem(buf, sizeof(SHARED_MEM), (void **)&app_client_shm.shm, false ); if (shm_handle == NULL) { strcpy(buf, "Can't create shared memory"); retval = ERR_SHMGET; goto error; } } app_client_shm.reset_msgs(); if (config.run_apps_manually) { pid = GetCurrentProcessId(); pid_handle = GetCurrentProcess(); set_task_state(PROCESS_EXECUTING, "start"); return 0; } // NOTE: in Windows, stderr is redirected in boinc_init_diagnostics(); cmd_line = exec_path + std::string(" ") + wup->command_line; relative_to_absolute(slot_dir, slotdirpath); bool success = false; get_sandbox_account_token(); for (i=0; i<5; i++) { if (sandbox_account_token != NULL) { if (CreateProcessAsUser( sandbox_account_token, exec_path, (LPSTR)cmd_line.c_str(), NULL, NULL, FALSE, CREATE_NEW_PROCESS_GROUP|CREATE_NO_WINDOW|IDLE_PRIORITY_CLASS, NULL, slotdirpath, &startup_info, &process_info )) { success = true; break; } } else { if (CreateProcess( exec_path, (LPSTR)cmd_line.c_str(), NULL, NULL, FALSE, CREATE_NEW_PROCESS_GROUP|CREATE_NO_WINDOW|IDLE_PRIORITY_CLASS, NULL, slotdirpath, &startup_info, &process_info )) { success = true; break; } } windows_error_string(error_msg, sizeof(error_msg)); msg_printf(wup->project, MSG_INTERNAL_ERROR, "Process creation failed: %s", error_msg ); boinc_sleep(drand()); } if (!success) { sprintf(buf, "CreateProcess() failed - %s", error_msg); retval = ERR_EXEC; goto error; } pid = process_info.dwProcessId; pid_handle = process_info.hProcess; #elif defined(__EMX__) char* argv[100]; char current_dir[_MAX_PATH]; // Set up core/app shared memory seg if needed // if (!app_client_shm.shm) { retval = create_shmem( shmem_seg_name, sizeof(SHARED_MEM), (void**)&app_client_shm.shm ); if (retval) { return retval; } } app_client_shm.reset_msgs(); // save current dir getcwd( current_dir, sizeof(current_dir)); // chdir() into the slot directory // retval = chdir(slot_dir); if (retval) { sprintf(buf, "Can't change directory: %s", slot_dir, boincerror(retval)); goto error; } // hook up stderr to a specially-named file // //freopen(STDERR_FILE, "a", stderr); argv[0] = exec_name; char cmdline[8192]; strcpy(cmdline, wup->command_line.c_str()); parse_command_line(cmdline, argv+1); if (log_flags.task_debug) { debug_print_argv(argv); } sprintf(buf, "../../%s", exec_path ); pid = spawnv(P_NOWAIT, buf, argv); if (pid == -1) { sprintf(buf, "Process creation failed: %s\n", buf, boincerror(retval)); chdir(current_dir); retval = ERR_EXEC; goto error; } // restore current dir chdir(current_dir); if (log_flags.task_debug) { msg_printf(0, MSG_INFO, "[task_debug] ACTIVE_TASK::start(): forked process: pid %d\n", pid ); } // set idle process priority if (setpriority(PRIO_PROCESS, pid, PROCESS_IDLE_PRIORITY)) { perror("setpriority"); } #else // Unix/Linux/Mac case char* argv[100]; // Set up core/app shared memory seg if needed // if (!app_client_shm.shm) { if (app_version->api_major_version() >= 6) { // Use mmap() shared memory sprintf(buf, "%s/%s", slot_dir, MMAPPED_FILE_NAME); if (g_use_sandbox) { if (!boinc_file_exists(buf)) { int fd = open(buf, O_RDWR | O_CREAT, 0660); if (fd >= 0) { close (fd); #ifdef SANDBOX set_to_project_group(buf); #endif } } } retval = create_shmem_mmap( buf, sizeof(SHARED_MEM), (void**)&app_client_shm.shm ); } else { // Use shmget() shared memory retval = create_shmem( shmem_seg_name, sizeof(SHARED_MEM), gstate.boinc_project_gid, (void**)&app_client_shm.shm ); if (retval) { needs_shmem = true; destroy_shmem(shmem_seg_name); return retval; } } needs_shmem = false; } app_client_shm.reset_msgs(); #if (defined (__APPLE__) && (defined(__i386__) || defined(__x86_64__))) // PowerPC apps emulated on i386 Macs crash if running graphics powerpc_emulated_on_i386 = ! is_native_i386_app(exec_path); #endif if (config.run_apps_manually) { pid = getpid(); // use the client's PID set_task_state(PROCESS_EXECUTING, "start"); return 0; } pid = fork(); if (pid == -1) { sprintf(buf, "fork() failed: %s", strerror(errno)); retval = ERR_FORK; goto error; } if (pid == 0) { // from here on we're running in a new process. // If an error happens, // exit nonzero so that the core client knows there was a problem. // add project dir to library path // char libpath[8192]; get_project_dir(wup->project, buf, sizeof(buf)); sprintf(libpath, "%s:%s", getenv("LD_LIBRARY_PATH"), buf); setenv("LD_LIBRARY_PATH", libpath, 1); retval = chdir(slot_dir); if (retval) { perror("chdir"); fflush(NULL); _exit(errno); } #if 0 // set stack size limit to the max. // Some BOINC apps have reported problems with exceeding // small stack limits (e.g. 8 MB) // and it seems like the best thing to raise it as high as possible // struct rlimit rlim; #define MIN_STACK_LIMIT 64000000 getrlimit(RLIMIT_STACK, &rlim); if (rlim.rlim_cur != RLIM_INFINITY && rlim.rlim_cur <= MIN_STACK_LIMIT) { if (rlim.rlim_max == RLIM_INFINITY || rlim.rlim_max > MIN_STACK_LIMIT) { rlim.rlim_cur = MIN_STACK_LIMIT; } else { rlim.rlim_cur = rlim.rlim_max; } setrlimit(RLIMIT_STACK, &rlim); } #endif // hook up stderr to a specially-named file // freopen(STDERR_FILE, "a", stderr); // set idle process priority #ifdef HAVE_SETPRIORITY if (setpriority(PRIO_PROCESS, 0, PROCESS_IDLE_PRIORITY)) { perror("setpriority"); } #endif char cmdline[8192]; strcpy(cmdline, wup->command_line.c_str()); sprintf(buf, "../../%s", exec_path ); if (g_use_sandbox) { char switcher_path[100]; sprintf(switcher_path, "../../%s/%s", SWITCHER_DIR, SWITCHER_FILE_NAME); argv[0] = SWITCHER_FILE_NAME; argv[1] = buf; argv[2] = exec_name; parse_command_line(cmdline, argv+3); if (log_flags.task_debug) { debug_print_argv(argv); } // Files written by projects have user boinc_project and group boinc_project, // so they must be world-readable so BOINC CLient can read them umask(2); retval = execv(switcher_path, argv); } else { argv[0] = exec_name; parse_command_line(cmdline, argv+1); retval = execv(buf, argv); } msg_printf(wup->project, MSG_INTERNAL_ERROR, "Process creation (%s) failed: %s, errno=%d\n", buf, boincerror(retval), errno ); perror("execv"); fflush(NULL); _exit(errno); } if (log_flags.task_debug) { msg_printf(0, MSG_INFO, "[task_debug] ACTIVE_TASK::start(): forked process: pid %d\n", pid ); } #endif set_task_state(PROCESS_EXECUTING, "start"); return 0; // go here on error; "buf" contains error message, "retval" is nonzero // error: // if something failed, it's possible that the executable was munged. // Verify it to trigger another download. // gstate.input_files_available(result, true); gstate.report_result_error(*result, buf); set_task_state(PROCESS_COULDNT_START, "start"); return retval; } // Resume the task if it was previously running; otherwise start it // Postcondition: "state" is set correctly // int ACTIVE_TASK::resume_or_start(bool first_time) { const char* str = "??"; int retval; switch (task_state()) { case PROCESS_UNINITIALIZED: if (first_time) { retval = start(true); str = "Starting"; } else { retval = start(false); str = "Restarting"; } if ((retval == ERR_SHMGET) || (retval == ERR_SHMAT)) { return retval; } if (retval) { set_task_state(PROCESS_COULDNT_START, "resume_or_start1"); return retval; } break; case PROCESS_SUSPENDED: retval = unsuspend(); if (retval) { msg_printf(wup->project, MSG_INTERNAL_ERROR, "Couldn't resume task %s", result->name ); set_task_state(PROCESS_COULDNT_START, "resume_or_start2"); return retval; } str = "Resuming"; break; default: msg_printf(result->project, MSG_INTERNAL_ERROR, "Unexpected state %d for task %s", task_state(), result->name ); return 0; } if (log_flags.task) { msg_printf(result->project, MSG_INFO, "%s task %s using %s version %d", str, result->name, app_version->app->name, app_version->version_num ); } return 0; } #if (defined (__APPLE__) && (defined(__i386__) || defined(__x86_64__))) union headeru { fat_header fat; mach_header mach; }; // Read the mach-o headers to determine the architectures // supported by executable file. // Returns 1 if application can run natively on i386 Macs, else returns 0. // int ACTIVE_TASK::is_native_i386_app(char* exec_path) { FILE *f; int result = 0; headeru myHeader; fat_arch fatHeader; uint32_t n, i, len; f = boinc_fopen(exec_path, "rb"); if (!f) { return result; // Should never happen } myHeader.fat.magic = 0; myHeader.fat.nfat_arch = 0; fread(&myHeader, 1, sizeof(fat_header), f); switch (myHeader.mach.magic) { case MH_MAGIC: if (myHeader.mach.cputype == CPU_TYPE_I386) { result = 1; // Single-architecture i386 file } break; case FAT_CIGAM: n = _OSSwapInt32(myHeader.fat.nfat_arch); // Multiple architecture (fat) file for (i=0; i