Esempio n. 1
0
/// Executes an external binary and replaces the current process.
///
/// This differs from process::exec() in that this function reports errors
/// caused by the exec(2) system call to let the caller decide how to handle
/// them.
///
/// This function must not use any of the logging features so that the output
/// of the subprocess is not "polluted" by our own messages.
///
/// This function must also not affect the global state of the current process
/// as otherwise we would not be able to use vfork().  Only state stored in the
/// stack can be touched.
///
/// \param program The binary to execute.
/// \param args The arguments to pass to the binary, without the program name.
///
/// \throw system_error If the exec(2) call fails.
void
process::exec_unsafe(const fs::path& program, const args_vector& args)
{
    PRE(args.size() < MAX_ARGS);
    int original_errno = 0;
    try {
        const char* argv[MAX_ARGS + 1];

        argv[0] = program.c_str();
        for (args_vector::size_type i = 0; i < args.size(); i++)
            argv[1 + i] = args[i].c_str();
        argv[1 + args.size()] = NULL;

        const int ret = ::execv(program.c_str(),
                                (char* const*)(unsigned long)(const void*)argv);
        original_errno = errno;
        INV(ret == -1);
        std::cerr << "Failed to execute " << program << ": "
                  << std::strerror(original_errno) << "\n";
    } catch (const std::runtime_error& error) {
        std::cerr << "Failed to execute " << program << ": "
                  << error.what() << "\n";
        std::abort();
    } catch (...) {
        std::cerr << "Failed to execute " << program << "; got unexpected "
            "exception during exec\n";
        std::abort();
    }

    // We must do this here to prevent our exception from being caught by the
    // generic handlers above.
    INV(original_errno != 0);
    throw system_error("Failed to execute " + program.str(), original_errno);
}
Esempio n. 2
0
/// Initializes an empty database.
///
/// \param db The database to initialize.
///
/// \return The metadata record written into the new database.
///
/// \throw store::error If there is a problem initializing the database.
store::metadata
store::detail::initialize(sqlite::database& db)
{
    PRE(empty_database(db));

    const fs::path schema = schema_file();

    std::ifstream input(schema.c_str());
    if (!input)
        throw error(F("Cannot open database schema '%s'") % schema);

    LI(F("Populating new database with schema from %s") % schema);
    const std::string schema_string = utils::read_stream(input);
    try {
        db.exec(schema_string);

        const metadata metadata = metadata::fetch_latest(db);
        LI(F("New metadata entry %s") % metadata.timestamp());
        if (metadata.schema_version() != detail::current_schema_version) {
            UNREACHABLE_MSG(F("current_schema_version is out of sync with "
                              "%s") % schema);
        }
        return metadata;
    } catch (const store::integrity_error& e) {
        // Could be raised by metadata::fetch_latest.
        UNREACHABLE_MSG("Inconsistent code while creating a database");
    } catch (const sqlite::error& e) {
        throw error(F("Failed to initialize database: %s") % e.what());
    }
}
Esempio n. 3
0
/// Creates a directory.
///
/// \param dir The path to the directory to create.
/// \param mode The permissions for the new directory.
///
/// \throw system_error If the call to mkdir(2) fails.
void
fs::mkdir(const fs::path& dir, const int mode)
{
    if (::mkdir(dir.c_str(), static_cast< mode_t >(mode)) == -1) {
        const int original_errno = errno;
        throw fs::system_error(F("Failed to create directory %s") % dir,
                               original_errno);
    }
}
Esempio n. 4
0
/// Backs up a database for schema migration purposes.
///
/// \todo We should probably use the SQLite backup API instead of doing a raw
/// file copy.  We issue our backup call with the database already open, but
/// because it is quiescent, it's OK to do so.
///
/// \param source Location of the database to be backed up.
/// \param old_version Version of the database's CURRENT schema, used to
///     determine the name of the backup file.
///
/// \throw error If there is a problem during the backup.
void
store::detail::backup_database(const fs::path& source, const int old_version)
{
    const fs::path target(F("%s.v%s.backup") % source.str() % old_version);

    LI(F("Backing up database %s to %s") % source % target);

    std::ifstream input(source.c_str());
    if (!input)
        throw error(F("Cannot open database file %s") % source);

    std::ofstream output(target.c_str());
    if (!output)
        throw error(F("Cannot create database backup file %s") % target);

    char buffer[1024];
    while (input.good()) {
        input.read(buffer, sizeof(buffer));
        if (input.good() || input.eof())
            output.write(buffer, input.gcount());
    }
    if (!input.good() && !input.eof())
        throw error(F("Error while reading input file %s") % source);
}
Esempio n. 5
0
/// Creates a temporary file.
///
/// The temporary file is created using mkstemp(3) using the provided template.
/// This should be most likely used in conjunction with fs::auto_file.
///
/// \param path_template The template for the temporary path, which is a
///     basename that is created within the TMPDIR.  Must contain the XXXXXX
///     pattern, which is atomically replaced by a random unique string.
///
/// \return The generated path for the temporary directory.
///
/// \throw fs::system_error If the call to mkstemp(3) fails.
fs::path
fs::mkstemp(const std::string& path_template)
{
    PRE(path_template.find("XXXXXX") != std::string::npos);

    const fs::path tmpdir(utils::getenv_with_default("TMPDIR", "/tmp"));
    const fs::path full_template = tmpdir / path_template;

    utils::auto_array< char > buf(new char[full_template.str().length() + 1]);
    std::strcpy(buf.get(), full_template.c_str());
    if (::mkstemp(buf.get()) == -1) {
        const int original_errno = errno;
        throw fs::system_error(F("Cannot create temporary file using template "
                                 "%s") % full_template, original_errno);
    }
    return fs::path(buf.get());
}
Esempio n. 6
0
/// Computes the test cases list of a test program.
///
/// \param status The termination status of the subprocess used to execute
///     the exec_test() method or none if the test timed out.
/// \param stdout_path Path to the file containing the stdout of the test.
/// \param stderr_path Path to the file containing the stderr of the test.
///
/// \return A list of test cases.
///
/// \throw error If there is a problem parsing the test case list.
model::test_cases_map
engine::atf_interface::parse_list(const optional< process::status >& status,
                                  const fs::path& stdout_path,
                                  const fs::path& stderr_path) const
{
    const std::string stderr_contents = utils::read_file(stderr_path);
    if (!stderr_contents.empty())
        LW("Test case list wrote to stderr: " + stderr_contents);

    if (!status)
        throw engine::error("Test case list timed out");
    if (status.get().exited()) {
        const int exitstatus = status.get().exitstatus();
        if (exitstatus == EXIT_SUCCESS) {
            // Nothing to do; fall through.
        } else if (exitstatus == exit_eacces) {
            throw engine::error("Permission denied to run test program");
        } else if (exitstatus == exit_enoent) {
            throw engine::error("Cannot find test program");
        } else if (exitstatus == exit_enoexec) {
            throw engine::error("Invalid test program format");
        } else {
            throw engine::error("Test program did not exit cleanly");
        }
    } else {
        throw engine::error("Test program received signal");
    }

    std::ifstream input(stdout_path.c_str());
    if (!input)
        throw engine::load_error(stdout_path, "Cannot open file for read");
    const model::test_cases_map test_cases = parse_atf_list(input);

    if (!stderr_contents.empty())
        throw engine::error("Test case list wrote to stderr");

    return test_cases;
}
Esempio n. 7
0
/// Recursively removes a directory.
///
/// This operation simulates a "rm -r".  No effort is made to forcibly delete
/// files and no attention is paid to mount points.
///
/// \param directory The directory to remove.
///
/// \throw fs::error If there is a problem removing any directory or file.
void
fs::rm_r(const fs::path& directory)
{
    DIR* dirp = ::opendir(directory.c_str());
    if (dirp == NULL) {
        const int original_errno = errno;
        throw fs::system_error(F("Failed to open directory %s") %
                               directory.str(), original_errno);
    }
    try {
        ::dirent* dp;
        while ((dp = ::readdir(dirp)) != NULL) {
            const std::string name = dp->d_name;
            if (name == "." || name == "..")
                continue;

            const fs::path entry = directory / dp->d_name;

            const struct ::stat sb = safe_stat(entry);
            if (S_ISDIR(sb.st_mode)) {
                LD(F("Descending into %s") % entry);
                fs::rm_r(entry);
            } else {
                LD(F("Removing file %s") % entry);
                fs::unlink(entry);
            }
        }
    } catch (...) {
        ::closedir(dirp);
        throw;
    }
    ::closedir(dirp);

    LD(F("Removing empty directory %s") % directory);
    fs::rmdir(directory);
}
Esempio n. 8
0
/// Checks if a file exists.
///
/// Be aware that this is racy in the same way as access(2) is.
///
/// \param path The file to check the existance of.
///
/// \return True if the file exists; false otherwise.
bool
fs::exists(const fs::path& path)
{
    return ::access(path.c_str(), F_OK) == 0;
}
Esempio n. 9
0
/// Waits for completion of any forked test case.
///
/// Note that if the terminated test case has a cleanup routine, this function
/// is the one in charge of spawning the cleanup routine asynchronously.
///
/// \return The result of the execution of a subprocess.  This is a dynamically
/// allocated object because the scheduler can spawn subprocesses of various
/// types and, at wait time, we don't know upfront what we are going to get.
scheduler::result_handle_ptr
scheduler::scheduler_handle::wait_any(void)
{
    _pimpl->generic.check_interrupt();

    executor::exit_handle handle = _pimpl->generic.wait_any();

    const exec_data_map::iterator iter = _pimpl->all_exec_data.find(
        handle.original_pid());
    exec_data_ptr& data = (*iter).second;

    utils::dump_stacktrace_if_available(data->test_program->absolute_path(),
                                        _pimpl->generic, handle);

    optional< model::test_result > result;
    try {
        test_exec_data* test_data = &dynamic_cast< test_exec_data& >(
            *data.get());

        test_data->exit_handle = handle;

        const model::test_case& test_case = test_data->test_program->find(
            test_data->test_case_name);

        result = test_case.fake_result();

        if (!result && handle.status() && handle.status().get().exited() &&
            handle.status().get().exitstatus() == exit_skipped) {
            // If the test's process terminated with our magic "exit_skipped"
            // status, there are two cases to handle.  The first is the case
            // where the "skipped cookie" exists, in which case we never got to
            // actually invoke the test program; if that's the case, handle it
            // here.  The second case is where the test case actually decided to
            // exit with the "exit_skipped" status; in that case, just fall back
            // to the regular status handling.
            const fs::path skipped_cookie_path = handle.control_directory() /
                skipped_cookie;
            std::ifstream input(skipped_cookie_path.c_str());
            if (input) {
                result = model::test_result(model::test_result_skipped,
                                            utils::read_stream(input));
                input.close();

                // If we determined that the test needs to be skipped, we do not
                // want to run the cleanup routine because doing so could result
                // in errors.  However, we still want to run the cleanup routine
                // if the test's body reports a skip (because actions could have
                // already been taken).
                test_data->needs_cleanup = false;
            }
        }
        if (!result) {
            result = test_data->interface->compute_result(
                handle.status(),
                handle.control_directory(),
                handle.stdout_file(),
                handle.stderr_file());
        }
        INV(result);

        if (!result.get().good()) {
            append_files_listing(handle.work_directory(),
                                 handle.stderr_file());
        }

        if (test_data->needs_cleanup) {
            INV(test_case.get_metadata().has_cleanup());
            // The test body has completed and we have processed it.  If there
            // is a cleanup routine, trigger it now and wait for any other test
            // completion.  The caller never knows about cleanup routines.
            _pimpl->spawn_cleanup(test_data->test_program,
                                  test_data->test_case_name,
                                  test_data->user_config, handle, result.get());
            test_data->needs_cleanup = false;

            // TODO(jmmv): Chaining this call is ugly.  We'd be better off by
            // looping over terminated processes until we got a result suitable
            // for user consumption.  For the time being this is good enough and
            // not a problem because the call chain won't get big: the majority
            // of test cases do not have cleanup routines.
            return wait_any();
        }
    } catch (const std::bad_cast& e) {
        const cleanup_exec_data* cleanup_data =
            &dynamic_cast< const cleanup_exec_data& >(*data.get());

        // Handle the completion of cleanup subprocesses internally: the caller
        // is not aware that these exist so, when we return, we must return the
        // data for the original test that triggered this routine.  For example,
        // because the caller wants to see the exact same exec_handle that was
        // returned by spawn_test.

        const model::test_result& body_result = cleanup_data->body_result;
        if (body_result.good()) {
            if (!handle.status()) {
                result = model::test_result(model::test_result_broken,
                                            "Test case cleanup timed out");
            } else {
                if (!handle.status().get().exited() ||
                    handle.status().get().exitstatus() != EXIT_SUCCESS) {
                    result = model::test_result(
                        model::test_result_broken,
                        "Test case cleanup did not terminate successfully");
                } else {
                    result = body_result;
                }
            }
        } else {
            result = body_result;
        }

        handle = cleanup_data->body_exit_handle;
    }
    INV(result);

    std::shared_ptr< result_handle::bimpl > result_handle_bimpl(
        new result_handle::bimpl(handle, _pimpl->all_exec_data));
    std::shared_ptr< test_result_handle::impl > test_result_handle_impl(
        new test_result_handle::impl(
            data->test_program, data->test_case_name, result.get()));
    return result_handle_ptr(new test_result_handle(result_handle_bimpl,
                                                    test_result_handle_impl));
}