librsync
とは
librsync
は、rsync
で使われている高速なファイル転送アルゴリズムを、自分のプログラムから使うことのできるライブラリです。現在、Dropbox
などで使われています1。
rsync
は、ファイルをリモートサーバーにバックアップする際、サーバーに保管された古いファイルとの差分のみを送信することで高速にバックアップを行えるツールで、その仕組みは以下のようになっています。
- サーバーは指示されたファイルを一定のブロックサイズに分割し、それぞれに対しチェックサムを計算してクライアントに送信する。
- クライアントは最新版のファイルの各バイトから始まる一定のブロックサイズのバイト列についてそれぞれチェックサムを計算する。(このとき、高速化のためローリングチェックサムと呼ばれる簡易的なチェックサムが用いられる。)これらの値をサーバーから送られたチェックサムと比較してファイルの差分を作成し、サーバーに送信する。
- サーバーはクライアントから送られたファイルの差分を指示されたファイルに適用することでファイルを最新版に更新する。
librsync
を試してみる
インストール
まず、パッケージマネージャなどを用いてlibrsync
をインストールします。
$ sudo pacman -S librsync
もしパッケージが見つからない場合は
https://github.com/librsync/librsync
を使用できます。
インストールすると、(私の環境では)
- ヘッダファイルが
/usr/include/librsync.h
等に - 共有ライブラリが
/usr/lib/librsync.so
等に
インストールされます。
コーディング
以下のようなコードを用意します。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <librsync.h>
int main(int argc, char **argv) {
if (argc != 4) {
fprintf(stderr, "Usage: %s oldfile fromfile tofile\n", argv[0]);
return 1;
}
FILE *oldfile, *sigfile, *deltafile, *fromfile, *tofile;
rs_signature_t *sumset;
rs_result r;
char *sigfile_name = strdup(tmpnam(NULL));
char *deltafile_name = strdup(tmpnam(NULL));
mkfifo(sigfile_name, S_IWUSR | S_IRUSR);
mkfifo(deltafile_name, S_IWUSR | S_IRUSR);
if (fork() == 0) { // receiver
oldfile = fopen(argv[1], "rb");
deltafile = fopen(deltafile_name, "rb");
sigfile = fopen(sigfile_name, "wb");
tofile = fopen(argv[3], "wb");
// oldfile -> sigfile
r = rs_sig_file(oldfile, sigfile, RS_DEFAULT_BLOCK_LEN, 8, RS_BLAKE2_SIG_MAGIC, NULL);
fclose(sigfile);
// oldfile, deltafile -> tofile
r = rs_patch_file(oldfile, deltafile, tofile, NULL);
fclose(oldfile);
fclose(deltafile);
fclose(tofile);
}
// sender
deltafile = fopen(deltafile_name, "wb");
sigfile = fopen(sigfile_name, "rb");
fromfile = fopen(argv[2], "rb");
// sigfile -> sumset
r = rs_loadsig_file(sigfile, &sumset, NULL);
rs_build_hash_table(sumset);
// sumset, fromfile -> deltafile
r = rs_delta_file(sumset, fromfile, deltafile, NULL);
fclose(deltafile);
fclose(sigfile);
fclose(fromfile);
return 0;
}
このプログラムでは、まずsigfile
及びdeltafile
の2つのfifoを作成し、fork()
を用いて送信側と受信側のプロセスに分けて実験しています。
受信側(子プロセス)
-
rs_sig_file()
を用いて、古いファイル(oldfile
)からブロックサイズごとのチェックサムを計算し、送信側にsigfile
経由で送信する。 - 送信側からファイルの差分データ(
deltafile
)を受信したら、rs_patch_file()
を用いて古いファイル(oldfile
)にdeltafile
を適用して最新版のファイル(tofile
)を受信側に作成する。
送信側(親プロセス)
- 受信側から古いファイルのチェックサム(
sigfile
)が送られてくるのを待ち、rs_loading_file()
を用いて、チェックサムをメモリ上に展開する。またチェックサムのハッシュテーブルを作成する。 -
rs_delta_file()
を用いて、チェックサムと送信側の最新版のファイル(fromfile
)から差分ファイル(deltafile
)を作成する。
このプログラムは、以下のようにコンパイルすることができます。
$ gcc -lrsync main.c
実行
まず、oldfile
(受信側用), fromfile
(送信側用)を生成します。100はファイルのサイズなので適当に変更してください。
$ cat /dev/urandom/ | tr -dc "[:alnum:]" | head -c 100 > fromfile
$ cat /dev/urandom/ | tr -dc "[:alnum:]" | head -c 100 > oldfile
次にプログラムを実行します。
$ ./a.out oldfile fromfile tofile
送信側のファイルfromfile
と全く同じものが受信側のファイルtofile
として作成されたことを確認します。
(fromfile
とtofile
で同じファイル名を指定すると問題が生じるので、別のファイルを指定してください。)
成果物
成果物を以下にアップロードしています。
https://github.com/yasuo-ozu/librsync_test