// Copyright 2015 The Chromium OS 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 <err.h>
#include <fcntl.h>
#include <getopt.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <limits>
#include "bsdiff/bsdiff.h"
#include "bsdiff/bsdiff_arguments.h"
#include "bsdiff/constants.h"
#include "bsdiff/patch_writer_factory.h"
namespace {
// mmap() the passed |filename| to read-only memory and store in |filesize| the
// size of the file. To release the memory, call munmap with the returned
// pointer and filesize. In case of error returns nullptr.
void* MapFile(const char* filename, size_t* filesize) {
int fd = open(filename, O_RDONLY);
if (fd < 0) {
perror("open()");
return nullptr;
}
struct stat st;
fstat(fd, &st);
if (static_cast<uint64_t>(st.st_size) > std::numeric_limits<size_t>::max()) {
fprintf(stderr, "File too big\n");
close(fd);
return nullptr;
}
*filesize = st.st_size;
void* ret = mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (ret == MAP_FAILED) {
perror("mmap()");
close(fd);
return nullptr;
}
close(fd);
return ret;
}
// Generate bsdiff patch from the |old_filename| file to the |new_filename|
// file with options in |arguments|. Store the resulting patch in a new
// |patch_filename| file. Returns 0 on success.
int GenerateBsdiffFromFiles(const char* old_filename,
const char* new_filename,
const char* patch_filename,
const bsdiff::BsdiffArguments& arguments) {
size_t oldsize;
uint8_t* old_buf = static_cast<uint8_t*>(MapFile(old_filename, &oldsize));
if (!old_buf) {
return 1;
}
size_t newsize;
uint8_t* new_buf = static_cast<uint8_t*>(MapFile(new_filename, &newsize));
if (!new_buf) {
munmap(old_buf, oldsize);
return 1;
}
std::unique_ptr<bsdiff::PatchWriterInterface> patch_writer;
std::vector<uint8_t> raw_data;
if (arguments.format() == bsdiff::BsdiffFormat::kLegacy) {
patch_writer = bsdiff::CreateBsdiffPatchWriter(patch_filename);
} else if (arguments.format() == bsdiff::BsdiffFormat::kBsdf2) {
patch_writer = bsdiff::CreateBSDF2PatchWriter(patch_filename,
arguments.compressor_types(),
arguments.brotli_quality());
} else if (arguments.format() == bsdiff::BsdiffFormat::kEndsley) {
patch_writer = bsdiff::CreateEndsleyPatchWriter(
&raw_data, arguments.compressor_types()[0], arguments.brotli_quality());
} else {
std::cerr << "unexpected bsdiff format." << std::endl;
return 1;
}
int ret = bsdiff::bsdiff(old_buf, oldsize, new_buf, newsize,
arguments.min_length(), patch_writer.get(), nullptr);
munmap(old_buf, oldsize);
munmap(new_buf, newsize);
if (!ret && arguments.format() == bsdiff::BsdiffFormat::kEndsley) {
// Store the raw_data on disk.
FILE* fp = fopen(patch_filename, "wb");
if (!fp) {
perror("Opening the patch file");
return 1;
}
if (raw_data.size() != fwrite(raw_data.data(), 1, raw_data.size(), fp)) {
perror("Writing to the patch file");
ret = 1;
}
fclose(fp);
}
return ret;
}
void PrintUsage(const std::string& proc_name) {
std::cerr << "usage: " << proc_name
<< " [options] oldfile newfile patchfile\n";
std::cerr << " --format <legacy|bsdiff40|bsdf2|endsley> The format of the"
" bsdiff patch.\n"
<< " --minlen LEN The minimum match length "
"required to consider a match in the algorithm.\n"
<< " --type <bz2|brotli|nocompression> The algorithm to compress "
"the patch, bsdf2 format only. Multiple supported compressors "
"should be split by ':', e.g. bz2:brotli.\n"
<< " --brotli_quality Quality of the brotli "
"compressor.\n";
}
} // namespace
int main(int argc, char* argv[]) {
bsdiff::BsdiffArguments arguments;
if (!arguments.ParseCommandLine(argc, argv)) {
PrintUsage(argv[0]);
return 1;
}
// The optind will be updated in ParseCommandLine to parse the options; and
// we expect the rest of the arguments to be oldfile, newfile, patchfile.
if (!arguments.IsValid() || argc - optind != 3) {
PrintUsage(argv[0]);
return 1;
}
return GenerateBsdiffFromFiles(argv[optind], argv[optind + 1],
argv[optind + 2], arguments);
}