[Linux] FUSEでデータ生成

2017年9月19日火曜日

FUSE linux

データ生成

[Linux] FUSEを使ってみようではFUSEを使って、ファイルシステムとしてマウントする例を見たけど、今度はFUSEを使ってデータを生成するファイルを作ってみよう。

開発用のヘッダファイルとかが必要になるので、https://github.com/libfuseからライブラリを入手するか、fuse-developパッケージをインストールしよう。
今回はfuse 2.9.3を使用した。

0を生成し続ける

例えば、/dev/zeroのようにひたすら0を生成し続けるファイルを作りたければ以下のようにすればいい。
/dev/zeroとは違って、null文字ではなく、文字の'0'を生成するようにしてみよう。

今回はFUSEのサンプルプログラムであるhello.cをコピーして、これを元にした。
ちなみに、サンプルプログラム(hello.c)はGPLだけど、libfuse自体はLGPLなので、libfuseを使っているからといって自分のプログラムをGPLにしなければいけないわけではない。

zero.c
/*
  Copyright (C) 2017 Takehiko Ichioka <ichioka.takehiko@gmail.com>

  based on code by FUSE: Filesystem in Userspace
  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>

  This program can be distributed under the terms of the GNU GPL.
  See the file COPYING.

  gcc -Wall zero.c `pkg-config fuse --cflags --libs` -o zero
*/

#define FUSE_USE_VERSION 26

#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

static const char *zero_path = "/zero";

static int zero_getattr(const char *path, struct stat *stbuf)
{
 int res = 0;

 memset(stbuf, 0, sizeof(struct stat));
 if (strcmp(path, "/") == 0) {
  stbuf->st_mode = S_IFDIR | 0755;
  stbuf->st_nlink = 2;
 } else if (strcmp(path, zero_path) == 0) {
  stbuf->st_mode = S_IFREG | 0444;
  stbuf->st_nlink = 1;
  stbuf->st_size = 0;
 } else
  res = -ENOENT;

 return res;
}

static int zero_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    off_t offset, struct fuse_file_info *fi)
{
 (void) offset;
 (void) fi;

 if (strcmp(path, "/") != 0)
  return -ENOENT;

 filler(buf, ".", NULL, 0);
 filler(buf, "..", NULL, 0);
 filler(buf, zero_path + 1, NULL, 0);

 return 0;
}

static int zero_open(const char *path, struct fuse_file_info *fi)
{
 if (strcmp(path, zero_path) != 0)
  return -ENOENT;

 if ((fi->flags & 3) != O_RDONLY)
  return -EACCES;

 return 0;
}

static int zero_read(const char *path, char *buf, size_t size, off_t offset,
        struct fuse_file_info *fi)
{
 (void) fi;
 if(strcmp(path, zero_path) != 0)
  return -ENOENT;
 memset(buf, '0', size);

 return size;
}

static struct fuse_operations zero_oper = {
 .getattr = zero_getattr,
 .readdir = zero_readdir,
 .open  = zero_open,
 .read  = zero_read,
};

int main(int argc, char *argv[])
{
 return fuse_main(argc, argv, &zero_oper, NULL);
}

基本的には前述のhello.cと同じになる。
hello_read関数が呼ばれると、とにかくバッファサイズ分'0'を詰めているのが異なるところだ。

コンパイル&実行

これをコンパイルして、実行するとmntディレクトリにzeroファイルが見えるようになる。

$ gcc -Wall zero.c $(pkg-config fuse --cflags --libs) -o zero
$ ls -l mnt
total 0
$ ./zero -o direct_io mnt
$ ls -l mnt
total 0
-r--r--r-- 1 root root 0 Jan  1  1970 zero

ポイントは実行時に -o direct_io オプションをつけていること。
これをつけないと、zeroファイル読み込み時にファイルサイズが0と判定されて、1バイトも読み込まれずに終了してしまう。

これで、10文字分'0'を読み込みたければ、以下のようにすればいい。

$ cat mnt/zero | head -c10
0000000000$ 

これをうまく使えば、ファイル読み込みを適度に遅延させたり、ログ出力をシミュレートすることもできそうだね。