c++-based patching, almost working

This commit is contained in:
David Rose 2009-09-28 15:55:37 +00:00
parent 83e6ecc1a4
commit 3771d613ad
10 changed files with 610 additions and 16 deletions

View File

@ -48,6 +48,7 @@
p3dObject.h p3dObject.I \
p3dOsxSplashWindow.h p3dOsxSplashWindow.I \
p3dPackage.h p3dPackage.I \
p3dPatchfileReader.h p3dPatchfileReader.I \
p3dPatchFinder.h p3dPatchFinder.I \
p3dPythonObject.h \
p3dReferenceCount.h p3dReferenceCount.I \
@ -82,6 +83,7 @@
p3dObject.cxx \
p3dOsxSplashWindow.cxx \
p3dPackage.cxx \
p3dPatchfileReader.cxx \
p3dPatchFinder.cxx \
p3dPythonObject.cxx \
p3dReferenceCount.cxx \

View File

@ -288,14 +288,37 @@ read_hash(const string &pathname) {
}
////////////////////////////////////////////////////////////////////
// Function: FileSpec::compare_hash
// Function: FileSpec::read_hash_stream
// Access: Public
// Description: Returns true if this hash sorts before the other
// hash, false otherwise.
// Description: Reads the hash from the next 16 bytes on the
// indicated istream, in the same unusual order observed
// by Panda's HashVal::read_stream() method.
////////////////////////////////////////////////////////////////////
bool FileSpec::
read_hash_stream(istream &in) {
for (int i = 0; i < hash_size; i += 4) {
unsigned int a = in.get();
unsigned int b = in.get();
unsigned int c = in.get();
unsigned int d = in.get();
_hash[i + 0] = d;
_hash[i + 1] = c;
_hash[i + 2] = b;
_hash[i + 3] = a;
}
return !in.fail();
}
////////////////////////////////////////////////////////////////////
// Function: FileSpec::compare_hash
// Access: Public
// Description: Returns < 0 if this hash sorts before the other
// hash, > 0 if it sorts after, 0 if they are the same.
////////////////////////////////////////////////////////////////////
int FileSpec::
compare_hash(const FileSpec &other) const {
return memcmp(_hash, other._hash, hash_size) < 0;
return memcmp(_hash, other._hash, hash_size);
}
////////////////////////////////////////////////////////////////////

View File

@ -46,7 +46,8 @@ public:
bool check_hash(const string &pathname) const;
bool read_hash(const string &pathname);
bool compare_hash(const FileSpec &other) const;
bool read_hash_stream(istream &in);
int compare_hash(const FileSpec &other) const;
void write(ostream &out) const;
void output_hash(ostream &out) const;

View File

@ -726,8 +726,8 @@ build_install_plans(TiXmlDocument *doc) {
return;
}
_install_plans.push_back(InstallPlan());
InstallPlan &plan = _install_plans.back();
_install_plans.push_front(InstallPlan());
InstallPlan &plan = _install_plans.front();
bool needs_redownload = false;
@ -745,7 +745,8 @@ build_install_plans(TiXmlDocument *doc) {
// Uncompress the compressed archive to generate the uncompressed
// archive.
step = new InstallStepUncompressFile(this, _compressed_archive, _uncompressed_archive);
step = new InstallStepUncompressFile
(this, _compressed_archive, _uncompressed_archive, true);
plan.push_back(step);
}
@ -775,9 +776,40 @@ build_install_plans(TiXmlDocument *doc) {
P3DPatchFinder patch_finder;
P3DPatchFinder::Patchfiles chain;
if (patch_finder.get_patch_chain_to_current(chain, doc, *on_disk_ptr)) {
cerr << "got patch chain of length " << chain.size() << "\n";
} else {
cerr << "No patch chain possible.\n";
nout << "Got patch chain of length " << chain.size() << "\n";
// OK, we can create a plan to download and apply the patches.
_install_plans.push_front(InstallPlan());
InstallPlan &plan = _install_plans.front();
P3DPatchFinder::Patchfiles::iterator pi;
for (pi = chain.begin(); pi != chain.end(); ++pi) {
P3DPatchFinder::Patchfile *patchfile = (*pi);
// Download the patchfile
step = new InstallStepDownloadFile(this, patchfile->_file);
plan.push_back(step);
// Uncompress it
FileSpec new_file = patchfile->_file;
string new_filename = new_file.get_filename();
size_t dot = new_filename.rfind('.');
assert(new_filename.substr(dot) == ".pz");
new_filename = new_filename.substr(0, dot);
new_file.set_filename(new_filename);
step = new InstallStepUncompressFile
(this, patchfile->_file, new_file, false);
plan.push_back(step);
// And apply it
FileSpec source_file = patchfile->_source_file;
FileSpec target_file = patchfile->_target_file;
source_file.set_filename(_uncompressed_archive.get_filename());
target_file.set_filename(_uncompressed_archive.get_filename());
step = new InstallStepApplyPatch
(this, new_file, source_file, target_file);
plan.push_back(step);
}
}
}
}
@ -818,6 +850,9 @@ follow_install_plans(bool download_finished) {
_current_step_effort = step->get_effort();
InstallToken token = step->do_step(download_finished);
nout << step << ":";
step->output(nout);
nout << " returned " << token << "\n";
switch (token) {
case IT_step_failed:
// This plan has failed.
@ -845,6 +880,7 @@ follow_install_plans(bool download_finished) {
}
// That plan failed. Go on to the next plan.
nout << "Plan failed.\n";
_install_plans.pop_front();
}
@ -1252,6 +1288,17 @@ do_step(bool download_finished) {
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::InstallStepDownloadFile::output
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
void P3DPackage::InstallStepDownloadFile::
output(ostream &out) {
out << "InstallStepDownloadFile(" << _package->get_package_name()
<< ", " << _file.get_filename() << ")";
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::InstallStepUncompressFile::Constructor
@ -1260,10 +1307,11 @@ do_step(bool download_finished) {
////////////////////////////////////////////////////////////////////
P3DPackage::InstallStepUncompressFile::
InstallStepUncompressFile(P3DPackage *package, const FileSpec &source,
const FileSpec &target) :
const FileSpec &target, bool verify_target) :
InstallStep(package, target.get_size(), _uncompress_factor),
_source(source),
_target(target)
_target(target),
_verify_target(verify_target)
{
}
@ -1373,7 +1421,7 @@ do_step(bool download_finished) {
source.close();
target.close();
if (!_target.full_verify(_package->get_package_dir())) {
if (_verify_target && !_target.full_verify(_package->get_package_dir())) {
nout << "after uncompressing " << target_pathname
<< ", failed hash check\n";
return IT_step_failed;
@ -1392,6 +1440,17 @@ do_step(bool download_finished) {
return IT_step_complete;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::InstallStepUncompressFile::output
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
void P3DPackage::InstallStepUncompressFile::
output(ostream &out) {
out << "InstallStepUncompressFile(" << _package->get_package_name()
<< ", " << _source.get_filename() << ", " << _target.get_filename()
<< ", " << _verify_target << ")";
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::InstallStepUnpackArchive::Constructor
@ -1426,3 +1485,65 @@ do_step(bool download_finished) {
return IT_step_complete;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::InstallStepUnpackArchive::output
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
void P3DPackage::InstallStepUnpackArchive::
output(ostream &out) {
out << "InstallStepUnpackArchive(" << _package->get_package_name() << ")";
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::InstallStepApplyPatch::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
P3DPackage::InstallStepApplyPatch::
InstallStepApplyPatch(P3DPackage *package, const FileSpec &patchfile,
const FileSpec &source, const FileSpec &target) :
InstallStep(package, target.get_size(), _patch_factor),
_reader(package->get_package_dir(), patchfile, source, target)
{
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::InstallStepApplyPatch::do_step
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
P3DPackage::InstallToken P3DPackage::InstallStepApplyPatch::
do_step(bool download_finished) {
// Open the patchfile
if (!_reader.open_read()) {
_reader.close();
return IT_step_failed;
}
// Apply the patch.
while (_reader.step()) {
_bytes_done = _reader.get_bytes_written();
report_step_progress();
}
// Close and verify.
_reader.close();
if (!_reader.get_success()) {
nout << "Patching failed\n";
return IT_step_failed;
}
return IT_step_complete;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::InstallStepApplyPatch::output
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
void P3DPackage::InstallStepApplyPatch::
output(ostream &out) {
out << "InstallStepApplyPatch(" << _package->get_package_name() << ")";
}

View File

@ -17,6 +17,7 @@
#include "p3d_plugin_common.h"
#include "p3dFileDownload.h"
#include "p3dPatchfileReader.h"
#include "fileSpec.h"
#include "get_tinyxml.h"
#include <deque>
@ -119,6 +120,7 @@ private:
virtual ~InstallStep();
virtual InstallToken do_step(bool download_finished) = 0;
virtual void output(ostream &out) = 0;
inline double get_effort() const;
inline double get_progress() const;
@ -136,6 +138,7 @@ private:
virtual ~InstallStepDownloadFile();
virtual InstallToken do_step(bool download_finished);
virtual void output(ostream &out);
string _urlbase;
string _pathname;
@ -146,17 +149,32 @@ private:
class InstallStepUncompressFile : public InstallStep {
public:
InstallStepUncompressFile(P3DPackage *package, const FileSpec &source,
const FileSpec &target);
const FileSpec &target, bool verify_target);
virtual InstallToken do_step(bool download_finished);
virtual void output(ostream &out);
FileSpec _source;
FileSpec _target;
bool _verify_target;
};
class InstallStepUnpackArchive : public InstallStep {
public:
InstallStepUnpackArchive(P3DPackage *package, size_t unpack_size);
virtual InstallToken do_step(bool download_finished);
virtual void output(ostream &out);
};
class InstallStepApplyPatch : public InstallStep {
public:
InstallStepApplyPatch(P3DPackage *package,
const FileSpec &patchfile,
const FileSpec &source,
const FileSpec &target);
virtual InstallToken do_step(bool download_finished);
virtual void output(ostream &out);
P3DPatchfileReader _reader;
};
typedef deque<InstallStep *> InstallPlan;

View File

@ -108,7 +108,7 @@ operator < (const PackageVersionKey &other) const {
if (_host_url != other._host_url) {
return _host_url < other._host_url;
}
return _file.compare_hash(other._file);
return _file.compare_hash(other._file) < 0;
}
////////////////////////////////////////////////////////////////////

View File

@ -0,0 +1,88 @@
// Filename: p3dPatchfileReader.I
// Created by: drose (28Sep09)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University. All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license. You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
// Function: P3DPatchfileReader::is_open
// Access: Public
// Description: Returns true if the patchfile is currently open,
// false otherwise.
////////////////////////////////////////////////////////////////////
inline bool P3DPatchfileReader::
is_open() const {
return _is_open;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPatchfileReader::get_bytes_written
// Access: Public
// Description: Returns the number of bytes written to the output
// file so far during the patching process.
////////////////////////////////////////////////////////////////////
inline size_t P3DPatchfileReader::
get_bytes_written() const {
return _bytes_written;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPatchfileReader::get_success
// Access: Public
// Description: Returns true if the patching process has completed
// successfully, false if it has failed or has not yet
// completed.
////////////////////////////////////////////////////////////////////
inline bool P3DPatchfileReader::
get_success() const {
return _success;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPatchfileReader::read_uint16
// Access: Private
// Description: Extracts an unsigned short from the patchfile.
////////////////////////////////////////////////////////////////////
inline unsigned int P3DPatchfileReader::
read_uint16() {
unsigned int a = _patch_in.get();
unsigned int b = _patch_in.get();
return (b << 8) | a;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPatchfileReader::read_uint32
// Access: Private
// Description: Extracts an unsigned long from the patchfile.
////////////////////////////////////////////////////////////////////
inline unsigned int P3DPatchfileReader::
read_uint32() {
unsigned int a = _patch_in.get();
unsigned int b = _patch_in.get();
unsigned int c = _patch_in.get();
unsigned int d = _patch_in.get();
return (d << 24) | (c << 16) | (b << 8) | a;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPatchfileReader::read_int32
// Access: Private
// Description: Extracts a signed long from the patchfile.
////////////////////////////////////////////////////////////////////
inline int P3DPatchfileReader::
read_int32() {
unsigned int a = _patch_in.get();
unsigned int b = _patch_in.get();
unsigned int c = _patch_in.get();
int d = _patch_in.get();
return (d << 24) | (c << 16) | (b << 8) | a;
}

View File

@ -0,0 +1,264 @@
// Filename: p3dPatchfileReader.cxx
// Created by: drose (28Sep09)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University. All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license. You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////
#include "p3dPatchfileReader.h"
////////////////////////////////////////////////////////////////////
// Function: P3DPatchfileReader::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
P3DPatchfileReader::
P3DPatchfileReader(const string &package_dir, const FileSpec &patchfile,
const FileSpec &source, const FileSpec &target) :
_package_dir(package_dir),
_patchfile(patchfile),
_source(source),
_target(target)
{
_is_open = false;
_bytes_written = 0;
_success = false;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPatchfileReader::Destructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
P3DPatchfileReader::
~P3DPatchfileReader() {
close();
}
////////////////////////////////////////////////////////////////////
// Function: P3DPatchfileReader::open_read
// Access: Public
// Description: Opens the named patchfile for reading, reads the
// header, and validates the inputs. Returns true on
// success, false otherwise. If this returns false, you
// should immediately call close(), or let this object
// destruct.
////////////////////////////////////////////////////////////////////
bool P3DPatchfileReader::
open_read() {
close();
// Synthesize an output filename, in case the source and the target
// refer to the same filename.
_output_pathname = _target.get_pathname(_package_dir);
_output_pathname += ".tmp";
string patch_pathname = _patchfile.get_pathname(_package_dir);
_patch_in.clear();
_patch_in.open(patch_pathname.c_str(), ios::in | ios::binary);
string source_pathname = _source.get_pathname(_package_dir);
_source_in.clear();
_source_in.open(source_pathname.c_str(), ios::in | ios::binary);
mkfile_complete(_output_pathname, nout);
_target_out.clear();
_target_out.open(_output_pathname.c_str(), ios::out | ios::binary);
_is_open = true;
// If any of those failed to open, we fail.
if (_patch_in.fail() || _source_in.fail() || _target_out.fail()) {
nout << "Couldn't open patchfile source, input, and/or target.\n";
return false;
}
// Read the patchfile header and validate it against the hashes we
// were given.
unsigned int magic_number = read_uint32();
if (magic_number != 0xfeebfaac) {
nout << "Not a valid patchfile: " << patch_pathname << "\n";
return false;
}
unsigned int version = read_uint16();
if (version != 2) {
// This code only knows about patchfile version 2. If the
// patchfile code is updated, we have to update this code
// accordingly.
nout << "Unsupported patchfile version: " << version << "\n";
return false;
}
size_t source_length = read_uint32();
if (source_length != _source.get_size()) {
nout << "Patchfile " << patch_pathname
<< " doesn't match source size.\n";
return false;
}
FileSpec validate;
validate.read_hash_stream(_patch_in);
if (_source.compare_hash(validate) != 0) {
nout << "Patchfile " << patch_pathname
<< " doesn't match source hash.\n";
return false;
}
_target_length = read_uint32();
if (_target_length != _target.get_size()) {
nout << "Patchfile " << patch_pathname
<< " doesn't match target size.\n";
return false;
}
validate.read_hash_stream(_patch_in);
if (_target.compare_hash(validate) != 0) {
nout << "Patchfile " << patch_pathname
<< " doesn't match target hash.\n";
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPatchfileReader::step
// Access: Public
// Description: Performs one incremental step of the patching
// operation. Returns true if the operation should
// continue and step() should be called again, false if
// the patching is done (either successfully, or due to
// failure).
////////////////////////////////////////////////////////////////////
bool P3DPatchfileReader::
step() {
assert(_is_open);
size_t add_length = read_uint16();
if (add_length != 0) {
// Add a number of bytes from the patchfile.
if (!copy_bytes(_patch_in, add_length)) {
nout << "Truncated patchfile.\n";
return false;
}
}
size_t copy_length = read_uint16();
if (copy_length != 0) {
// Copy a number of bytes from the original source.
ssize_t offset = read_int32();
_source_in.seekg(offset, ios::cur);
if (!copy_bytes(_source_in, copy_length)) {
nout << "Garbage in patchfile.\n";
return false;
}
}
assert(_bytes_written <= _target_length);
// When both counts reach 0, the patchfile is done.
if (add_length != 0 || copy_length != 0) {
// So, we've still got more to do.
return true;
}
if (_bytes_written != _target_length) {
nout << "Patchfile wrote truncated file.\n";
return false;
}
// Set the _success flag true, so close() will move the finished
// file into place.
_success = true;
close();
// Now validate the hash.
if (!_target.full_verify(_package_dir)) {
nout << "After patching, " << _target.get_filename()
<< " is still incorrect.\n";
_success = false;
return false;
}
// Successfully patched! Return false to indicate completion.
return false;
}
////////////////////////////////////////////////////////////////////
// Function: P3DPatchfileReader::close
// Access: Public
// Description: Closes the previously-opened files, and moves the
// output file into place.
////////////////////////////////////////////////////////////////////
void P3DPatchfileReader::
close() {
if (!_is_open) {
return;
}
_patch_in.close();
_source_in.close();
_target_out.close();
if (_success) {
// Move the output file onto the target file.
string target_pathname = _target.get_pathname(_package_dir);
#ifdef _WIN32
// Windows can't delete a file if it's read-only.
chmod(target_pathname.c_str(), 0644);
#endif
unlink(target_pathname.c_str());
rename(_output_pathname.c_str(), target_pathname.c_str());
} else {
// Failure; remove the output file.
#ifdef _WIN32
chmod(_output_pathname.c_str(), 0644);
#endif
unlink(_output_pathname.c_str());
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DPatchfileReader::copy_bytes
// Access: Private
// Description: Copies the indicated number of bytes from the
// indicated stream onto the output stream. Returns
// true on success, false if the input stream didn't
// have enough bytes.
////////////////////////////////////////////////////////////////////
bool P3DPatchfileReader::
copy_bytes(istream &in, size_t copy_byte_count) {
static const size_t buffer_size = 8192;
char buffer[buffer_size];
size_t read_size = min(copy_byte_count, buffer_size);
in.read(buffer, read_size);
size_t count = in.gcount();
while (count != 0) {
_target_out.write(buffer, count);
_bytes_written += count;
if (_bytes_written > _target_length) {
nout << "Runaway patchfile.\n";
return false;
}
if (count != read_size) {
return false;
}
copy_byte_count -= count;
count = 0;
if (copy_byte_count != 0) {
read_size = min(copy_byte_count, buffer_size);
in.read(buffer, read_size);
count = in.gcount();
}
}
return (copy_byte_count == 0);
}

View File

@ -0,0 +1,76 @@
// Filename: p3dPatchfileReader.h
// Created by: drose (27Sep09)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University. All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license. You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////
#ifndef P3DPATCHFILEREADER_H
#define P3DPATCHFILEREADER_H
#include "p3d_plugin_common.h"
#include "p3dInstanceManager.h" // for openssl
#include "fileSpec.h"
////////////////////////////////////////////////////////////////////
// Class : P3DPatchfileReader
// Description : A read-only implementation of Panda's patchfile
// format, for applying patches.
//
// This object assumes that the sourcefile has been
// already validated against its md5 hash, and does not
// validate it again. It *does* verify that the md5
// hash in source and target match those read in the
// patchfile header; and it verifies the md5 hash on the
// target after completion.
////////////////////////////////////////////////////////////////////
class P3DPatchfileReader {
public:
P3DPatchfileReader(const string &package_dir,
const FileSpec &patchfile,
const FileSpec &source,
const FileSpec &target);
~P3DPatchfileReader();
bool open_read();
inline bool is_open() const;
bool step();
inline size_t get_bytes_written() const;
inline bool get_success() const;
void close();
private:
bool copy_bytes(istream &in, size_t copy_byte_count);
inline unsigned int read_uint16();
inline unsigned int read_uint32();
inline int read_int32();
private:
string _package_dir;
FileSpec _patchfile;
FileSpec _source;
FileSpec _target;
string _output_pathname;
ifstream _patch_in;
ifstream _source_in;
ofstream _target_out;
bool _is_open;
size_t _target_length;
size_t _bytes_written;
bool _success;
};
#include "p3dPatchfileReader.I"
#endif

View File

@ -17,6 +17,7 @@
#include "p3dObject.cxx"
#include "p3dOsxSplashWindow.cxx"
#include "p3dPackage.cxx"
#include "p3dPatchfileReader.cxx"
#include "p3dPatchFinder.cxx"
#include "p3dPythonObject.cxx"
#include "p3dReferenceCount.cxx"