| Index: src/platform/update_engine/delta_diff_generator.cc
|
| diff --git a/src/platform/update_engine/delta_diff_generator.cc b/src/platform/update_engine/delta_diff_generator.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..6f352c712a12243c3e397ed4f00779eeeb371292
|
| --- /dev/null
|
| +++ b/src/platform/update_engine/delta_diff_generator.cc
|
| @@ -0,0 +1,423 @@
|
| +// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "update_engine/delta_diff_generator.h"
|
| +#include <dirent.h>
|
| +#include <endian.h>
|
| +#include <errno.h>
|
| +#include <stdio.h>
|
| +#include <unistd.h>
|
| +#include <algorithm>
|
| +#include <vector>
|
| +#include <tr1/memory>
|
| +#include <zlib.h>
|
| +#include "chromeos/obsolete_logging.h"
|
| +#include "base/scoped_ptr.h"
|
| +#include "update_engine/delta_diff_parser.h"
|
| +#include "update_engine/gzip.h"
|
| +#include "update_engine/subprocess.h"
|
| +#include "update_engine/utils.h"
|
| +
|
| +using std::vector;
|
| +using std::tr1::shared_ptr;
|
| +using chromeos_update_engine::DeltaArchiveManifest;
|
| +
|
| +namespace chromeos_update_engine {
|
| +
|
| +namespace {
|
| +// These structs and methods are helpers for EncodeDataToDeltaFile()
|
| +
|
| +// Before moving the data into a proto buffer, the data is stored in
|
| +// memory in these Node and Child structures.
|
| +
|
| +// Each Node struct represents a file on disk (which can be regular file,
|
| +// directory, fifo, socket, symlink, etc). Nodes that contain children
|
| +// (just directories) will have a vector of Child objects. Each child
|
| +// object has a filename and a pointer to the associated Node. Thus,
|
| +// filenames for files are stored with their parents, not as part of
|
| +// the file itself.
|
| +
|
| +// These structures are easier to work with than the protobuf format
|
| +// when adding files. When generating a delta file, we add an entry
|
| +// for each file to a root Node object. Then, we sort each Node's
|
| +// children vector so the children are stored alphabetically. Then,
|
| +// we assign an index value to the idx field of each Node by a preorder
|
| +// tree traversal. The index value assigned to a Node is the index it
|
| +// will have in the DeltaArchiveManifest protobuf.
|
| +// Finally, we add each Node to a DeltaArchiveManifest protobuf.
|
| +
|
| +struct Node;
|
| +
|
| +struct Child {
|
| + Child(const string& the_name,
|
| + Node* the_node)
|
| + : name(the_name),
|
| + node(the_node) {}
|
| + string name;
|
| + // Use shared_ptr here rather than scoped_ptr b/c this struct will be copied
|
| + // in stl containers
|
| + scoped_ptr<Node> node;
|
| +};
|
| +
|
| +// For the C++ sort() function.
|
| +struct ChildLessThan {
|
| + bool operator()(const shared_ptr<Child>& a, const shared_ptr<Child>& b) {
|
| + return a->name < b->name;
|
| + }
|
| +};
|
| +
|
| +struct Node {
|
| + Node()
|
| + : mode(0),
|
| + uid(0),
|
| + gid(0),
|
| + compressed(false),
|
| + offset(-1),
|
| + length(0),
|
| + idx(0) {}
|
| +
|
| + mode_t mode;
|
| + uid_t uid;
|
| + gid_t gid;
|
| +
|
| + // data
|
| + bool compressed;
|
| + int offset; // -1 means no data
|
| + int length;
|
| +
|
| + vector<shared_ptr<Child> > children;
|
| + int idx;
|
| +};
|
| +
|
| +// This function sets *node's variables to match what's at path.
|
| +// This includes calling this function recursively on all children. Children
|
| +// not on the same device as the original node will not be considered.
|
| +// Returns true on success.
|
| +bool UpdateNodeFromPath(const string& path, Node* node) {
|
| + // Set metadata
|
| + struct stat stbuf;
|
| + TEST_AND_RETURN_FALSE_ERRNO(lstat(path.c_str(), &stbuf) == 0);
|
| + const dev_t dev = stbuf.st_dev;
|
| + node->mode = stbuf.st_mode;
|
| + node->uid = stbuf.st_uid;
|
| + node->gid = stbuf.st_gid;
|
| + if (!S_ISDIR(node->mode)) {
|
| + return true;
|
| + }
|
| +
|
| + DIR* dir = opendir(path.c_str());
|
| + TEST_AND_RETURN_FALSE(dir);
|
| +
|
| + struct dirent entry;
|
| + struct dirent* dir_entry;
|
| +
|
| + for (;;) {
|
| + TEST_AND_RETURN_FALSE_ERRNO(readdir_r(dir, &entry, &dir_entry) == 0);
|
| + if (!dir_entry) {
|
| + // done
|
| + break;
|
| + }
|
| + if (!strcmp(".", dir_entry->d_name))
|
| + continue;
|
| + if (!strcmp("..", dir_entry->d_name))
|
| + continue;
|
| +
|
| + string child_path = path + "/" + dir_entry->d_name;
|
| + struct stat child_stbuf;
|
| + TEST_AND_RETURN_FALSE_ERRNO(lstat(child_path.c_str(), &child_stbuf) == 0);
|
| + // make sure it's on the same dev
|
| + if (child_stbuf.st_dev != dev)
|
| + continue;
|
| + shared_ptr<Child> child(new Child(dir_entry->d_name, new Node));
|
| + node->children.push_back(child);
|
| + TEST_AND_RETURN_FALSE(UpdateNodeFromPath(path + "/" + child->name,
|
| + child->node.get()));
|
| + }
|
| + TEST_AND_RETURN_FALSE_ERRNO(closedir(dir) == 0);
|
| + // Done with all subdirs. sort children.
|
| + sort(node->children.begin(), node->children.end(), ChildLessThan());
|
| + return true;
|
| +}
|
| +
|
| +// We go through n setting the index value of each Node to
|
| +// *next_index_value, then increment next_index_value.
|
| +// We then recursively assign index values to children.
|
| +// The first caller should call this with *next_index_value == 0 and
|
| +// the root Node, thus setting the root Node's index to 0.
|
| +void PopulateChildIndexes(Node* n, int* next_index_value) {
|
| + n->idx = (*next_index_value)++;
|
| + for (unsigned int i = 0; i < n->children.size(); i++) {
|
| + PopulateChildIndexes(n->children[i]->node.get(), next_index_value);
|
| + }
|
| +}
|
| +
|
| +// This converts a Node tree rooted at n into a DeltaArchiveManifest.
|
| +void NodeToDeltaArchiveManifest(Node* n, DeltaArchiveManifest* archive) {
|
| + DeltaArchiveManifest_File *f = archive->add_files();
|
| + f->set_mode(n->mode);
|
| + f->set_uid(n->uid);
|
| + f->set_gid(n->gid);
|
| + if (!S_ISDIR(n->mode))
|
| + return;
|
| + for (unsigned int i = 0; i < n->children.size(); i++) {
|
| + DeltaArchiveManifest_File_Child* child = f->add_children();
|
| + child->set_name(n->children[i]->name);
|
| + child->set_index(n->children[i]->node->idx);
|
| + }
|
| + for (unsigned int i = 0; i < n->children.size(); i++) {
|
| + NodeToDeltaArchiveManifest(n->children[i]->node.get(), archive);
|
| + }
|
| +}
|
| +
|
| +} // namespace {}
|
| +
|
| +// For each file in archive, write a delta for it into out_file
|
| +// and update 'file' to refer to the delta.
|
| +// This is a recursive function. Returns true on success.
|
| +bool DeltaDiffGenerator::WriteFileDiffsToDeltaFile(
|
| + DeltaArchiveManifest* archive,
|
| + DeltaArchiveManifest_File* file,
|
| + const std::string& file_name,
|
| + const std::string& old_path,
|
| + const std::string& new_path,
|
| + FileWriter* out_file_writer,
|
| + int* out_file_length) {
|
| + TEST_AND_RETURN_FALSE(file->has_mode());
|
| +
|
| + // Stat the actual file, too
|
| + struct stat stbuf;
|
| + TEST_AND_RETURN_FALSE_ERRNO(lstat((new_path + "/" + file_name).c_str(),
|
| + &stbuf) == 0);
|
| + TEST_AND_RETURN_FALSE(stbuf.st_mode == file->mode());
|
| +
|
| + // See if we're a directory or not
|
| + if (S_ISDIR(file->mode())) {
|
| + for (int i = 0; i < file->children_size(); i++) {
|
| + DeltaArchiveManifest_File_Child* child = file->mutable_children(i);
|
| + DeltaArchiveManifest_File* child_file =
|
| + archive->mutable_files(child->index());
|
| + TEST_AND_RETURN_FALSE(WriteFileDiffsToDeltaFile(
|
| + archive,
|
| + child_file,
|
| + child->name(),
|
| + old_path + "/" + file_name,
|
| + new_path + "/" + file_name,
|
| + out_file_writer,
|
| + out_file_length));
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + if (S_ISFIFO(file->mode()) || S_ISSOCK(file->mode())) {
|
| + // These don't store any additional data
|
| + return true;
|
| + }
|
| +
|
| + vector<char> data;
|
| + bool should_compress = true;
|
| + bool format_set = false;
|
| + DeltaArchiveManifest_File_DataFormat format;
|
| + if (S_ISLNK(file->mode())) {
|
| + LOG(INFO) << "link";
|
| + TEST_AND_RETURN_FALSE(EncodeLink(new_path + "/" + file_name, &data));
|
| + } else if (S_ISCHR(file->mode()) || S_ISBLK(file->mode())) {
|
| + LOG(INFO) << "dev";
|
| + TEST_AND_RETURN_FALSE(EncodeDev(stbuf, &data));
|
| + } else if (S_ISREG(file->mode())) {
|
| + LOG(INFO) << "reg";
|
| + // regular file. We may use a delta here.
|
| + TEST_AND_RETURN_FALSE(EncodeFile(old_path, new_path, file_name, &format,
|
| + &data));
|
| + should_compress = false;
|
| + format_set = true;
|
| + if ((format == DeltaArchiveManifest_File_DataFormat_BSDIFF) ||
|
| + (format == DeltaArchiveManifest_File_DataFormat_FULL_GZ))
|
| + TEST_AND_RETURN_FALSE(!data.empty());
|
| + } else {
|
| + // Should never get here; unhandled mode type.
|
| + LOG(ERROR) << "Unhandled mode type: " << file->mode();
|
| + return false;
|
| + }
|
| + LOG(INFO) << "data len: " << data.size();
|
| + if (!format_set) {
|
| + // Pick a format now
|
| + vector<char> compressed_data;
|
| + TEST_AND_RETURN_FALSE(GzipCompress(data, &compressed_data));
|
| + if (compressed_data.size() < data.size()) {
|
| + format = DeltaArchiveManifest_File_DataFormat_FULL_GZ;
|
| + data.swap(compressed_data);
|
| + } else {
|
| + format = DeltaArchiveManifest_File_DataFormat_FULL;
|
| + }
|
| + format_set = true;
|
| + }
|
| +
|
| + if (!data.empty()) {
|
| + TEST_AND_RETURN_FALSE(format_set);
|
| + file->set_data_format(format);
|
| + file->set_data_offset(*out_file_length);
|
| + TEST_AND_RETURN_FALSE(static_cast<ssize_t>(data.size()) ==
|
| + out_file_writer->Write(&data[0], data.size()));
|
| + file->set_data_length(data.size());
|
| + *out_file_length += data.size();
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +bool DeltaDiffGenerator::EncodeLink(const std::string& path,
|
| + std::vector<char>* out) {
|
| + // Store symlink path as file data
|
| + vector<char> link_data(4096);
|
| + int rc = readlink(path.c_str(), &link_data[0], link_data.size());
|
| + TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
|
| + link_data.resize(rc);
|
| + out->swap(link_data);
|
| + return true;
|
| +}
|
| +
|
| +bool DeltaDiffGenerator::EncodeDev(const struct stat& stbuf,
|
| + std::vector<char>* out) {
|
| + LinuxDevice dev;
|
| + dev.set_major(major(stbuf.st_rdev));
|
| + dev.set_minor(minor(stbuf.st_rdev));
|
| + out->resize(dev.ByteSize());
|
| + TEST_AND_RETURN_FALSE(dev.SerializeToArray(&(*out)[0], out->size()));
|
| + return true;
|
| +}
|
| +
|
| +// Encode the file at new_path + "/" + file_name. It may be a binary diff
|
| +// based on old_path + "/" + file_name. out_data_format will be set to
|
| +// the format used. out_data_format may not be NULL.
|
| +bool DeltaDiffGenerator::EncodeFile(
|
| + const string& old_dir,
|
| + const string& new_dir,
|
| + const string& file_name,
|
| + DeltaArchiveManifest_File_DataFormat* out_data_format,
|
| + vector<char>* out) {
|
| + TEST_AND_RETURN_FALSE(out_data_format);
|
| + // First, see the full length:
|
| + vector<char> full_data;
|
| + TEST_AND_RETURN_FALSE(utils::ReadFile(new_dir + "/" + file_name, &full_data));
|
| + vector<char> gz_data;
|
| + if (!full_data.empty()) {
|
| + TEST_AND_RETURN_FALSE(GzipCompress(full_data, &gz_data));
|
| + }
|
| + vector<char> *ret = NULL;
|
| +
|
| + if (gz_data.size() < full_data.size()) {
|
| + *out_data_format = DeltaArchiveManifest_File_DataFormat_FULL_GZ;
|
| + ret = &gz_data;
|
| + } else {
|
| + *out_data_format = DeltaArchiveManifest_File_DataFormat_FULL;
|
| + ret = &full_data;
|
| + }
|
| +
|
| + struct stat old_stbuf;
|
| + if ((stat((old_dir + "/" + file_name).c_str(), &old_stbuf) < 0) ||
|
| + (!S_ISREG(old_stbuf.st_mode))) {
|
| + // stat() failed or old file is not a regular file. Just send back the full
|
| + // contents
|
| + *out = *ret;
|
| + return true;
|
| + }
|
| + // We have an old file. Do a binary diff. For now use bsdiff.
|
| + const string kPatchFile = "/tmp/delta.patch";
|
| +
|
| + vector<string> cmd;
|
| + cmd.push_back("/usr/bin/bsdiff");
|
| + cmd.push_back(old_dir + "/" + file_name);
|
| + cmd.push_back(new_dir + "/" + file_name);
|
| + cmd.push_back(kPatchFile);
|
| +
|
| + int rc = 1;
|
| + TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &rc));
|
| + TEST_AND_RETURN_FALSE(rc == 0);
|
| + vector<char> patch_file;
|
| + TEST_AND_RETURN_FALSE(utils::ReadFile(kPatchFile, &patch_file));
|
| + unlink(kPatchFile.c_str());
|
| +
|
| + if (patch_file.size() < ret->size()) {
|
| + *out_data_format = DeltaArchiveManifest_File_DataFormat_BSDIFF;
|
| + ret = &patch_file;
|
| + }
|
| +
|
| + *out = *ret;
|
| + return true;
|
| +}
|
| +
|
| +DeltaArchiveManifest* DeltaDiffGenerator::EncodeMetadataToProtoBuffer(
|
| + const char* new_path) {
|
| + Node node;
|
| + if (!UpdateNodeFromPath(new_path, &node))
|
| + return NULL;
|
| + int index = 0;
|
| + PopulateChildIndexes(&node, &index);
|
| + DeltaArchiveManifest *ret = new DeltaArchiveManifest;
|
| + NodeToDeltaArchiveManifest(&node, ret);
|
| + return ret;
|
| +}
|
| +
|
| +bool DeltaDiffGenerator::EncodeDataToDeltaFile(DeltaArchiveManifest* archive,
|
| + const std::string& old_path,
|
| + const std::string& new_path,
|
| + const std::string& out_file) {
|
| + DirectFileWriter out_writer;
|
| + int r = out_writer.Open(out_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
| + TEST_AND_RETURN_FALSE_ERRNO(r >= 0);
|
| + ScopedFileWriterCloser closer(&out_writer);
|
| + TEST_AND_RETURN_FALSE(out_writer.Write(DeltaDiffParser::kFileMagic,
|
| + strlen(DeltaDiffParser::kFileMagic))
|
| + == static_cast<ssize_t>(
|
| + strlen(DeltaDiffParser::kFileMagic)));
|
| + // Write 8 null bytes. This will be filled in w/ the offset of
|
| + // the protobuf.
|
| + TEST_AND_RETURN_FALSE(out_writer.Write("\0\0\0\0\0\0\0\0", 8) == 8);
|
| + // 8 more bytes will be filled w/ the protobuf length.
|
| + TEST_AND_RETURN_FALSE(out_writer.Write("\0\0\0\0\0\0\0\0", 8) == 8);
|
| + int out_file_length = strlen(DeltaDiffParser::kFileMagic) + 16;
|
| +
|
| + TEST_AND_RETURN_FALSE(archive->files_size() > 0);
|
| + DeltaArchiveManifest_File* file = archive->mutable_files(0);
|
| +
|
| + TEST_AND_RETURN_FALSE(WriteFileDiffsToDeltaFile(archive,
|
| + file,
|
| + "",
|
| + old_path,
|
| + new_path,
|
| + &out_writer,
|
| + &out_file_length));
|
| +
|
| + // Finally, write the protobuf to the end of the file
|
| + string encoded_archive;
|
| + TEST_AND_RETURN_FALSE(archive->SerializeToString(&encoded_archive));
|
| +
|
| + // Compress the protobuf (which contains filenames)
|
| + vector<char> compressed_encoded_archive;
|
| + TEST_AND_RETURN_FALSE(GzipCompressString(encoded_archive,
|
| + &compressed_encoded_archive));
|
| +
|
| + TEST_AND_RETURN_FALSE(out_writer.Write(compressed_encoded_archive.data(),
|
| + compressed_encoded_archive.size()) ==
|
| + static_cast<ssize_t>(
|
| + compressed_encoded_archive.size()));
|
| +
|
| + // write offset of protobut to just after the file magic
|
| + int64 big_endian_protobuf_offset = htobe64(out_file_length);
|
| + TEST_AND_RETURN_FALSE(pwrite(out_writer.fd(),
|
| + &big_endian_protobuf_offset,
|
| + sizeof(big_endian_protobuf_offset),
|
| + strlen(DeltaDiffParser::kFileMagic)) ==
|
| + sizeof(big_endian_protobuf_offset));
|
| + // Write the size just after the offset
|
| + int64 pb_length = htobe64(compressed_encoded_archive.size());
|
| + TEST_AND_RETURN_FALSE(pwrite(out_writer.fd(),
|
| + &pb_length,
|
| + sizeof(pb_length),
|
| + strlen(DeltaDiffParser::kFileMagic) +
|
| + sizeof(big_endian_protobuf_offset)) ==
|
| + sizeof(pb_length));
|
| + return true;
|
| +}
|
| +
|
| +} // namespace chromeos_update_engine
|
|
|