// 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 . #include "cpp.h" #ifdef _WIN32 #include "boinc_win.h" #else #include "config.h" #include #include #include #include #include #include #endif #include "error_numbers.h" #include "file_names.h" #include "util.h" #include "str_util.h" #include "str_replace.h" #include "filesys.h" #include "parse.h" #include "client_msgs.h" #include "client_state.h" #include "sandbox.h" bool g_use_sandbox = false; #ifndef _WIN32 // POSIX requires that shells run from an application will use the // real UID and GID if different from the effective UID and GID. // Mac OS 10.4 did not enforce this, but OS 10.5 does. Since // system() invokes a shell, we can't use it to run the switcher // or setprojectgrp utilities, so we must do a fork() and execv(). // int switcher_exec(const char *util_filename, const char* cmdline) { char* argv[100]; char util_path[MAXPATHLEN]; char command [1024]; char buffer[1024]; int fds_out[2], fds_err[2]; int stat; int retval; std::string output_out, output_err; sprintf(util_path, "%s/%s", SWITCHER_DIR, util_filename); argv[0] = const_cast(util_filename); // Make a copy of cmdline because parse_command_line modifies it safe_strcpy(command, cmdline); parse_command_line(const_cast(cmdline), argv+1); // Create the output pipes if (pipe(fds_out) == -1) { perror("pipe() for fds_out failed in switcher_exec"); return ERR_PIPE; } if (pipe(fds_err) == -1) { perror("pipe() for fds_err failed in switcher_exec"); return ERR_PIPE; } int pid = fork(); if (pid == -1) { perror("fork() failed in switcher_exec"); return ERR_FORK; } if (pid == 0) { // This is the new (forked) process // Setup pipe redirects while ((dup2(fds_out[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {} while ((dup2(fds_err[1], STDERR_FILENO) == -1) && (errno == EINTR)) {} // Child only needs one-way (write) pipes so close read pipes close(fds_out[0]); close(fds_err[0]); execv(util_path, argv); fprintf(stderr, "execv failed in switcher_exec(%s, %s): %s", util_path, cmdline, strerror(errno)); _exit(EXIT_FAILURE); } // Parent only needs one-way (read) pipes so close write pipes close(fds_out[1]); close(fds_err[1]); // Capture stdout output while (1) { ssize_t count = read(fds_out[0], buffer, sizeof(buffer)); if (count == -1) { if (errno == EINTR) { continue; } else { break; } } else if (count == 0) { break; } else { buffer[count] = '\0'; output_out += buffer; } } // Capture stderr output while (1) { ssize_t count = read(fds_err[0], buffer, sizeof(buffer)); if (count == -1) { if (errno == EINTR) { continue; } else { break; } } else if (count == 0) { break; } else { buffer[count] = '\0'; output_err += buffer; } } // Wait for command to complete, like system() does. waitpid(pid, &stat, 0); // Close pipe descriptors close(fds_out[0]); close(fds_err[0]); if (WIFEXITED(stat)) { retval = WEXITSTATUS(stat); if (retval) { if (log_flags.task_debug) { msg_printf(0, MSG_INTERNAL_ERROR, "[task_debug] failure in switcher_exec"); msg_printf(0, MSG_INTERNAL_ERROR, "[task_debug] switcher: %s", util_path); msg_printf(0, MSG_INTERNAL_ERROR, "[task_debug] command: %s", command); msg_printf(0, MSG_INTERNAL_ERROR, "[task_debug] exit code: %d", retval); msg_printf(0, MSG_INTERNAL_ERROR, "[task_debug] stdout: %s", output_out.c_str()); msg_printf(0, MSG_INTERNAL_ERROR, "[task_debug] stderr: %s", output_err.c_str()); } } return retval; } return 0; } int kill_via_switcher(int pid) { char cmd[1024]; if (!g_use_sandbox) return 0; // if project application is running as user boinc_project and // client is running as user boinc_master, // we cannot send a signal directly, so use switcher. // sprintf(cmd, "/bin/kill kill -s KILL %d", pid); return switcher_exec(SWITCHER_FILE_NAME, cmd); } #ifndef _DEBUG static int lookup_group(const char* name, gid_t& gid) { struct group* gp = getgrnam(name); if (!gp) return ERR_GETGRNAM; gid = gp->gr_gid; return 0; } #endif int remove_project_owned_file_or_dir(const char* path) { char cmd[1024]; if (g_use_sandbox) { sprintf(cmd, "/bin/rm rm -fR \"%s\"", path); if (switcher_exec(SWITCHER_FILE_NAME, cmd)) { return ERR_UNLINK; } else { return 0; } } return ERR_UNLINK; } int get_project_gid() { if (g_use_sandbox) { #ifdef _DEBUG // GDB can't attach to applications which are running as a different user // or group, so fix up data with current user and group during debugging gstate.boinc_project_gid = getegid(); #else return lookup_group(BOINC_PROJECT_GROUP_NAME, gstate.boinc_project_gid); #endif // _DEBUG } else { gstate.boinc_project_gid = 0; } return 0; } int set_to_project_group(const char* path) { if (g_use_sandbox) { if (switcher_exec(SETPROJECTGRP_FILE_NAME, path)) { return ERR_CHOWN; } } return 0; } #else int get_project_gid() { return 0; } int set_to_project_group(const char*) { return 0; } #endif // ! _WIN32 // delete a file. // return success if we deleted it or it didn't exist in the first place // static int delete_project_owned_file_aux(const char* path) { #ifdef _WIN32 if (DeleteFile(path)) return 0; int error = GetLastError(); if (error == ERROR_FILE_NOT_FOUND) { return 0; } if (error == ERROR_ACCESS_DENIED) { SetFileAttributes(path, FILE_ATTRIBUTE_NORMAL); if (DeleteFile(path)) return 0; } return error; #else int retval = unlink(path); if (retval == 0) return 0; if (errno == ENOENT) { return 0; } if (g_use_sandbox && (errno == EACCES)) { // We may not have permission to read subdirectories created by projects // return remove_project_owned_file_or_dir(path); } return ERR_UNLINK; #endif } // Delete the file located at path. // If "retry" is set, do retries for 5 sec in case some // other program (e.g. virus checker) has the file locked. // Don't do this if deleting directories - it can lock up the Manager. // int delete_project_owned_file(const char* path, bool retry) { int retval = 0; retval = delete_project_owned_file_aux(path); if (retval && retry) { if (log_flags.slot_debug) { msg_printf(0, MSG_INFO, "[slot] delete of %s failed (%d); retrying", path, retval ); } double start = dtime(); do { boinc_sleep(drand()*2); // avoid lockstep retval = delete_project_owned_file_aux(path); if (!retval) break; } while (dtime() < start + FILE_RETRY_INTERVAL); } if (retval) { if (log_flags.slot_debug) { msg_printf(0, MSG_INFO, "[slot] failed to remove file %s: %s", path, boincerror(retval) ); } safe_strcpy(boinc_failed_file, path); return ERR_UNLINK; } if (log_flags.slot_debug) { msg_printf(0, MSG_INFO, "[slot] removed file %s", path); } return 0; } // recursively delete everything in the specified directory // (but not the directory itself). // If an error occurs, delete as much as possible. // int client_clean_out_dir(const char* dirpath, const char* reason) { char filename[MAXPATHLEN], path[MAXPATHLEN]; int retval, final_retval = 0; DIRREF dirp; if (reason && log_flags.slot_debug) { msg_printf(0, MSG_INFO, "[slot] cleaning out %s: %s", dirpath, reason); } dirp = dir_open(dirpath); if (!dirp) { #ifndef _WIN32 if (g_use_sandbox && (errno == EACCES)) { // dir may be owned by boinc_apps return remove_project_owned_file_or_dir(dirpath); } #endif return 0; // if dir doesn't exist, it's empty } while (1) { strcpy(filename, ""); retval = dir_scan(filename, dirp, sizeof(filename)); if (retval) { if (retval != ERR_NOT_FOUND) { if (log_flags.slot_debug) { msg_printf(0, MSG_INFO, "[slot] dir_scan(%s) failed: %s", dirpath, boincerror(retval) ); } final_retval = retval; } break; } sprintf(path, "%s/%s", dirpath, filename); if (is_dir(path)) { retval = client_clean_out_dir(path, NULL); if (retval) final_retval = retval; retval = remove_project_owned_dir(path); if (retval) final_retval = retval; } else { retval = delete_project_owned_file(path, false); if (retval) final_retval = retval; } } dir_close(dirp); return final_retval; } int remove_project_owned_dir(const char* name) { #ifdef _WIN32 if (!RemoveDirectory(name)) { return GetLastError(); } return 0; #else int retval; retval = rmdir(name); // We may not have permission to read subdirectories created by projects if (retval && g_use_sandbox && (errno == EACCES)) { retval = remove_project_owned_file_or_dir(name); } return retval; #endif }