/* -*-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: rebuild.cc 1.3.1.1.1.8.1.21.1.4.1.4.1.14.1.6 Sun, 09 May 2004 18:21:12 -0700 jmacd $
*/
extern "C" {
#include "md5.h"
}
#include "prcs.h"
#include "repository.h"
#include "misc.h"
#include "vc.h"
#include "setkeys.h"
#include "checkout.h"
#include "hash.h"
#include "system.h"
#include "memseg.h"
#include "rebuild.h"
#include "prcsdir.h"
#include "projdesc.h"
#include "fileent.h"
#include "syscmd.h"
#include "checkin.h"
#if __GNUG__ > 3 || (__GNUG__ == 3 && __GNUC_MINOR__ >= 2)
#include <ext/stdio_filebuf.h>
#endif
struct RebuildCallbackData {
RepEntry* rep_entry;
RcsFileTable* file_table;
};
static const char *old_data_file_headers[] =
{
"\158\233\071\375",
"\159\234\072\376",
NULL
};
static const char data_file_header[] = "\156\234\072\376";
static const int data_file_length = 4;
static const char project_summary_header[] = "@";
static const char rcs_file_summary_header[] = "#";
static const char version_count_header[] = "$";
const char prcs_data_file_name[] = "prcs_data";
static MemorySegment read_buf(false);
static PrRcsDeltaPtrError rebuild_repository_file(RepEntry* rep_entry, const char* name);
/**********************************************************************/
/* Miscellaneous Debris */
/**********************************************************************/
/* Simply compute the md5 checksum of a memory segment */
char* md5_buffer(const char* buffer, int buflen)
{
MD5_CTX context;
static unsigned char digest[16];
MD5Init(&context);
MD5Update(&context, (unsigned char*)buffer, buflen);
MD5Final(digest, &context);
return (char*)digest;
}
/* Determines whether a file is garbage by looking at its extension and
* for the "prcs_" prefix. */
static bool is_garbage_by_name(const char* pathname)
{
const char* name = strip_leading_path(pathname);
int len = strlen(name);
if(strcmp(name + len - 2, ",v") == 0)
return false;
else if(strncmp(name, "prcs_", 5) == 0)
return false;
else
return true;
}
/* Determines whether a file is used internally by PRCS, by looking at the
* prefix. */
static bool is_prcs_file(const char* pathname, const char* project_file_name)
{
const char* name = strip_leading_path(pathname);
const char* projname = strip_leading_path(project_file_name);
if(strncmp(name, "prcs_", 5) == 0)
return true;
else if(strcmp(name, projname) == 0)
return true;
else
return false;
}
/* Writes the data file header, consisting of the string "This file contains ..."
* and the project name */
static void write_data_file_header(ostream& os, const char* /*name*/)
{
os.write(data_file_header, data_file_length);
}
/* Compute the checksum of a file by checkin it out into memory,
* and calling md5_buffer. */
static PrVoidError compute_cksum(const char* versionfile, RcsVersionData* ver)
{
FILE* cofile;
Return_if_fail(cofile << VC_checkout_stream(ver->rcs_version(), versionfile));
// Blah
Dstring ds;
describe_versionfile (versionfile, ver, ds);
Return_if_fail(read_buf.map_file(ds.cast(), fileno(cofile)));
ver->length(read_buf.length());
Return_if_fail(VC_close_checkout_stream(cofile, ver->rcs_version(), versionfile));
ver->unkeyed_checksum(md5_buffer(read_buf.segment(), read_buf.length()));
return NoError;
}
/* Create a data file with no versions in it, for initializing a repository */
PrVoidError create_empty_data(const char* project, const char* filename0)
{
ofstream new_data;
new_data.open(filename0);
write_data_file_header(new_data, project);
if(new_data.bad()) {
pthrow prcserror << "Failed creating new repository data file "
<< squote(filename0) << perror;
}
return NoError;
}
/**********************************************************************/
/* Write Methods */
/**********************************************************************/
/* Write out the ProjectVersionData structure */
static void write_data_file_project_info(ostream& os, ProjectVersionData* ver)
{
os << ver->date() << '\0';
os << ver->author() << '\0';
os << ver->rcs_version() << '\0';
os << (int)ver->deleted() << '\0';
os << ver->prcs_major() << '\0'
<< ver->prcs_minor() << '\0';
os << ver->parent_count() << '\0';
for (int i = 0; i < ver->parent_count(); i += 1)
os << ver->parent_index(i) << '\0';
}
/* Write out the RcsVersionData structure */
static void write_data_file_rcs_info(ostream& os, RcsVersionData* ver)
{
os << ver->date() << '\0';
os << ver->length() << '\0';
os << ver->author() << '\0';
os << ver->rcs_version() << '\0';
os << ver->plus_lines () << '\0';
os << ver->minus_lines () << '\0';
os.write(ver->unkeyed_checksum(), 16);
}
/* Write out a ProjectVersionData for each version in the project */
static void write_data_file_project_summary(ostream& os,
ProjectVersionDataPtrArray *project_summary)
{
os << project_summary_header << project_summary->length() << '\0';
foreach(proj_ptr2, project_summary, ProjectVersionDataPtrArray::ArrayIterator)
write_data_file_project_info(os, *proj_ptr2);
}
static void write_data_file_rcs_file_summary(ostream&os, const char* name, RcsDelta* delta)
{
if (delta->count() == 0) return;
/* Here because reference_files isn't removing files it deletes
* from the table and this is easier. */
os << rcs_file_summary_header << name << '\0';
os << version_count_header << delta->count() << '\0';
foreach(ver, delta, RcsDelta::DeltaIterator)
write_data_file_rcs_info(os, *ver);
}
/**********************************************************************/
/* Deletion Methods */
/**********************************************************************/
/* Delete a single version from a single file. */
/* Whether RCS really deletes the version or not is ignored */
static PrVoidError delete_version(const char* file_name, const char* version)
{
if(!option_report_actions) {
bool deleted;
Return_if_fail(deleted << VC_delete_version(version, file_name));
if(!deleted)
return NoError;
}
if(option_long_format)
prcsoutput << (option_report_actions ? "Delete" : "Deleted")
<< " unreferenced version " << version
<< " from version file " << squote(file_name) << dotendl;
return NoError;
}
/* Delete all unreferenced versions from an RCS file */
static PrVoidError delete_unreferenced_versions_file(RepEntry* rep_entry,
const char* file_name,
RcsDelta* delta)
{
foreach(data_ptr, delta, RcsDelta::DeltaIterator) {
RcsVersionData* data = *data_ptr;
if(data->referenced() == RcsVersionData::Referenced)
continue;
rep_entry->Rep_log() << "Deleteing version " << data->rcs_version()
<< " from file " << file_name << dotendl;
delta->remove(data->rcs_version());
Return_if_fail(delete_version(file_name, data->rcs_version()));
}
return NoError;
}
static PrVoidError ignoring(ProjectVersionData* project_data)
{
prcswarning << "Project version " << project_data << "'s project file is invalid "
"and will be ignored" << dotendl;
bug ();
return NoError;
}
/* Mark all versions referenced by a single version of a project
* in the file table. */
static PrVoidError reference_versions(const char* project_file_name,
ProjectVersionData* project_data,
RcsFileTable* file_table)
{
FILE* cofile;
ProjectDescriptor* project;
Dstring name;
Return_if_fail(cofile << VC_checkout_stream(project_data->rcs_version(),
project_file_name));
name.sprintf("%s:%s", project_file_name, project_data->rcs_version());
If_fail(project << read_project_file(cmd_root_project_name,
strip_leading_path(name),
false,
cofile,
KeepNothing))
return ignoring(project_data);
Return_if_fail(VC_close_checkout_stream(cofile,
project_data->rcs_version(),
project_file_name));
foreach_fileent (fe_ptr, project) {
FileEntry *fe = *fe_ptr;
if (fe->file_type() == RealFile && !fe->descriptor_name())
return ignoring(project_data);
}
foreach_fileent(fe_ptr, project) {
RcsDelta **lookup, *delta;
RcsVersionData* version_data;
FileEntry *fe = *fe_ptr;
if(fe->file_type() != RealFile)
continue;
if((lookup = file_table->lookup(fe->descriptor_name())) == NULL) {
/*delete project;*/
pthrow prcserror << "Version file "
<< squote(fe->descriptor_name())
<< " referenced by project version "
<< project_data
<< " but not found, cannot clean repository" << dotendl;
}
delta = *lookup;
version_data = delta->lookup(fe->descriptor_version_number());
if(!version_data) {
/*delete project;*/
pthrow prcserror << "Version " << fe->descriptor_version_number()
<< " in repository file "
<< squote(fe->descriptor_name())
<< " referenced by project version "
<< project_data
<< " but not found, cannot clean repository"
<< dotendl;
}
version_data->reference(RcsVersionData::Referenced);
}
/* @@@ This is where the more-complete parent-version consistency check should go. */
delete project;
return NoError;
}
/* For each file, call delete_unreferenced_versions_file */
static PrVoidError
delete_unreferenced_versions(RepEntry* rep_entry)
{
ProjectVersionDataPtrArray* project_summary = rep_entry->project_summary();
RcsFileTable* file_table = rep_entry->rcs_file_summary();
foreach(project_data_ptr, project_summary,
ProjectVersionDataPtrArray::ArrayIterator) {
if((*project_data_ptr)->deleted())
continue;
Return_if_fail(reference_versions(rep_entry->Rep_name_of_version_file(),
*project_data_ptr,
file_table));
}
foreach(file_pair_ptr, file_table, RcsFileTable::HashIterator) {
Dstring name(rep_entry->Rep_name_in_entry((*file_pair_ptr).x()));
name.append (",v");
Return_if_fail(delete_unreferenced_versions_file(rep_entry, name, (*file_pair_ptr).y()));
if((*file_pair_ptr).y()->count() == 0) {
/* Takes care of deleteing the empty file */
rebuild_repository_file(rep_entry, name);
}
}
return NoError;
}
/**********************************************************************/
/* Gather Methods */
/**********************************************************************/
/* Read one RCS file's rlog, and return an RcsDelta table */
static PrRcsDeltaPtrError rebuild_repository_file(RepEntry* rep_entry,
const char* name)
{
RcsDelta* data;
Return_if_fail(data << VC_get_version_data(name));
if(data->count() == 0) {
if(option_long_format) {
prcsoutput << (option_report_actions ? "Delete" : "Deleting")
<< " empty version file " << squote(strip_leading_path(name))
<< " from repository" << dotendl;
}
rep_entry->Rep_log() << "Deleting empty file " << squote(name) << dotendl;
if(!option_report_actions) {
If_fail(Err_unlink(name))
pthrow prcserror << "Unlink failed on "
<< squote(name) << perror;
}
return (RcsDelta*)0;
}
/* @@@ Ideally, the cksum would get stored in the RCS log message so that this step
* could actually verify, not simply re-compute the cksum. */
foreach(rev_ptr, data, RcsDelta::DeltaIterator) {
Return_if_fail(compute_cksum(name, *rev_ptr));
}
return data;
}
static PrVoidError rebuild_file_table_file (const char* name,
const void* data)
{
RepEntry* rep_entry = ((RebuildCallbackData*)data)->rep_entry;
RcsFileTable* file_table = ((RebuildCallbackData*)data)->file_table;
if(fs_is_symlink(name) || is_garbage_by_name(name)) {
rep_entry->Rep_log() << "Deleting garbage " << squote(name) << dotendl;
/* Delete it! */
if(!option_report_actions) {
If_fail(Err_unlink(name)) {
prcswarning << "Failed removing garbage file "
<< squote(name)
<< " from repository" << dotendl;
return NoError;
}
}
if(option_long_format) {
prcsoutput << (option_report_actions ? "Delete" : "Deleting")
<< " garbage file " << squote(name)
<< " from repository" << dotendl;
}
} else if(!is_prcs_file(name, rep_entry->Rep_name_of_version_file())) {
/* Found an RCS file, run rebuild */
RcsDelta* delta_ptr;
Return_if_fail(delta_ptr << rebuild_repository_file(rep_entry, name));
if(!delta_ptr)
return NoError;
Dstring saved_name (name + strlen(rep_entry->Rep_entry_path()) + 1);
saved_name.truncate (saved_name.length() - 2);
file_table->insert(p_strdup(saved_name.cast()), delta_ptr);
}
return NoError;
}
static PrRcsFileTablePtrError rebuild_file_table(RepEntry* rep_ptr)
{
RcsFileTable* file_table = new RcsFileTable;
RebuildCallbackData data;
data.rep_entry = rep_ptr;
data.file_table = file_table;
Return_if_fail(directory_recurse (rep_ptr->Rep_entry_path(),
&data, rebuild_file_table_file));
return file_table;
}
/* This mainly checks for duplicate version numbers, a symptom of the
* bug reported by Keith Owens in versions up to 1.3.0. Also checks
* for invalid parent-version indices. */
static PrVoidError rebuild_check_project_summary (ProjectVersionDataPtrArray* project_summary)
{
ProjectVersionDataPtrArray copy (*project_summary);
/* Loop through a copy of the project summary, once for each major
* version, checking for duplicate minor version numbers. (Note:
* similarly ugly code to info_array() -- need to simplify this
* kind of iteration) */
for (int i = 0; i < copy.length (); i += 1) {
if (copy.index (i) != NULL) {
const char *major = copy.index(i)->prcs_major ();
int minor = 0;
for (int j = i; j < copy.length (); j += 1) {
ProjectVersionData *check = copy.index(j);
if (check == NULL) {
continue;
}
if (strcmp (major, check->prcs_major ()) == 0) {
int this_minor = check->prcs_minor_int ();
if (this_minor <= minor) {
pthrow prcserror << ((this_minor <= 0) ? "Invalid" : "Duplicate")
<< " project version "
<< check
<< " found, please contact "
<< maintainer
<< " for assistance with this problem"
<< dotendl;
}
while (++minor < this_minor) {
/* I made this a warning not an error simply because there is a
* missing version in PRCS's own repository (from Feb 20, 1996,
* and I don't remember why). If this is a genuine error it needs
* to be caught somewhere else. A check should be inserted in
* reference_versions when the project file is actually
* checked-out that the parent-indices match reality. */
prcswarning << "Missing any record of project version "
<< major << "." << minor
<< prcsendl;
}
ASSERT (check->version_index () == j, "set by VC_get_project_version_data");
for (int k = 0; k < check->parent_count (); k += 1) {
int p_i = check->parent_index (k);
if (p_i < 0 || p_i >= j) {
pthrow prcserror << "Invalid parent-version index found for project version "
<< check << ", please contact "
<< maintainer
<< " for assitance with this problem"
<< dotendl;
}
}
copy.index (j, (ProjectVersionData*)0);
}
}
}
}
return NoError;
}
/* This is the top-level call to rebuild a repository data file. */
static PrVoidError rebuild_repository(RepEntry *rep_ptr,
ProjectVersionDataPtrArray* project_summary,
RcsFileTable* file_table)
{
if(!project_summary) {
Return_if_fail(project_summary <<
VC_get_project_version_data(rep_ptr->Rep_name_of_version_file()));
}
Return_if_fail(rebuild_check_project_summary (project_summary));
if(!file_table) {
Return_if_fail(file_table << rebuild_file_table(rep_ptr));
}
/* Unlink here in case we're mmaping the file that's about to get truncated. */
If_fail(Err_unlink(rep_ptr->Rep_name_in_entry(prcs_data_file_name))) {
pthrow prcserror << "Unlink failed on old repository data file "
<< squote(rep_ptr->Rep_name_in_entry(prcs_data_file_name))
<< perror;
}
rep_ptr->project_summary(project_summary);
rep_ptr->rcs_file_summary(file_table);
If_fail(delete_unreferenced_versions(rep_ptr))
prcsinfo << "Continuing to build repository data file, "
"no cleaning was performed" << dotendl;
if (option_report_actions) {
prcsinfo << "Rebuild will be likely successful, the repository appears to be well" << dotendl;
return NoError;
}
ofstream new_data(rep_ptr->Rep_name_in_entry(prcs_data_file_name));
write_data_file_header(new_data, cmd_root_project_name);
write_data_file_project_summary(new_data, project_summary);
foreach(file_pair_ptr, file_table, RcsFileTable::HashIterator) {
write_data_file_rcs_file_summary(new_data,
(*file_pair_ptr).x(),
(*file_pair_ptr).y());
}
new_data.close();
if(new_data.bad()) {
pthrow prcserror << "Write failed to reconstructed project data file "
<< squote(rep_ptr->Rep_name_in_entry(prcs_data_file_name))
<< perror;
}
return NoError;
}
/* Run rebuild with an opened repository */
PrVoidError admin_rebuild_command_no_open(RepEntry* rep, bool valid_rep_data)
{
Umask mask (rep->Rep_get_umask());
Return_if_fail(rep->Rep_uncompress_all_files());
if(valid_rep_data)
Return_if_fail(rebuild_repository(rep,
rep->project_summary(),
rep->rcs_file_summary()));
else
Return_if_fail(rebuild_repository(rep, NULL, NULL));
Return_if_fail(rep->Rep_compress_all_files());
Return_if_fail(rep->Rep_make_default_tag ());
return NoError;
}
/* This is it. */
PrPrcsExitStatusError admin_rebuild_command()
{
RepEntry *rep;
Return_if_fail(rep << Rep_init_repository_entry(cmd_root_project_name,
true, false, false));
rep->Rep_log() << "Rebuilding project" << dotendl;
prcsinfo << "This command may run for a long time" << dotendl;
Return_if_fail(admin_rebuild_command_no_open(rep, false));
rep->Rep_log() << "Suceeded rebuilding project" << dotendl;
return ExitSuccess;
}
/* Mark a project version as deleted by changing its log information */
static PrVoidError mark_deleted(ProjectVersionData* project_data, RepEntry* rep_entry)
{
Dstring log;
project_data->deleted(true);
format_version_log(project_data, log);
Return_if_fail(VC_set_log(log.cast(),
project_data->rcs_version(),
rep_entry->Rep_name_of_version_file()));
return NoError;
}
/* This is it, too. */
PrPrcsExitStatusError delete_command()
{
RepEntry *rep_entry;
ProjectVersionData* project_data;
Return_if_fail(rep_entry << Rep_init_repository_entry(cmd_root_project_name,
true, false, true));
Return_if_fail(project_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 0
/*@@@ I have no idea about this*/
if(project_data->prcs_minor_int() == 0)
pthrow prcserror << "You may not delete minor version 0" << dotendl;
#endif
if(project_data->deleted())
pthrow prcserror << "That version is already deleted" << dotendl;
prcsquery << "Delete version "
<< project_data
<< report("")
<< defopt('y', "You know what you're doing, go ahead")
<< optfail('n')
<< query(". Are you sure");
Return_if_fail(prcsquery.result());
if(option_report_actions)
return ExitSuccess;
rep_entry->Rep_log() << "Deleting version " << project_data << dotendl;
Return_if_fail(mark_deleted(project_data, rep_entry));
Return_if_fail(admin_rebuild_command_no_open(rep_entry, true));
rep_entry->Rep_log() << "Suceeded deleting version" << dotendl;
return ExitSuccess;
}
/* READ */
/* Called when something goes wrong reading a data file */
PrVoidError RebuildFile::bad_data_file()
{
pthrow prcserror << "Invalid repository data file "
<< squote(source_name())
<< ". Please run " << squote("prcs admin rebuild")
<< " to rebuild the data file"
<< dotendl;
}
/* Read the header and assert that the projects match */
PrVoidError RebuildFile::read_header()
{
const char* s;
s = get_string(data_file_length);
if (!s) return bad_data_file();
for (int i = 0; old_data_file_headers[i]; i += 1) {
if (strncmp (s, old_data_file_headers[i], data_file_length) == 0) {
pthrow prcserror << "The repository data file is outdated, you must run "
<< squote ("prcs admin rebuild")
<< " to rebuild the data file" << dotendl;
}
}
if (strncmp (s, data_file_header, data_file_length) != 0)
return bad_data_file();
return NoError;
}
/* Read a single project version */
PrProjectVersionDataPtrError RebuildFile::read_project_version(int i)
{
ProjectVersionData* ver = new ProjectVersionData(i);
int get;
if((get = get_size()) < 0)
return bad_data_file();
ver->date(get);
ver->author(get_string());
ver->rcs_version(get_string());
if(!(ver->author() && ver->rcs_version()))
return bad_data_file();
if((get = get_size()) < 0)
return bad_data_file();
ver->deleted(get);
ver->prcs_major(get_string());
ver->prcs_minor(get_string());
if ((get = get_size()) < 0)
return bad_data_file();
for (int i = 0, c = get; i < c; i += 1) {
if ((get = get_size()) < 0)
return bad_data_file();
ver->new_parent(get);
}
return ver;
}
/* Read a single RCS version */
PrRcsVersionDataPtrError RebuildFile::read_rcs_version()
{
RcsVersionData* ver = new RcsVersionData;
const char* cksum;
const char *pls, *mls;
ver->date(get_size());
ver->length(get_size());
if(ver->date() < 0 || ver->length() < 0)
return bad_data_file();
ver->author(get_string());
ver->rcs_version(get_string());
if(!(ver->author() && ver->rcs_version()))
return bad_data_file();
if (! (pls = get_string ()) || ! (mls = get_string ()))
return bad_data_file ();
ver->set_plus_lines (pls);
ver->set_minus_lines (mls);
if(!(cksum = get_string(16)))
return bad_data_file();
ver->unkeyed_checksum(cksum);
return ver;
}
/* Read some number of project versions */
PrVoidError RebuildFile::read_project_summary(int& count)
{
int version_count;
ProjectVersionData* ver;
if((version_count = get_size()) < 0)
return bad_data_file();
for(int i = 0; i < version_count; i += 1) {
Return_if_fail(ver << read_project_version(count++));
project_data->append(ver);
}
return NoError;
}
/* Read some number of RCS file summaries */
PrVoidError RebuildFile::read_rcs_file_summary()
{
const char *read_file_name;
RcsDelta **lookup, *delta_array;
int num;
if((read_file_name = get_string()) == NULL)
return bad_data_file();
if((lookup = rcs_file_table->lookup(read_file_name)) == NULL) {
delta_array = new RcsDelta(NULL);
rcs_file_table->insert(read_file_name, delta_array);
} else {
delta_array = *lookup;
}
if(!get_string(version_count_header))
return bad_data_file();
if((num = get_size()) < 0)
return bad_data_file();
for(int i = 0; i < num; i += 1) {
RcsVersionData *ver;
Return_if_fail(ver << read_rcs_version());
delta_array->insert(ver->rcs_version(), ver);
ASSERT(delta_array->lookup(ver->rcs_version()) == ver, "hope so");
}
return NoError;
}
/* the following RebuildFile methods deal with reading or expecting strings
* and positive ints from a file. */
PrRebuildFilePtrError read_repository_data(const char* filename0, bool write)
{
RebuildFile* file = new RebuildFile;
Return_if_fail(file->init_from_file(filename0, write));
return file;
}
PrVoidError RebuildFile::init_from_file(const char* filename0, bool write)
{
filename.assign(filename0);
seg = new MemorySegment(false, write);
Return_if_fail(seg->map_file(filename0));
last = seg->segment() + seg->length();
offset = 0;
Return_if_fail(read_header());
rcs_file_table = new RcsFileTable;
project_data = new ProjectVersionDataPtrArray;
const char* s;
int version_count = 0;
while((s = get_string(strlen(project_summary_header))) != NULL) {
if(strncmp(s, project_summary_header, strlen(project_summary_header)) == 0)
Return_if_fail(read_project_summary(version_count));
else if(strncmp(s, rcs_file_summary_header, strlen(rcs_file_summary_header)) == 0)
Return_if_fail(read_rcs_file_summary());
else
return bad_data_file();
}
if(done())
return NoError;
else
return bad_data_file();
}
const char* RebuildFile::get_string()
{
const char* val = seg->segment() + offset;
if(seg->length() < offset)
return NULL;
offset += strlen(val) + 1;
return val;
}
const char* RebuildFile::get_string(int len)
{
if(last - (seg->segment() + offset) < len || seg->length() < offset)
return NULL;
const char* val = seg->segment() + offset;
offset += len;
return val;
}
bool RebuildFile::get_string(const char* expected, bool term)
{
int len = strlen(expected);
if(last - (seg->segment() + offset) < len || seg->length() < offset)
return false;
if(strncmp(expected, seg->segment() + offset, len) != 0)
return false;
offset += len;
if(term)
offset += 1;
return true;
}
int RebuildFile::get_size()
{
const char* p = seg->segment() + offset;
char c = 0;
int val = 0;
while(p < last && (c = *p++) != 0) {
if(c < '0' || c > '9')
return -1;
val *= 10;
val += (c - '0');
}
offset = p - seg->segment();
if(p <= last && c == 0)
return val;
return -1;
}
const char* RebuildFile::source_name() { return filename; }
bool RebuildFile::done() { return offset == seg->length(); }
void RebuildFile::init_stream()
{
if(!buf) {
#if __GNUG__ < 3
buf = new filebuf(seg->fd());
buf->seekoff(0,ios::end);
#elif __GNUG__ == 3 and __GNUC_MINOR__ < 2
buf = new filebuf(fdopen(dup(seg->fd()), "a+"), ios::out);
buf->pubseekoff(0, ios::end, ios::out);
#else
buf = new __gnu_cxx::stdio_filebuf<char> (seg->fd(), ios::out,
false /* close */, default_segment_size);
buf->pubseekoff(0, ios::end, ios::out);
#endif
os = new ostream(buf);
}
}
/* The following three methods append new data to the file */
void RebuildFile::add_project_data(ProjectVersionData* data)
{
init_stream();
(*os) << project_summary_header << 1 << '\0';
write_data_file_project_info(*os, data);
}
void RebuildFile::add_rcs_file_data(const char* new_file_name, RcsVersionData* data)
{
init_stream();
(*os) << rcs_file_summary_header << new_file_name << '\0';
(*os) << version_count_header << 1 << '\0';
write_data_file_rcs_info(*os, data);
}
PrVoidError RebuildFile::update_project_data()
{
os->flush();
if(os->fail())
goto error;
delete os;
delete buf;
os = NULL;
buf = NULL;
If_fail(seg->unmap())
goto error;
delete seg;
seg = NULL;
return NoError;
error:
pthrow prcserror << "Failed updating repository data file"
<< squote(source_name()) << perror;
}
RebuildFile::~RebuildFile()
{
delete seg;
foreach(data_ptr, project_data, ProjectVersionDataPtrArray::ArrayIterator)
delete (*data_ptr);
delete project_data;
foreach(file_ptr, rcs_file_table, RcsFileTable::HashIterator) {
delete (*file_ptr).y();
}
delete rcs_file_table;
}
ProjectVersionDataPtrArray* RebuildFile::get_project_summary() const
{
return project_data;
}
RcsFileTable* RebuildFile::get_rcs_file_summary() const
{
return rcs_file_table;
}
RebuildFile::RebuildFile()
:buf(NULL), os(NULL) { }
syntax highlighted by Code2HTML, v. 0.9.1