/* -*-Mode: C++;-*- * PRCS - The Project Revision Control System * Copyright (C) 1997 Josh MacDonald * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id: execute.cc 1.10.1.26 Sat, 30 Oct 1999 18:47:14 -0700 jmacd $ */ extern "C" { #include } #include "prcs.h" extern "C" { #include #ifdef HAVE_VFORK_H #include #endif } #include "projdesc.h" #include "checkin.h" #include "fileent.h" #include "misc.h" #include "repository.h" #include "checkout.h" #include "vc.h" #include "system.h" #include "syscmd.h" static bool execute_matched(FileEntry*); static bool execute_matched_match(FileEntry* fe, reg2ex2_t *re); static void add_fake(ProjectDescriptor *project, const char* name, const char* tag, FileTable *table, FileEntryPtrArray* add_to); static FileEntryPtrArray* eliminate_unmatched_files(ProjectDescriptor* project); static void maybe_add_subdirs_file(FileEntry* fe, FileTable* table, FileEntryPtrArray* add_to); static PrVoidError init_regex(); static int compare_fileent(const void* a, const void* b); static PrVoidError do_execute_all(FileEntryPtrArray*, ProjectDescriptor*); static PrVoidError do_execute(FileEntryPtrArray*, ProjectDescriptor*); static PrVoidError do_execute_file(FileEntry* fe, ProjectDescriptor*); static PrBoolError prepare_arg(FileEntryPtrArray* fe_ptrs, const char* arg, ProjectDescriptor* project, ArgList* args); static PrProjectDescriptorPtrError get_execute_project(); static PrConstCharPtrError make_execute_temp_file(FileEntry* fe, ProjectDescriptor* project); static PrIntError exec_command(ArgList* args); static PrVoidError open_exec_file(FileEntry* fe, ProjectDescriptor* project); static PrVoidError close_exec_file(FileEntry* fe); static reg2ex2_t *match_regex; static reg2ex2_t *not_match_regex; static bool any_failures; static int current_file_descriptor; static FILE* current_co_stream; PrPrcsExitStatusError execute_command() { any_failures = false; Return_if_fail(init_regex()); ProjectDescriptor *project; Return_if_fail(project << get_execute_project()); eliminate_unnamed_files(project); Return_if_fail(warn_unused_files(false)); FileEntryPtrArray *all_files = eliminate_unmatched_files(project); if (cmd_diff_options_count < 1) { /* @@@ Undocumented, no command, just print all names. */ kill_prefix(prcsoutput); foreach(fe_ptr, all_files, FileEntryPtrArray::ArrayIterator) prcsoutput << (*fe_ptr)->working_path() << prcsendl; } else if (option_all_files) { Return_if_fail(do_execute_all(all_files, project)); } else { Return_if_fail(do_execute(all_files, project)); } return ExitSuccess; } static PrProjectDescriptorPtrError get_execute_project() { ProjectDescriptor *project; if (!option_version_present) { Return_if_fail(project << read_project_file(cmd_root_project_full_name, cmd_root_project_file_path, true, KeepNothing)); prcsinfo << "Executing command for working project" << dotendl; } else { RepEntry* rep_entry; ProjectVersionData* version_data; Return_if_fail(rep_entry << Rep_init_repository_entry(cmd_root_project_name, false, false, true)); Return_if_fail(version_data << resolve_version(cmd_version_specifier_major, cmd_version_specifier_minor, cmd_root_project_full_name, cmd_root_project_file_path, NULL, rep_entry)); if (version_data->prcs_minor_int() == 0) { Return_if_fail(project << checkout_create_empty_prj_file(temp_file_1, cmd_root_project_full_name, version_data->prcs_major(), KeepNothing)); } else { Return_if_fail(project << rep_entry -> checkout_create_prj_file(temp_file_1, cmd_root_project_full_name, version_data->rcs_version(), KeepNothing)); } project->repository_entry(rep_entry); prcsinfo << "Executing command for version " << version_data << dotendl; } return project; } static PrVoidError init_regex() { if (option_match_file) { match_regex = new reg2ex2_t; Return_if_fail(prcs_compile_regex(option_match_file_pattern, match_regex)); } if (option_not_match_file) { not_match_regex = new reg2ex2_t; Return_if_fail(prcs_compile_regex(option_not_match_file_pattern, not_match_regex)); } return NoError; } static bool execute_matched_match(FileEntry* fe, reg2ex2_t *re) { if (prcs_regex_matches(fe->working_path(), re)) return true; return fe->file_attrs()->regex_matches(re); } static bool execute_matched(FileEntry* fe) { if ( option_match_file && !execute_matched_match(fe, match_regex)) { if (option_long_format) prcsinfo << "Match failed for " << squote(fe->working_path()) << dotendl; return false; } if (option_not_match_file && execute_matched_match(fe, not_match_regex)) { if (option_long_format) prcsinfo << "Match failed for " << squote(fe->working_path()) << dotendl; return false; } return true; } static FileEntryPtrArray* eliminate_unmatched_files(ProjectDescriptor* project) { FileTable table(pathname_hash, pathname_equal); FileEntryPtrArray* all_files = new FileEntryPtrArray; if (cmd_prj_given_as_file) add_fake (project, project->project_file_path(), ":project-file", &table, all_files); foreach_fileent(fe_ptr, project) { FileEntry* fe = *fe_ptr; /* This implies that :implicit-directory files exist even when * the files in them don't. Otherwise, there is no other way * to get only the directories (because the real files in them * won't match. */ table.insert (fe->working_path(), fe /*bogus but not used*/); if (!fe->on_command_line() || !execute_matched(fe)) continue; all_files->append (fe); maybe_add_subdirs_file(*fe_ptr, &table, all_files); } all_files->sort(compare_fileent); return all_files; } static void maybe_add_subdirs_file(FileEntry* fe, FileTable* table, FileEntryPtrArray* add_to) { char dir[MAXPATHLEN]; char* name = dir; strncpy(dir, fe->working_path(), MAXPATHLEN); dir[MAXPATHLEN - 1] = 0; while(*name != '\0') { while(*name != '/' && *name != '\0') { name += 1; } if(*name == '\0') break; *name = '\0'; if (dir[0] && !table->isdefined(dir)) { add_fake(fe->project(), dir, ":implicit-directory", table, add_to); } *name = '/'; while(*name == '/') { name += 1; } } } static void add_fake(ProjectDescriptor *project, const char* name, const char* tag, FileTable *table, FileEntryPtrArray* add_to) { Dstring *wp = new Dstring (name); Dstring *nametag = new Dstring (tag); /* leak */ DstringPtrArray tags; tags.append (nametag); const PrcsAttrs *attrs; ListMarker ent_marker; If_fail(attrs << project->intern_attrs (&tags, 0, name, false)) ASSERT (false, "unless this attr is bogus..."); FileEntry* fe = new FileEntry (wp, ent_marker, attrs, NULL, NULL, NULL, 0666); if (!execute_matched (fe)) { delete fe; return; } table->insert (*wp, fe); add_to->append (fe); } static int compare_fileent(const void* a, const void* b) { const FileEntry* fe1 = *((FileEntry**)a); const FileEntry* fe2 = *((FileEntry**)b); const char* path1 = fe1->working_path(); const char* path2 = fe2->working_path(); while (*path1 && *path2 && *path1 == *path2) { path1 += 1; path2 += 1; } if (!*path1 && *path2 == '/') return 2 * !option_preorder - 1; if (!*path2 && *path1 == '/') return 2 * option_preorder - 1; bool isdir1 = fe1->file_type() == Directory || strchr(path1, '/'); bool isdir2 = fe2->file_type() == Directory || strchr(path2, '/'); if (isdir1 == isdir2) return strcmp(path1, path2); return 2 * (option_preorder ? isdir1 : isdir2) - 1; } static PrVoidError do_execute_all(FileEntryPtrArray* all_files, ProjectDescriptor* project) { ArgList* args = new ArgList; for(int i = 0; i < cmd_diff_options_count; i += 1) { bool skip; Return_if_fail(skip << prepare_arg(all_files, cmd_diff_options_given[i], project, args)); if (skip) pthrow prcserror << "No command could be built" << dotendl; } if (option_long_format) { prcsinfo << "Execute command: "; foreach(arg_ptr, args, ArgList::ArrayIterator) prcsinfo << (*arg_ptr) << ' '; prcsinfo << prcsendl; } if (!option_report_actions) { int status; Return_if_fail(status << exec_command(args)); if (option_long_format) { if (WIFEXITED(status)) prcsinfo << "Command exited with value " << WEXITSTATUS(status) << prcsendl; else prcsinfo << "Command terminated with signal " << WTERMSIG(status) << prcsendl; } if (WIFSIGNALED(status)) any_failures = true; } foreach (arg_ptr, args, ArgList::ArrayIterator) delete *arg_ptr; delete args; return NoError; } static PrVoidError do_execute(FileEntryPtrArray* files, ProjectDescriptor* project) { foreach(fe_ptr, files, FileEntryPtrArray::ArrayIterator) { Return_if_fail(do_execute_file(*fe_ptr, project)); if (project->repository_entry()) project->repository_entry()->Rep_clear_compressed_cache(); } return NoError; } static PrVoidError do_execute_file(FileEntry* fe, ProjectDescriptor* project) { ArgList* args = new ArgList; current_file_descriptor = -1; current_co_stream = NULL; if (option_pipe && fe->file_type() != RealFile) { if (option_long_format) prcsinfo << "Skipping file " << squote(fe->working_path()) << dotendl; return NoError; } If_fail(open_exec_file(fe, project)) { if (option_long_format) prcsinfo << "Skipping file " << squote(fe->working_path()) << dotendl; return NoError; } FileEntryPtrArray fake_array; for(int i = 0; i < cmd_diff_options_count; i += 1) { bool skip; fake_array.truncate(0); fake_array.append(fe); Return_if_fail(skip << prepare_arg(&fake_array, cmd_diff_options_given[i], project, args)); if (skip) { if (option_long_format) prcsinfo << "Could not build command for file " << squote(fe->working_path()) << dotendl; goto outahere; } } if (option_long_format) { prcsinfo << "Execute command: "; foreach(arg_ptr, args, ArgList::ArrayIterator) prcsinfo << (*arg_ptr) << ' '; prcsinfo << prcsendl; } if (!option_report_actions) { int status; Return_if_fail(status << exec_command(args)); if (option_long_format) { if (WIFEXITED(status)) prcsinfo << "Command exited with value " << WEXITSTATUS(status) << prcsendl; else prcsinfo << "Command terminated with signal " << WTERMSIG(status) << prcsendl; } if (WIFSIGNALED(status)) any_failures = true; } Return_if_fail(close_exec_file(fe)); outahere: foreach (arg_ptr, args, ArgList::ArrayIterator) delete *arg_ptr; delete args; return NoError; } static void append_name (Dstring* n, FileEntry* fe, const char* arg) { const char* path = fe->working_path(); if (strncmp(arg, "}", 1) == 0) n->append (path); else if (strncmp(arg, "base}", 5) == 0) n->append (strip_leading_path (path)); else if (strncmp(arg, "dir}", 4) == 0) { const char* last_slash = strrchr (path, '/'); if (last_slash) n->append (path, last_slash - path); else n->append ("."); } else abort (); } static PrBoolError prepare_arg(FileEntryPtrArray* fe_ptrs, const char* arg, ProjectDescriptor* project, ArgList* args) { char c; Dstring this_arg; while ((c = *arg++) != 0) { if (c == '{') { if (strncmp(arg, "}", 1) == 0 || strncmp(arg, "base}", 5) == 0 || strncmp(arg, "dir}", 4) == 0) { if (fe_ptrs->length() == 0) return true; append_name (& this_arg, fe_ptrs->index (0), arg); for (int i = 1; i < fe_ptrs->length(); i += 1) { args->append(p_strdup(this_arg)); this_arg.truncate(0); append_name (& this_arg, fe_ptrs->index (i), arg); } arg += strchr (arg, '}') - arg + 1; } else if (strncmp(arg, "file}", 5) == 0) { const char* name; bool breakit = false; bool any = false; arg += 5; if (fe_ptrs->length() == 0) return true; for(int i = 0; i < fe_ptrs->length(); i += 1) { if (fe_ptrs->index(i)->file_type() != RealFile) continue; any = true; Return_if_fail(name << make_execute_temp_file(fe_ptrs->index(i), project)); if (breakit) { args->append(p_strdup(this_arg)); this_arg.truncate(0); } this_arg.append(name); breakit = true; } if (!any) /* skip it */ return true; } else if (strncmp(arg, "options}", 8) == 0) { arg += 8; if (fe_ptrs->length() == 0) return true; fe_ptrs->index(0)->file_attrs()->print_to_string(&this_arg, false); for(int i = 1; i < fe_ptrs->length(); i += 1) { args->append(p_strdup(this_arg)); this_arg.truncate(0); fe_ptrs->index(i)->file_attrs()->print_to_string(&this_arg, false); } } else { this_arg.append (c); } } else { this_arg.append(c); } } args->append(p_strdup(this_arg)); return false; } static PrIntError exec_command(ArgList* args) { int status, pid; if((pid = vfork()) < 0) pthrow prcserror << "Fork failed" << perror; if (pid == 0) { const char *const* argv = args->cast(); if (current_file_descriptor >= 0) dup2(current_file_descriptor, STDIN_FILENO); execvp(argv[0], (char * const*)argv); abort_child(argv[0]); } If_fail(status << Err_waitpid_nostart(pid)) pthrow prcserror << "Waitpid failed on pid " << pid << perror; return status; } static PrVoidError open_exec_file(FileEntry* fe, ProjectDescriptor* project) { if (!option_pipe) /* nothing */; else if (fe->file_type() == RealFile && !fe->descriptor_name ()) { /* special case for project file */ If_fail(current_file_descriptor << Err_open(temp_file_1, O_RDONLY)) pthrow prcserror << "Open failed on " << squote(temp_file_1) << perror; } else if (option_version_present) { Return_if_fail(fe->initialize_descriptor(project->repository_entry(), false, false)); FILE* cofile; Return_if_fail(cofile << VC_checkout_stream(fe->descriptor_version_number(), fe->full_descriptor_name())); current_co_stream = cofile; current_file_descriptor = fileno(cofile); } else { If_fail(current_file_descriptor << Err_open(fe->working_path(), O_RDONLY)) pthrow prcserror << "Open failed on " << squote(fe->working_path()) << perror; } return NoError; } static PrVoidError close_exec_file(FileEntry* fe) { if (!option_pipe) /* nothing */; else if (fe->file_type() == RealFile && !fe->descriptor_name ()) { /* special case for project file */ close(current_file_descriptor); } else if (option_version_present) Return_if_fail(VC_close_checkout_stream(current_co_stream, fe->descriptor_version_number(), fe->full_descriptor_name())); else close(current_file_descriptor); return NoError; } static PrConstCharPtrError make_execute_temp_file(FileEntry* fe, ProjectDescriptor* project) { const char* lastdot = strrchr(fe->working_path(), '.'); const char* newname = make_temp_file((lastdot && !strchr(lastdot, '/')) ? lastdot : ""); if (option_version_present) { if (fe->file_type() == RealFile && !fe->descriptor_name ()) { /* special case for project file */ return temp_file_1; } Return_if_fail(fe->initialize_descriptor(project->repository_entry(), false, false)); Return_if_fail(VC_checkout_file(newname, fe->descriptor_version_number(), fe->full_descriptor_name())); } else { Return_if_fail(fs_copy_filename(fe->working_path(), newname)); } return newname; }