librsyncを使ってみる

# `librsync`とは

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

[^1]: https://github.com/librsync/librsync/blob/master/README.md

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

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

# `librsync`を試してみる

## インストール

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

```bash
$ sudo pacman -S librsync
```

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

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

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

インストールされます。

## コーディング

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

```c:ｍain.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`経由で送信する。
1. 送信側からファイルの差分データ(`deltafile`)を受信したら、`rs_patch_file()`を用いて古いファイル(`oldfile`)に`deltafile`を適用して最新版のファイル(`tofile`)を受信側に作成する。

送信側(親プロセス)

1. 受信側から古いファイルのチェックサム(`sigfile`)が送られてくるのを待ち、`rs_loading_file()`を用いて、チェックサムをメモリ上に展開する。またチェックサムのハッシュテーブルを作成する。
1. `rs_delta_file()`を用いて、チェックサムと送信側の最新版のファイル(`fromfile`)から差分ファイル(`deltafile`)を作成する。

このプログラムは、以下のようにコンパイルすることができます。

```bash
$ gcc -lrsync main.c
```

## 実行

まず、`oldfile` (受信側用), `fromfile`(送信側用)を生成します。100はファイルのサイズなので適当に変更してください。

```bash
$ cat /dev/urandom/ | tr -dc "[:alnum:]" | head -c 100 > fromfile
$ cat /dev/urandom/ | tr -dc "[:alnum:]" | head -c 100 > oldfile
```

次にプログラムを実行します。

```bash
$ ./a.out oldfile fromfile tofile
```

送信側のファイル`fromfile`と全く同じものが受信側のファイル`tofile`として作成されたことを確認します。
(`fromfile`と`tofile`で同じファイル名を指定すると問題が生じるので、別のファイルを指定してください。)

# 成果物

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