0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

NetBSDAdvent Calendar 2022

Day 21

NetBSDでname/valueリストを扱うlibnvを試してみる

Posted at

NetBSD Advent Calendar 2022 21日目の記事です。今日はNPF(NetBSD Packet Filter)のソースコード中に出てきた、 libnv の紹介を使用と思います。

libnvとは?

20日目の記事でNPFのソースコードを調べた際に、 nvlist_add_string() という関数が出てきました。例えば、/usr/src/lib/libnpf/npf.cにある以下のような処理です。

 629 nl_ext_t *
 630 npf_ext_construct(const char *name)
 631 {
 ...
 639     nvlist_add_string(ext->ext_dict, "name", name);
 640     return ext;
 641 }

関数名に含まれている"nvlist"とは、そもそも一体何なのでしょうか?
libnv のソースコードは /usr/src/sys/external/bsd/libnv/dist 以下に置かれています。

$ ls
CVS/                dnv.9               nv.9                nvlist.c
cnv.9               dnv.h               nv.h                nvlist_impl.h
cnv.h               dnvlist.c           nv_compat.h         nvpair.c
cnvlist.c           msgio.c             nv_impl.h           nvpair_impl.h
common_impl.h       msgio.h             nv_kern_netbsd.c

nv.9man 9 nv で参照されるファイルのようです。なぜか man 9 nv でマニュアルが引けないのですが、 mandoc コマンドで整形したマニュアルにしてみます。

$ mandoc nv.9
NV(9)                      Kernel Developer's Manual                     NV(9)

NAME
     nvlist_create, nvlist_destroy, nvlist_error, nvlist_set_error,
     nvlist_empty, nvlist_flags, nvlist_exists, nvlist_free, nvlist_clone,
     nvlist_dump, nvlist_fdump, nvlist_size, nvlist_pack, nvlist_unpack,
     nvlist_send, nvlist_recv, nvlist_xfer, nvlist_in_array, nvlist_next,
     nvlist_add, nvlist_move, nvlist_get, nvlist_take, nvlist_append - library
     for name/value pairs

LIBRARY
     Name/value pairs library (libnv, -lnv)

...
DESCRIPTION
     The libnv library allows to easily manage name value pairs as well as
     send and receive them over sockets.  A group (list) of name value pairs
     is called an nvlist.  The API supports the following data types:

なるほど、どうやら libnvName/value のペアになったデータを扱うためのライブラリで、さらにソケット経由でデータをやりとりすることも可能なようですね。

libnvはBSD系のOSなら利用できる?

NetBSDでは man 9 nv は存在していませんが、FreeBSDではlibnv(9)でマニュアルを引くことができます。少なくとも、FreeBSDとNetBSDでは libnv が利用できるようです。

FreeBSDでは /lib/libnv.so.0 でライブラリが提供されていますが、NetBSDにはこのライブラリが存在していません…。 libnv のソースコードは提供されているため、 libnv のソースコードを一緒にコンパイルする(アプリに直接組み込む)ことで利用できるはずです。

が、後述する libnv を使用したサンプルをコンパイルしようとすると、以下のエラーが発生します。

$ gcc \
    /usr/src/sys/external/bsd/libnv/dist/*.c \
    sample.c \
    -o sample \
    -I/usr/src/sys/external/bsd/libnv/dist/
ld: /tmp//cc8QVbnj.o: in function `main':
sample.c:(.text+0xc3): undefined reference to `nvlist_send'
ld: sample.c:(.text+0x11d): undefined reference to `nvlist_recv'

nvlist_send() という関数が見つからないというエラーになっています。ソースコードをgrepしてみると、この関数は nvlist.c で定義されています。

$ pushd /usr/src/sys/external/bsd/libnv/dist/
$ grep nvlist_send *.c
nv_kern_netbsd.c:nvlist_send_ioctl(int fd, unsigned long cmd, const nvlist_t *nvl)
nvlist.c:nvlist_send(int sock, const nvlist_t *nvl)
nvlist.c:       if (nvlist_send(sock, nvl) < 0) {

nvlist_send() がコンパイルされるための条件を見ると、 _KERNEL_STANDALONE が「定義されていない」状態で、かつ WITH_MSGIO マクロが定義されている場合にコンパイルされます。

grep -B3 ^nvlist_send nvlist.c

#if !defined(_KERNEL) && !defined(_STANDALONE) && defined(WITH_MSGIO)
int
nvlist_send(int sock, const nvlist_t *nvl)

さて、この WITH_MSGIO マクロですが、NetBSDのソースツリーの中では nvlist.c にしか出てきません。つまり、そのままだと常に nvlist_send() が未定義になるという構成になっています。FreeBSDの libnv では、このマクロ自体が存在していないため、NetBSDで独自に追加されているようですが、現状だと意味をなしていない(むしろ libnv をユーザランドで利用する際の妨げになっている?)ように見えます。とりあえず、これ以上の深掘りは止めて、コンパイル時に -DWITH_MSGIO を指定する方法で対応してみます。

libnvの簡単なサンプルを動かしてみる

libnv(9)のマニュアルを参照しながら libnv の簡単なサンプルを作成してみます。基本的には以下の手順によりプロセス間でのデータ通信が行えます。

  • データ送出側での処理
    • nvlist_create() でname/valueリストを作成する。
    • nvlist_add_string() nvlist_add_number() 等で、任意のデータ型のname/valueアイテムを設定する。
    • nvlist_send() で相手側のプロセスにデータを送出する。
    • nvlist_destroy() で確保したリソースを開放する。
  • データ受信側での処理
    • nvlist_recv() でname/valueリストを受信する。
    • nvlist_get_string() nvlist_get_number() 等で値を取得する。値取得時には設定されているnameをキーに指定する。
    • nvlist_destroy() で確保したリソースを開放する。

さっそくこの手順をコードに起こしてみます。具体的な実装は以下になります。

// libnvを使用して親・子プロセス間でデータをやり取りするサンプル。

#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>

// 以下の場所にあるnv.hを参照する。
//   /usr/src/sys/external/bsd/libnv/dist/
#include <nv.h>

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int r;
    int socks[2];
    int status;
    pid_t pid;

    r = socketpair(PF_UNIX, SOCK_STREAM, 0, socks);
    if (r == -1) {
        fprintf(stderr, "Error: sockpair()\n");
        exit(1);
    }

    pid = fork();
    if (pid == -1) {
    } else if (pid != 0) {
        // 親プロセス(データ送信側)
        nvlist_t *nvl;

        close(socks[0]);

        nvl = nvlist_create(0);

        // name/valueの形式で値をセットする。
        nvlist_add_string(nvl, "message", "hello,world");
        nvlist_add_number(nvl, "value", (uint64_t)123456);

        // 文字列と数値を送出する。
        nvlist_send(socks[1], nvl);

        nvlist_destroy(nvl);

        waitpid(pid, &status, 0);
        fprintf(stderr, "waitpid status= %d\n", WIFEXITED(status));
    } else {
        // 子プロセス(データ受信側)
        nvlist_t *nvl;
        const char *message;
        uint64_t value;

        nvl = nvlist_recv(socks[0], 0);
        if (nvl == NULL) {
            fprintf(stderr, "Error: nvlist_recv()\n");
            exit(1);
        }

        // 受信したデータの取り出し。
        message = nvlist_get_string(nvl, "message");
        value = nvlist_get_number(nvl, "value");

        printf("message= '%s', value= %llu\n", message, value);

        nvlist_destroy(nvl);
        fprintf(stderr, "quit child process.\n");
    }

    return 0;
}

コンパイルとプログラムの実行は以下の手順で行います。親プロセスと子プロセスの間で、文字列("hello,world")と値( 123456 )が送受信されていることが確認できます。

$ gcc \
    -DWITH_MSGIO \
    /usr/src/sys/external/bsd/libnv/dist/*.c \
    sample.c \
    -o sample \
    -I/usr/src/sys/external/bsd/libnv/dist/
$
$ ./sample
message= 'hello,world', value= 123456
quit child process.
waitpid status= 1

まとめ

libnv の紹介と簡単なサンプルを作成してみました。C言語ではプロセス間で任意の型のデータを送受信するのは地味に手間なので、こういったライブラリで手順が一般化されているのは助かります。また、今回の記事では触れませんでしたが、NPF(NetBSD Packet Filter)では、ユーザランドとカーネル空間でのデータ送受信にも利用されており、だいぶ汎用性のある機能となっています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?