/*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
//! LibRsync
//! # Examples
//! In the following example, the signature file should be computed on the local file, sent to the remote machine where the delta is computed. Then, `delta_file` must be run on the remote machine, creating a file /tmp/delta. Then, /tmp/delta should be copied to the local machine, and finally `patch_file` must be applied to write a copy of the remote file in /tmp/new.
//!
//! ```
//! extern crate rsync;
//! use rsync::*;
//!
//! fn main(){
//! sig_file("/tmp/old","/tmp/old.sig",2048,8,None).unwrap();
//! let sig=loadsig_file("/tmp/old.sig",None).unwrap();
//! delta_file(&sig, "/tmp/new", "/tmp/delta",None).unwrap();
//! patch_file("/tmp/old","/tmp/delta","/tmp/new.new",None).unwrap();
//! }
//! ```
extern crate libc;
use libc::{c_int,c_char,size_t,c_longlong,FILE,fopen,fclose,strlen};
use std::ptr;
use std::path::{Path};
use std::{error,fmt};
#[allow(missing_copy_implementations)]
enum CRsSignature {}
type CRsResult=c_int;
// This needs to be generated from the C headers.
type RsLongT=c_longlong;
#[repr(C)]
pub struct rs_stats_t {
op:*const c_char,
lit_cmds:c_int,
lit_bytes:RsLongT,
lit_cmdbytes:RsLongT,
copy_cmds:RsLongT,
copy_bytes:RsLongT,
copy_cmdbytes:RsLongT,
sig_cmds:RsLongT,
sig_bytes:RsLongT,
false_matches:c_int,
sig_blocks:RsLongT,
block_len:size_t,
in_bytes:RsLongT,
out_bytes:RsLongT
}
#[derive(Debug)]
pub enum Error {
Error(c_int),
PathEncoding,
FileMissing,
IO(std::io::Error)
}
extern "C" {
fn rs_loadsig_file(old_file:*const FILE, sig_out:*mut*mut CRsSignature,stats:*mut rs_stats_t)->CRsResult;
fn rs_sig_file(old_file:*const FILE,sig_file:*const FILE,
block_len:size_t, strong_len:size_t,
stats:*mut rs_stats_t)->CRsResult;
fn rs_delta_file(sig:*const CRsSignature,input_file:*const FILE,output_file:*const FILE,stats:*mut rs_stats_t)->CRsResult;
fn rs_patch_file(basis:*const FILE, delta:*const FILE, new_file:*const FILE, stats:*mut rs_stats_t)->CRsResult;
fn rs_strerror(err:CRsResult)->*const c_char;
fn rs_free_sumset(sig:*const CRsSignature);
fn rs_build_hash_table(sig:*mut CRsSignature)->c_int;
fn rs_sumset_dump(sig:*const CRsSignature);
}
pub fn sumset_dump(sig:&Signature) {
unsafe { rs_sumset_dump(sig.sig) }
}
pub struct Signature {
sig:*mut CRsSignature
}
impl Drop for Signature {
fn drop(&mut self){
unsafe { rs_free_sumset(self.sig) }
}
}
/// Generates a signature of the "old_file" input file and writes it to "signature_file".
pub fn sig_file<P:AsRef<Path>>(old_file:P,signature_file:P,
block_len:usize,strong_len:usize,stats:Option<&mut rs_stats_t>)->Result<(),Error>{
let old=old_file.as_ref().as_os_str().to_os_string();
let sig=signature_file.as_ref().as_os_str().to_os_string();
match (old.to_str(),sig.to_str()) {
(Some(old),Some(sig))=>{
unsafe {
let old=std::ffi::CString::new(old).unwrap();
let fi=fopen(old.as_ptr() as *const c_char,"rb".as_ptr() as *const c_char);
if !fi.is_null() {
let sig=std::ffi::CString::new(sig).unwrap();
let fo=fopen(sig.as_ptr() as *const c_char,"wb".as_ptr() as *const c_char);
let e=rs_sig_file(fi,fo,
block_len as size_t,
strong_len as size_t,
match stats {
None => std::ptr::null_mut(),
Some(x)=>x
});
fclose(fi);
fclose(fo);
if e==0 { Ok(()) } else { Err(Error::Error(e)) }
} else {
println!("old: {:?}",old);
Err(Error::FileMissing)
}
}
},
_=>Err(Error::PathEncoding)
}
}
/// Loads a signature from "sig_file".
pub fn loadsig_file<P:AsRef<Path>>(sig_file:P,stats:Option<&mut rs_stats_t>)->Result<Signature,Error>{
let sig_file=sig_file.as_ref().as_os_str().to_os_string();
match sig_file.to_str() {
Some(s)=>
unsafe {
let mut sig_out=ptr::null_mut();
let s=std::ffi::CString::new(s).unwrap();
let fi=fopen(s.as_ptr() as *const c_char,"rb".as_ptr() as *const c_char);
if !fi.is_null(){
let e=rs_loadsig_file(fi, &mut sig_out,
match stats {
None => std::ptr::null_mut(),
Some(x)=>x
});
fclose(fi);
if e==0 { Ok(Signature{sig:sig_out}) } else { Err(Error::Error(e)) }
} else {
Err(Error::FileMissing)
}
},
None =>{
Err(Error::PathEncoding)
}
}
}
/// Produce a delta from a signature and a "new" file.
pub fn delta_file<P:AsRef<Path>>(sig:&Signature,input_file:P,output_file:P,stats:Option<&mut rs_stats_t>)->Result<(),Error>{
let input_file=input_file.as_ref().as_os_str().to_os_string();
let output_file=output_file.as_ref().as_os_str().to_os_string();
match (input_file.to_str(),output_file.to_str()) {
(Some(i),Some(o))=>{
unsafe {
let i=std::ffi::CString::new(i).unwrap();
let fi=fopen(i.as_ptr() as *const c_char,"rb".as_ptr() as *const c_char);
if !fi.is_null() {
let o=std::ffi::CString::new(o).unwrap();
let fo=fopen(o.as_ptr() as *const c_char,"wb".as_ptr() as *const c_char);
rs_build_hash_table(sig.sig);
let e=rs_delta_file(sig.sig,fi,fo,
match stats {
None => std::ptr::null_mut(),
Some(x)=>x
});
fclose(fi);
fclose(fo);
if e==0 { Ok(()) } else { Err(Error::Error(e)) }
} else {
Err(Error::IO(std::io::Error::last_os_error()))
}
}
},
_=>Err(Error::PathEncoding)
}
}
/// Applies a delta file (or a "patch") to "old_file", and write the result to "new_file".
pub fn patch_file<P:AsRef<Path>>(old_file:P,delta:P,new_file:P,stats:Option<&mut rs_stats_t>)->Result<(),Error>{
let old_file=old_file.as_ref().as_os_str().to_os_string();
let delta=delta.as_ref().as_os_str().to_os_string();
let new_file=new_file.as_ref().as_os_str().to_os_string();
match (old_file.to_str(),delta.to_str(),new_file.to_str()) {
(Some(old),Some(delta),Some(new))=>{
unsafe {
let old=std::ffi::CString::new(old).unwrap();
let fa=fopen(old.as_ptr() as *const c_char,"rb".as_ptr() as *const c_char);
let delta=std::ffi::CString::new(delta).unwrap();
let fb=fopen(delta.as_ptr() as *const c_char,"rb".as_ptr() as *const c_char);
let new=std::ffi::CString::new(new).unwrap();
let fc=fopen(new.as_ptr() as *const c_char,"wb".as_ptr() as *const c_char);
if !fa.is_null() && !fb.is_null() {
let e=rs_patch_file(fa,fb,fc,match stats { Some(st)=>st, None => std::ptr::null_mut() });
fclose(fa);
fclose(fb);
fclose(fc);
if e==0 { Ok(()) } else { Err(Error::Error(e)) }
} else {
Err(Error::FileMissing)
}
}
},
_=>Err(Error::PathEncoding)
}
}
impl error::Error for Error {
fn cause(&self) -> Option<&dyn error::Error> {
match *self {
Error::IO(ref e)=>Some(e),
_ => None
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::PathEncoding => write!(f,"Error in the encoding of a path"),
Error::FileMissing => write!(f,"File missing"),
Error::Error(e)=>{
write!(f,"{}",
unsafe {
let c=rs_strerror(e);
std::str::from_utf8_unchecked(
std::slice::from_raw_parts(c as *const u8,strlen(c))
)
})
},
Error::IO(ref e)=>e.fmt(f)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::prelude::*;
extern crate rand;
#[test]
fn test() {
let mut local:Vec<u8>=vec![0;10000];
let mut remote:Vec<u8>=vec![0;10000];
for x in local.iter_mut() { *x = rand::random() }
for x in remote.iter_mut() { *x = rand::random() }
let local_file="local";
let remote_file="remote";
{
let mut f = File::create(local_file).unwrap();
f.write_all(&local[..]).unwrap();
let mut f = File::create(remote_file).unwrap();
f.write_all(&remote[..]).unwrap();
}
let signature_file="local.sig";
sig_file(local_file,signature_file,2048,8,None).unwrap();
let signature=loadsig_file(signature_file,None).unwrap();
let delta="delta";
delta_file(&signature,remote_file,delta,None).unwrap();
let copy_file="remote.copy";
patch_file(local_file,delta,copy_file,None).unwrap();
let mut f = File::open(copy_file).unwrap();
let mut contents: Vec<u8> = Vec::new();
let _ = f.read_to_end(&mut contents).unwrap();
if contents!=remote { panic!("test failled") }
}
}