/* -*-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: package.cc 1.15.1.2.1.8.1.8.1.7.1.12 Wed, 06 Feb 2002 20:57:16 -0800 jmacd $ */ extern "C" { #include #include } #include "prcs.h" #include "prcsdir.h" #include "syscmd.h" #include "misc.h" #include "repository.h" #include "projdesc.h" #include "system.h" #include "vc.h" #include "lock.h" const char gzip_magic_1 = '\037'; const char gzip_magic_2 = '\213'; const char magic_number_1 = '\200'; /* tar file contains PROJECT/files */ const char magic_number_2 = '\352'; /* versions 1.0.x and earlier. */ const char new_magic_number_1 = '\201'; /* tar file contains files, allows easy renaming */ const char new_magic_number_2 = '\353'; /* versions 1.1.0 and later. */ const int header_length = 256; static PrVoidError package_project(const char* project_name, const char* package_file, bool obtain_lock, bool compress_it); static PrVoidError package_cleanup_handler(void* data, bool /*signaled*/) { ((AdvisoryLock*)data)->unlock(); return NoError; } /* This class makes it so that package, unpackage, etc, work on broken * repositories, in case of trouble. */ class PseudoRepEntry { public: PseudoRepEntry (const char* name); PrVoidError lock (); PrVoidError exists (); PrVoidError remove (bool replaced); Dstring rep_path; Dstring ent_path; private: Dstring _name; AdvisoryLock *_lock; }; PseudoRepEntry::PseudoRepEntry (const char* name) :_name(name), _lock (NULL) { const char* path; path << Rep_guess_repository_path(); /* Can't fail after call to Rep_init_repository() */ rep_path.assign (path); ent_path.assign (rep_path); ent_path.append ('/'); ent_path.append (name); } PrVoidError PseudoRepEntry::lock() { Dstring lock_path (rep_path); lock_path.append (prcs_lock_dir); lock_path.append ('/'); lock_path.append (_name); _lock = new AdvisoryLock (lock_path); Return_if_fail (obtain_lock(_lock, true)); install_cleanup_handler(package_cleanup_handler, _lock); return NoError; } PrVoidError PseudoRepEntry::exists() { if(!fs_is_directory(ent_path)) { pthrow prcserror << "Repository entry " << squote(_name) << " does not exist" << dotendl; } return NoError; } PrVoidError PseudoRepEntry::remove(bool being_replaced) { if(fs_is_directory(ent_path)) { if (being_replaced) { prcsquery << "Project " << squote(_name) << " already exists. "; } else { prcsquery << "Remove project " << squote(_name) << ". "; } prcsquery << force("Deleting") << report("Delete") << optfail('n') << defopt('y', "Delete project from repository") << query("Are you sure"); Return_if_fail(prcsquery.result()); if(option_report_actions) return NoError; If_fail(fs_nuke_file(ent_path)) { pthrow prcserror << "Failed removing old repository entry " << squote(ent_path) << dotendl; } } else if (! being_replaced) { prcswarning << "Project does not exist: " << squote(_name) << dotendl; } return NoError; } PrPrcsExitStatusError package_command() { Return_if_fail(package_project(cmd_root_project_name, cmd_filenames_given[0], true, option_package_compress)); return ExitSuccess; } static PrVoidError package_project(const char* project_name, const char* package_file, bool obtain_lock, bool compress) { ArgList *args; int outfd = STDOUT_FILENO; char buf[header_length]; #ifdef HAVE_FORK int pipefd[2]; #endif int pid = -1, status; PseudoRepEntry *prep_entry; Return_if_fail(Rep_init_repository()); prep_entry = new PseudoRepEntry (project_name); Return_if_fail (prep_entry->exists()); if (obtain_lock) Return_if_fail (prep_entry->lock()); Return_if_fail(args << tar_command.new_arg_list()); if(strcmp(package_file, "-") != 0) { If_fail(outfd << Err_open(package_file, O_WRONLY|O_CREAT|O_TRUNC, 0666)) pthrow prcserror << "Can't open " << squote(package_file) << " for writing" << perror; } args->append("cf"); args->append("-"); args->append("-C"); args->append(prep_entry->ent_path); args->append("."); Return_if_fail(tar_command.open(true, false)); memset(buf, 0, header_length); buf[0] = new_magic_number_1; buf[1] = new_magic_number_2; sprintf(buf + 2, "%s", project_name); if(compress) { #ifdef HAVE_FORK Return_if_fail(gzip_command.init()); if(pipe(pipefd) != 0) { pthrow prcserror << "Pipe failed, can't compress data" << perror; } else if((pid = fork()) < 0) { pthrow prcserror << "Fork failed, can't compress data" << perror; } else if (pid == 0) { dup2(pipefd[PIPE_READ_FD], STDIN_FILENO); dup2(outfd, STDOUT_FILENO); close(pipefd[PIPE_READ_FD]); close(pipefd[PIPE_WRITE_FD]); execl(gzip_command.path(), "-", "-f", NULL); abort_child(gzip_command.path()); } else { dup2(pipefd[PIPE_WRITE_FD], outfd); close(pipefd[PIPE_READ_FD]); close(pipefd[PIPE_WRITE_FD]); } #else pthrow prcserror << "Not compiled with fork(), cannot compress package data" << dotendl; #endif } If_fail(Err_write(outfd, buf, header_length)) pthrow prcserror << "Write failed writing package file" << perror; If_fail(write_fds(fileno(tar_command.standard_out()), outfd)) pthrow prcserror << "Write failed writing package file" << perror; If_fail(Err_close(outfd)) pthrow prcserror << "Write failed writing package file" << perror; Return_if_fail_if_ne(tar_command.close(), 0) { unlink(package_file); pthrow prcserror << "System command tar failed" << dotendl; } if(compress) { If_fail(status << Err_waitpid(pid)) pthrow prcserror << "Waitpid failed on pid " << pid << perror; if(!WIFEXITED(status) || WEXITSTATUS(status) != 0) pthrow prcserror << "System command gzip failed" << dotendl; } return NoError; } static PrVoidError invalid() { pthrow prcserror << squote(cmd_root_project_name) << " is not a valid package file" << dotendl; } #ifdef HAVE_FORK static PrVoidError feed_pipe(const char* header_buf, int from_fd, int to_fd) { If_fail(Err_write(to_fd, header_buf, header_length)) pthrow prcserror << "Write failed writing to gzip pipe" << perror; If_fail(write_fds(from_fd, to_fd)) pthrow prcserror << "Write failed writing to gzip pipe" << perror; return NoError; } #endif PrPrcsExitStatusError unpackage_command() { ArgList *args; #ifdef HAVE_FORK int zipoutpipefd[2], zipinpipefd[2]; #endif int fd, zippid = 0, writepid = 0; Dstring projname; char buf[header_length]; bool new_format = false; bool do_rename = false; bool uncompress; if(strcmp(cmd_root_project_name, "-") != 0) { If_fail(fd << Err_open(cmd_root_project_full_name, O_RDONLY)) pthrow prcserror << "Open failed on package file" << perror; } else { fd = STDIN_FILENO; } if (cmd_filenames_count > 1) pthrow prcserror << "Too many file-or-dir options" << dotendl; If_fail(Err_read_expect(fd, buf, header_length)) { if (errno) pthrow prcserror << "Read error on package file" << perror; else return invalid(); } if(buf[0] == magic_number_1 && buf[1] == magic_number_2) { uncompress = false; } else if(buf[0] == new_magic_number_1 && buf[1] == new_magic_number_2) { uncompress = false; new_format = true; } else if(buf[0] == gzip_magic_1 && buf[1] == gzip_magic_2) { uncompress = true; } else { return invalid(); } if(uncompress) { #ifdef HAVE_FORK Return_if_fail(gzip_command.init()); if(pipe(zipoutpipefd) != 0 || pipe(zipinpipefd) != 0) { pthrow prcserror << "Pipe failed, can't compress data" << perror; } else if((zippid = fork()) < 0) { pthrow prcserror << "Fork failed, can't compress data" << perror; } else if (zippid == 0) { dup2(zipoutpipefd[PIPE_WRITE_FD], STDOUT_FILENO); dup2(zipinpipefd[PIPE_READ_FD], STDIN_FILENO); close(zipoutpipefd[PIPE_READ_FD]); close(zipoutpipefd[PIPE_WRITE_FD]); close(zipinpipefd[PIPE_READ_FD]); close(zipinpipefd[PIPE_WRITE_FD]); execl(gzip_command.path(), "-", "-d","-f", NULL); abort_child(gzip_command.path()); } else if((writepid = fork()) < 0) { pthrow prcserror << "Fork failed, can't compress data" << perror; } else if(writepid == 0) { int exit_val = 0; close(zipinpipefd[PIPE_READ_FD]); If_fail(feed_pipe(buf, fd, zipinpipefd[PIPE_WRITE_FD])) exit_val = 1; close(zipinpipefd[PIPE_WRITE_FD]); _exit(exit_val); } else { dup2(zipoutpipefd[PIPE_READ_FD], fd); close(zipoutpipefd[PIPE_READ_FD]); close(zipoutpipefd[PIPE_WRITE_FD]); close(zipinpipefd[PIPE_READ_FD]); close(zipinpipefd[PIPE_WRITE_FD]); If_fail(Err_read_expect(fd, buf, header_length)) return invalid(); if (buf[0] == magic_number_1 || buf[1] == magic_number_2) { } else if (buf[0] == new_magic_number_1 || buf[1] == new_magic_number_2) { new_format = true; } else { return invalid(); } } #else pthrow prcserror << "Not compiled with fork(), cannot uncompress package data" << dotendl; #endif } if (cmd_filenames_count == 1) { if (!new_format) pthrow prcserror << "Cannot rename project in package file created by PRCS versions " "older than 1.1.0" << dotendl; if (strcmp (projname, buf + 2) != 0) do_rename = true; projname.assign (cmd_filenames_given[0]); prcsinfo << "Unpackage project " << squote(projname) << ", formerly named " << squote(buf + 2) << dotendl; } else { projname.assign (buf + 2); prcsinfo << "Unpackage project " << squote(projname) << dotendl; } if (VC_check_token_match(projname, "label") <= 0) pthrow prcserror << "Illegal project name " << squote(projname) << dotendl; Return_if_fail(Rep_init_repository()); PseudoRepEntry *prep_entry = new PseudoRepEntry (projname); Return_if_fail (prep_entry->lock()); Return_if_fail (prep_entry->remove(true)); Return_if_fail(args << tar_command.new_arg_list()); if(option_report_actions) return ExitSuccess; const char* change_to_dir = new_format ? prep_entry->ent_path : prep_entry->rep_path; if (new_format) { If_fail (Err_mkdir (change_to_dir, 0777)) pthrow prcserror << "Mkdir " << squote(change_to_dir) << " failed" << perror; } If_fail(change_cwd(change_to_dir)) /* Sun's tar doesn't accept -C for 'x' operations, pretty lame huh. */ pthrow prcserror << "Chdir " << squote(change_to_dir) << " failed" << perror; args->append("-xpf"); args->append("-"); Return_if_fail_if_ne(tar_command.open_filein(fd, false), 0) pthrow prcserror << "System command tar failed" << dotendl; int status; if(uncompress) { If_fail(status << Err_waitpid(writepid)) pthrow prcserror << "Waitpid failed on pid " << writepid << perror; if(!WIFEXITED(status) || WEXITSTATUS(status) != 0) pthrow prcserror << "Pipe reader exited with non-zero status" << dotendl; If_fail(status << Err_waitpid(zippid)) pthrow prcserror << "Waitpid failed on pid " << zippid << perror; if(WIFSIGNALED(status) && WTERMSIG(status) == SIGPIPE) pthrow prcserror << "Child gzip process was terminated with SIGPIPE" << dotendl; if(!WIFEXITED(status) || WEXITSTATUS(status) != 0) pthrow prcserror << "Child gzip process returns non-zero status" << dotendl; } if (do_rename) { Dstring old_name (buf+2); old_name.append (".prj,v"); projname.append (".prj,v"); If_fail (Err_rename (old_name, projname)) pthrow prcserror << "Rename " << squote(old_name) << " to " << squote(projname) << " failed" << perror; } return ExitSuccess; } PrVoidError save_package() { Dstring new_package; new_package.append(cmd_root_project_name); new_package.append(".pkg"); prcsquery << "Enter a package name" << definput(new_package) << string_query(""); const char* ans; Return_if_fail(ans << prcsquery.string_result()); prcsquery << "Packaging repository. " << force("Compressing") << report("Compress") << option('n', "Don't compress the package") << defopt('y', "Compress the package") << query("Compress"); char c; Return_if_fail(c << prcsquery.result()); Return_if_fail(package_project(cmd_root_project_name, ans, false, c == 'y')); return NoError; } PrPrcsExitStatusError admin_pdelete_command() { Return_if_fail(Rep_init_repository()); PseudoRepEntry *prep_entry = new PseudoRepEntry (cmd_root_project_name); Return_if_fail (prep_entry->lock()); Return_if_fail (prep_entry->remove (false)); return ExitSuccess; } PrPrcsExitStatusError admin_pinfo_command() { Return_if_fail(Rep_init_repository()); const char* path; path << Rep_guess_repository_path (); /* Can't fail after Rep_init_repository */ Dir dir (path); prcsinfo << "Repository contains:" << prcsendl; kill_prefix(prcsoutput); foreach (ent_ptr, dir, Dir::FullDirIterator) { const char* fent = *ent_ptr; if (! fs_is_directory (fent)) continue; const char* ent = strip_leading_path (fent); if (strcmp (ent, ".locks") == 0) continue; prcsoutput << ent << prcsendl; } if (!dir.OK()) pthrow prcserror << "Error reading repository directory " << squote (path) << perror; return ExitSuccess; } PrPrcsExitStatusError admin_prename_command() { Return_if_fail(Rep_init_repository()); PseudoRepEntry *old_entry = new PseudoRepEntry (cmd_root_project_name); PseudoRepEntry *new_entry = new PseudoRepEntry (cmd_filenames_given[0]); Return_if_fail (old_entry->exists()); Return_if_fail (old_entry->lock()); Return_if_fail (new_entry->remove(true)); /* locking here doesn't really work.... */ /* All it takes is two rename() calls */ Dstring old_prj_name (old_entry->ent_path); Dstring new_prj_name (old_entry->ent_path); old_prj_name.append ('/'); old_prj_name.append (cmd_root_project_name); old_prj_name.append (".prj,v"); new_prj_name.append ('/'); new_prj_name.append (cmd_filenames_given[0]); new_prj_name.append (".prj,v"); if (VC_check_token_match(cmd_filenames_given[0], "label") <= 0) pthrow prcserror << "Illegal project name " << squote(cmd_filenames_given[0]) << dotendl; prcsinfo << "Renaming project " << cmd_root_project_name << " to " << cmd_filenames_given[0] << dotendl; if (option_report_actions) return ExitSuccess; If_fail (Err_rename (old_prj_name, new_prj_name)) pthrow prcserror << "Rename " << squote(old_prj_name) << " to " << squote(new_prj_name) << " failed" << perror; If_fail (Err_rename (old_entry->ent_path, new_entry->ent_path)) pthrow prcserror << "Rename " << squote(old_entry->ent_path) << " to " << squote(new_entry->ent_path) << " failed" << perror; return ExitSuccess; }