// The contents of this file are subject to the Mozilla Public License // Version 1.0 (the "License"); you may not use this file except in // compliance with the License. You may obtain a copy of the License at // http://www.mozilla.org/MPL/ // // Software distributed under the License is distributed on an "AS IS" // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the // License for the specific language governing rights and limitations // under the License. // // The Original Code is the Berkeley Open Infrastructure for Network Computing. // // The Initial Developer of the Original Code is the SETI@home project. // Portions created by the SETI@home project are Copyright (C) 2002 // University of California at Berkeley. All Rights Reserved. // // Contributor(s): // // Abstraction of a set of executing applications, // connected to I/O files in various ways. // Shouldn't depend on CLIENT_STATE. #include "windows_cpp.h" #include "error_numbers.h" #ifdef _WIN32 #include #include #endif #if HAVE_UNISTD_H #include #endif #if HAVE_SYS_WAIT_H #include #endif #if HAVE_SYS_TIME_H #include #endif #if HAVE_SYS_RESOURCE_H #include #endif #if HAVE_SYS_TYPES_H #include #endif #if HAVE_SYS_SIGNAL_H #include #endif #if HAVE_FCNTL_H #include #endif #if HAVE_SIGNAL_H #include #endif #include #include #include #include #include "client_state.h" #include "client_types.h" #include "filesys.h" #include "file_names.h" #include "log_flags.h" #include "parse.h" #include "util.h" #include "app.h" #include "boinc_api.h" #include "graphics_api.h" // take a string containing some space separated words. // return an array of pointers to the null-terminated words. // Modifies the string arg. // TODO: use strtok here int parse_command_line(char* p, char** argv) { char** pp = argv; bool space = true; while (*p) { if (isspace(*p)) { *p = 0; space = true; } else { if (space) { *pp++ = p; space = false; } } p++; } *pp++ = 0; return 0; } // Goes through an array of strings, and prints each string // static int print_argv(char** argv) { int i; for (i=0; argv[i]; i++) { fprintf(stderr, "argv[%d]: %s\n", i, argv[i]); } return 0; } ACTIVE_TASK::ACTIVE_TASK() { result = NULL; wup = NULL; app_version = NULL; pid = 0; slot = 0; state = PROCESS_UNINITIALIZED; exit_status = 0; signal = 0; strcpy(slot_dir, ""); } int ACTIVE_TASK::init(RESULT* rp) { result = rp; wup = rp->wup; app_version = wup->avp; 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 // int ACTIVE_TASK::start(bool first_time) { char exec_name[256], file_path[256], link_path[256], temp[256], exec_path[256]; unsigned int i; FILE_REF file_ref; FILE_INFO* fip; int retval; char init_data_path[256], graphics_data_path[256], fd_init_path[256]; FILE *f; APP_INIT_DATA aid; GRAPHICS_INFO gi; if (first_time) { checkpoint_cpu_time = 0; } current_cpu_time = checkpoint_cpu_time; starting_cpu_time = checkpoint_cpu_time; fraction_done = 0; gi.xsize = 640; gi.ysize = 480; gi.graphics_mode = MODE_WINDOW; gi.refresh_period = 0.1; memset(&aid, 0, sizeof(aid)); // TODO: fill in the app prefs, user name, team name, etc. aid.checkpoint_period = DEFAULT_CHECKPOINT_PERIOD; aid.fraction_done_update_period = DEFAULT_FRACTION_DONE_UPDATE_PERIOD; aid.wu_cpu_time = checkpoint_cpu_time; sprintf(init_data_path, "%s%s%s", slot_dir, PATH_SEPARATOR, INIT_DATA_FILE); f = fopen(init_data_path, "w"); if (!f) { if (log_flags.task_debug) { printf("Failed to open core to app prefs file %s.\n", init_data_path); } return ERR_FOPEN; } retval = write_init_data_file(f, aid); fclose(f); sprintf(graphics_data_path, "%s%s%s", slot_dir, PATH_SEPARATOR, GRAPHICS_DATA_FILE); f = fopen(graphics_data_path, "w"); if (!f) { if (log_flags.task_debug) { printf("Failed to open core to app graphics prefs file %s.\n", graphics_data_path); } return ERR_FOPEN; } retval = write_graphics_file(f, &gi); fclose(f); sprintf(fd_init_path, "%s%s%s", slot_dir, PATH_SEPARATOR, FD_INIT_FILE); f = fopen(fd_init_path, "w"); if (!f) { if(log_flags.task_debug) { printf("Failed to open init file %s.\n", fd_init_path); } return ERR_FOPEN; } // make soft links to the executable(s) // for (i=0; iapp_files.size(); i++) { fip = app_version->app_files[i].file_info; get_pathname(fip, file_path); if (i == 0) { strcpy(exec_name, fip->name); strcpy(exec_path, file_path); } if (first_time) { sprintf(link_path, "%s%s%s", slot_dir, PATH_SEPARATOR, fip->name); sprintf(temp, "..%s..%s%s", PATH_SEPARATOR, PATH_SEPARATOR, file_path ); retval = boinc_link( temp, link_path); if (log_flags.task_debug) { printf("link %s to %s\n", file_path, link_path); } if (retval) { perror("link"); fclose(f); return retval; } } } // create symbolic links, and hook up descriptors, for input files // for (i=0; iinput_files.size(); i++) { file_ref = wup->input_files[i]; get_pathname(file_ref.file_info, file_path); if (strlen(file_ref.open_name)) { if (first_time) { sprintf(link_path, "%s%s%s", slot_dir, PATH_SEPARATOR, file_ref.open_name); sprintf(temp, "..%s..%s%s", PATH_SEPARATOR, PATH_SEPARATOR, file_path ); if (log_flags.task_debug) { printf("link %s to %s\n", file_path, link_path); } retval = boinc_link(temp, link_path); if (retval) { perror("link"); fclose(f); return retval; } } } else { sprintf(temp, "..%s..%s%s", PATH_SEPARATOR, PATH_SEPARATOR, file_path); write_fd_init_file(f, temp, file_ref.fd, 1); } } // hook up the output files using BOINC soft links // for (i=0; ioutput_files.size(); i++) { file_ref = result->output_files[i]; get_pathname(file_ref.file_info, file_path); if (strlen(file_ref.open_name)) { if (first_time) { creat(file_path, 0660); sprintf(link_path, "%s%s%s", slot_dir, PATH_SEPARATOR, file_ref.open_name); sprintf(temp, "..%s..%s%s", PATH_SEPARATOR, PATH_SEPARATOR, file_path ); if (log_flags.task_debug) { printf("link %s to %s\n", file_path, link_path); } retval = boinc_link(temp, link_path); if (retval) { fclose(f); perror("link"); return retval; } } } else { sprintf(temp, "..%s..%s%s", PATH_SEPARATOR, PATH_SEPARATOR, file_path); write_fd_init_file(f, temp, file_ref.fd, 0); } } fclose(f); #ifdef HAVE_UNISTD_H #ifdef HAVE_SYS_TYPES_H char* argv[100]; pid = fork(); 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. // chdir() into the slot directory // retval = chdir(slot_dir); if (retval) { perror("chdir"); exit(retval); } // hook up stderr to a specially-named file // freopen(STDERR_FILE, "a", stderr); argv[0] = exec_name; parse_command_line(wup->command_line, argv+1); if (log_flags.task_debug) print_argv(argv); boinc_resolve_filename(exec_name, temp, sizeof(temp)); retval = execv(temp, argv); fprintf(stderr, "execv failed: %d\n", retval); perror("execv"); exit(1); } if (log_flags.task_debug) printf("forked process: pid %d\n", pid); #endif #endif #ifdef _WIN32 PROCESS_INFORMATION process_info; STARTUPINFO startup_info; char slotdirpath[256]; memset( &process_info, 0, sizeof( process_info ) ); memset( &startup_info, 0, sizeof( startup_info ) ); startup_info.cb = sizeof(startup_info); startup_info.lpReserved = NULL; startup_info.lpDesktop = ""; // NOTE: in Windows, stderr is redirected within boinc_init(); // Need to condense argv into a single string //if (log_flags.task_debug) print_argv(argv); // full_path(slot_dir, slotdirpath); if (!CreateProcess(exec_path, wup->command_line, NULL, NULL, FALSE, CREATE_NEW_PROCESS_GROUP|CREATE_NO_WINDOW|NORMAL_PRIORITY_CLASS, NULL, slotdirpath, &startup_info, &process_info )) { state = GetLastError(); LPVOID lpMsgBuf; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, state, 0, // Default language (LPTSTR) &lpMsgBuf, 0, NULL ); fprintf(stdout, "CreateProcess: %s\n", (LPCTSTR)lpMsgBuf); } pid_handle = process_info.hProcess; #endif state = PROCESS_RUNNING; return 0; } // Sends a request to the process of this active task to exit. If it // doesn't exist within a set time (seconds), the process is terminated // void ACTIVE_TASK::request_exit(int seconds) { int retval; if(seconds<0) { fprintf(stderr, "error: ACTIVE_TASK.request_exit: negative seconds\n"); seconds=0; } #if HAVE_SIGNAL_H #if HAVE_SYS_TYPES_H retval = kill(pid, SIGTERM); boinc_sleep(seconds); while(retval) retval=kill(pid, SIGKILL); #endif #endif #ifdef _WIN32 retval = TerminateProcess(pid_handle, -1);//exit codes should be changed boinc_sleep(seconds); while(retval) retval=TerminateProcess(pid_handle, -1); #endif } // Inserts an active task into the ACTIVE_TASK_SET and starts it up // int ACTIVE_TASK_SET::insert(ACTIVE_TASK* atp) { int retval; get_slot_dir(atp->slot, atp->slot_dir); clean_out_dir(atp->slot_dir); retval = atp->start(true); if (retval) return retval; active_tasks.push_back(atp); return 0; } // Checks if any child processes have exited and records their final CPU time // bool ACTIVE_TASK_SET::poll() { ACTIVE_TASK* atp; char path[256]; int n; #ifdef _WIN32 unsigned long exit_code; int i; FILETIME creation_time, exit_time, kernel_time, user_time; ULARGE_INTEGER tKernel, tUser; LONGLONG totTime; bool found = false; for (i=0; ipid_handle, &exit_code)) { // Get the elapsed CPU time if (GetProcessTimes(atp->pid_handle, &creation_time, &exit_time, &kernel_time, &user_time)) { tKernel.LowPart = kernel_time.dwLowDateTime; tKernel.HighPart = kernel_time.dwHighDateTime; tUser.LowPart = user_time.dwLowDateTime; tUser.HighPart = user_time.dwHighDateTime; // Runtimes in 100-nanosecond units totTime = tKernel.QuadPart + tUser.QuadPart; atp->result->final_cpu_time = (totTime / 10000000.0); } else { // This probably isn't correct atp->result->final_cpu_time = ((double)clock())/CLOCKS_PER_SEC; } if (exit_code != STILL_ACTIVE) { found = true; // Not sure how to incorporate the other states (WAS_SIGNALED, etc) atp->state = PROCESS_EXITED; atp->exit_status = exit_code; atp->result->exit_status = atp->exit_status; } } } return found; #endif #if HAVE_SYS_RESOURCE_H #if HAVE_SYS_WAIT_H #if HAVE_SYS_TIME_H struct rusage rs; int pid; int stat; pid = wait3(&stat, WNOHANG, &rs); if (pid <= 0) return false; if (log_flags.task_debug) printf("got signal for process %d\n", pid); atp = lookup_pid(pid); if (!atp) { fprintf(stderr, "ACTIVE_TASK_SET::poll(): pid %d not found\n", pid); return true; } double x = rs.ru_utime.tv_sec + rs.ru_utime.tv_usec/1.e6; atp->result->final_cpu_time = atp->starting_cpu_time + x; if (WIFEXITED(stat)) { atp->state = PROCESS_EXITED; atp->exit_status = WEXITSTATUS(stat); atp->result->exit_status = atp->exit_status; if (log_flags.task_debug) printf("process exited: status %d\n", atp->exit_status); } else if (WIFSIGNALED(stat)) { atp->state = PROCESS_WAS_SIGNALED; atp->signal = WTERMSIG(stat); atp->result->exit_status = atp->signal; if (log_flags.task_debug) printf("process was signaled: %d\n", atp->signal); } else { atp->state = PROCESS_EXIT_UNKNOWN; atp->result->exit_status = -1; } #endif #endif #endif // check for the stderr file, copy to result record // sprintf(path, "%s/%s", atp->slot_dir, STDERR_FILE); FILE* f = fopen(path, "r"); if (f) { n = fread(atp->result->stderr_out, 1, STDERR_MAX_LEN, f); atp->result->stderr_out[STDERR_MAX_LEN-1] = 0; fclose(f); } clean_out_dir(atp->slot_dir); return true; } // Find the ACTIVE_TASK in the current set with the matching PID // ACTIVE_TASK* ACTIVE_TASK_SET::lookup_pid(int pid) { unsigned int i; ACTIVE_TASK* atp; for (i=0; ipid == pid) return atp; } return NULL; } // Sends a suspend request to all currently running computation processes // void ACTIVE_TASK_SET::suspend_all() { unsigned int i; ACTIVE_TASK* atp; for (i=0; isuspend(); } } // Sends a resume signal to all currently running computation processes // void ACTIVE_TASK_SET::unsuspend_all() { unsigned int i; ACTIVE_TASK* atp; for (i=0; iunsuspend(); } } // Attempts to exit all currently running computation processes, either // via exit request or termination // void ACTIVE_TASK_SET::exit_tasks() { unsigned int i; ACTIVE_TASK *atp; for (i=0; irequest_exit(0); atp->check_app_status_files(); } } #ifdef _WIN32 // Send a suspend request to the ACTIVE_TASK // void ACTIVE_TASK::suspend() { // figure out a way to do this, perhaps via trigger file? //kill(atp->pid, SIGSTOP); } // Send a resume request to the ACTIVE_TASK // void ACTIVE_TASK::unsuspend() { // figure out a way to do this, perhaps via trigger file? //kill(atp->pid, SIGCONT); } #else // Send a suspend request to the ACTIVE_TASK // void ACTIVE_TASK::suspend() { kill(this->pid, SIGSTOP); } // Send a resume request to the ACTIVE_TASK // void ACTIVE_TASK::unsuspend() { kill(this->pid, SIGCONT); } #endif // Remove an ACTIVE_TASK from the set, assumes that the task has // already been shut down via request_exit or similar means // int ACTIVE_TASK_SET::remove(ACTIVE_TASK* atp) { vector::iterator iter; iter = active_tasks.begin(); while (iter != active_tasks.end()) { if (*iter == atp) { active_tasks.erase(iter); return 0; } iter++; } fprintf(stderr, "ACTIVE_TASK_SET::remove(): not found\n"); return 1; } // Restart active tasks without wiping and reinitializing slot directories // int ACTIVE_TASK_SET::restart_tasks() { vector::iterator iter; ACTIVE_TASK* atp; int retval; iter = active_tasks.begin(); while (iter != active_tasks.end()) { atp = *iter; get_slot_dir(atp->slot, atp->slot_dir); retval = atp->start(false); if (log_flags.task) { printf("restarting application for result %s\n", atp->result->name); } if (retval) { fprintf(stderr, "ACTIVE_TASKS::restart_tasks(); restart failed: %d\n", retval); active_tasks.erase(iter); } else { iter++; } } return 0; } // See if the app has generated new checkpoint CPU or fraction-done files. // If so read them and return true. // bool ACTIVE_TASK::check_app_status_files() { FILE* f; char path[256]; bool found = false; int retval; sprintf(path, "%s/%s", slot_dir, CHECKPOINT_CPU_FILE); f = fopen(path, "r"); if (f) { found = true; parse_checkpoint_cpu_file(f, checkpoint_cpu_time); fclose(f); retval = file_delete(path); if (retval) { fprintf(stderr, "ACTIVE_TASK.check_app_status_files: could not delete %s: %d\n", path, retval ); } } sprintf(path, "%s/%s", slot_dir, FRACTION_DONE_FILE); f = fopen(path, "r"); if (f) { found = true; parse_fraction_done_file(f, current_cpu_time, fraction_done); fclose(f); retval = file_delete(path); if (retval) { fprintf(stderr, "ACTIVE_TASK.check_app_status_files: could not delete %s: %d\n", path, retval ); } } return found; } // Poll each of the currently running tasks and get their CPU time // bool ACTIVE_TASK_SET::poll_time() { ACTIVE_TASK* atp; unsigned int i; bool updated = false; for (i=0; icheck_app_status_files(); } return updated; } // Gets the next available free slot, or returns -1 if all slots are full // TODO: don't use malloc here // int ACTIVE_TASK_SET::get_free_slot(int total_slots) { unsigned int i; char *slot_status; if (active_tasks.size() >= (unsigned int)total_slots) { return -1; } slot_status = (char *)calloc( sizeof(char), total_slots ); if (!slot_status) return -1; for (i=0; islot >= 0 && active_tasks[i]->slot < total_slots) { slot_status[active_tasks[i]->slot] = 1; } } for (i=0; i<(unsigned int)total_slots; i++) { if (!slot_status[i]) { free(slot_status); return i; } } free(slot_status); return -1; } // Write XML data about this ACTIVE_TASK // int ACTIVE_TASK::write(FILE* fout) { fprintf(fout, "\n" " %s\n" " %s\n" " %d\n" " %d\n" " %f\n" "\n", result->project->master_url, result->name, app_version->version_num, slot, checkpoint_cpu_time ); return 0; } // Parse XML information about an active task // int ACTIVE_TASK::parse(FILE* fin, CLIENT_STATE* cs) { char buf[256], result_name[256], project_master_url[256]; int app_version_num=0; PROJECT* project; strcpy(result_name, ""); strcpy(project_master_url, ""); while (fgets(buf, 256, fin)) { if (match_tag(buf, "")) { project = cs->lookup_project(project_master_url); if (!project) { fprintf(stderr, "ACTIVE_TASK::parse(): project not found: %s\n", project_master_url ); return -1; } result = cs->lookup_result(project, result_name); if (!result) { fprintf(stderr, "ACTIVE_TASK::parse(): result not found\n"); return -1; } wup = result->wup; app_version = cs->lookup_app_version( result->app, app_version_num ); if (!app_version) { fprintf(stderr, "ACTIVE_TASK::parse(): app_version not found\n"); return -1; } return 0; } else if (parse_str(buf, "", result_name, sizeof(result_name))) continue; else if (parse_str(buf, "", project_master_url, sizeof(project_master_url))) continue; else if (parse_int(buf, "", app_version_num)) continue; else if (parse_int(buf, "", slot)) continue; else if (parse_double(buf, "", checkpoint_cpu_time)) continue; else fprintf(stderr, "ACTIVE_TASK::parse(): unrecognized %s\n", buf); } return -1; } // Write XML information about this active task set // int ACTIVE_TASK_SET::write(FILE* fout) { unsigned int i; fprintf(fout, "\n"); for (i=0; iwrite(fout); } fprintf(fout, "\n"); return 0; } // Parse XML information about an active task set // int ACTIVE_TASK_SET::parse(FILE* fin, CLIENT_STATE* cs) { ACTIVE_TASK* atp; char buf[256]; int retval; while (fgets(buf, 256, fin)) { if (match_tag(buf, "")) return 0; else if (match_tag(buf, "")) { atp = new ACTIVE_TASK; retval = atp->parse(fin, cs); if (!retval) active_tasks.push_back(atp); else delete atp; } else { fprintf(stderr, "ACTIVE_TASK_SET::parse(): unrecognized %s\n", buf); } } return 0; }