C
rsync

librsyncを使ってみる

librsyncとは

librsyncは、rsyncで使われている高速なファイル転送アルゴリズムを、自分のプログラムから使うことのできるライブラリです。現在、Dropboxなどで使われています1

rsyncは、ファイルをリモートサーバーにバックアップする際、サーバーに保管された古いファイルとの差分のみを送信することで高速にバックアップを行えるツールで、その仕組みは以下のようになっています。

  1. サーバーは指示されたファイルを一定のブロックサイズに分割し、それぞれに対しチェックサムを計算してクライアントに送信する。
  2. クライアントは最新版のファイルの各バイトから始まる一定のブロックサイズのバイト列についてそれぞれチェックサムを計算する。(このとき、高速化のためローリングチェックサムと呼ばれる簡易的なチェックサムが用いられる。)これらの値をサーバーから送られたチェックサムと比較してファイルの差分を作成し、サーバーに送信する。
  3. サーバーはクライアントから送られたファイルの差分を指示されたファイルに適用することでファイルを最新版に更新する。

librsyncを試してみる

インストール

まず、パッケージマネージャなどを用いてlibrsyncをインストールします。

$ sudo pacman -S librsync

もしパッケージが見つからない場合は
https://github.com/librsync/librsync
を使用できます。

インストールすると、(私の環境では)

  • ヘッダファイルが /usr/include/librsync.h 等に
  • 共有ライブラリが /usr/lib/librsync.so 等に

インストールされます。

コーディング

以下のようなコードを用意します。

main.c
#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()を用いて送信側と受信側のプロセスに分けて実験しています。

受信側(子プロセス)

  1. rs_sig_file()を用いて、古いファイル(oldfile)からブロックサイズごとのチェックサムを計算し、送信側にsigfile経由で送信する。
  2. 送信側からファイルの差分データ(deltafile)を受信したら、rs_patch_file()を用いて古いファイル(oldfile)にdeltafileを適用して最新版のファイル(tofile)を受信側に作成する。

送信側(親プロセス)

  1. 受信側から古いファイルのチェックサム(sigfile)が送られてくるのを待ち、rs_loading_file()を用いて、チェックサムをメモリ上に展開する。またチェックサムのハッシュテーブルを作成する。
  2. 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として作成されたことを確認します。
(fromfiletofileで同じファイル名を指定すると問題が生じるので、別のファイルを指定してください。)

成果物

成果物を以下にアップロードしています。
https://github.com/yasuo-ozu/librsync_test