ふふーん

一緒にやりたくなったのよ!あなたと、調合を!

OVMF_VARSをパースするやつ

OVMF_VARS.fdがどういうフォーマットで値を保存しているのかみてみた。

$ hexdump -C OVMF_VARS.fd | head -n20
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  8d 2b f1 ff 96 76 8b 4c  a9 85 27 47 07 5b 4f 50  |.+...v.L..'G.[OP|
00000020  00 00 02 00 00 00 00 00  5f 46 56 48 ff fe 04 00  |........_FVH....|
00000030  48 00 19 f9 00 00 00 02  20 00 00 00 00 10 00 00  |H....... .......|
00000040  00 00 00 00 00 00 00 00  78 2c f3 aa 7b 94 9a 43  |........x,..{..C|
00000050  a1 80 2e 14 4e c3 77 92  b8 df 00 00 5a fe 00 00  |....N.w.....Z...|
00000060  00 00 00 00 aa 55 3c 00  07 00 00 00 00 00 00 00  |.....U<.........|
00000070  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000080  00 00 00 00 00 00 00 00  08 00 00 00 04 00 00 00  |................|
00000090  11 40 70 eb 02 14 d3 11  8e 77 00 a0 c9 69 72 3b  |.@p......w...ir;|
000000a0  4d 00 54 00 43 00 00 00  01 00 00 00 aa 55 3c 00  |M.T.C........U<.|
000000b0  07 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000d0  14 00 00 00 02 00 00 00  61 df e4 8b ca 93 d2 11  |........a.......|
000000e0  aa 0d 00 e0 98 03 2b 8c  42 00 6f 00 6f 00 74 00  |......+.B.o.o.t.|
000000f0  4f 00 72 00 64 00 65 00  72 00 00 00 00 00 ff ff  |O.r.d.e.r.......|
00000100  aa 55 3f 00 07 00 00 00  00 00 00 00 00 00 00 00  |.U?.............|
00000110  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000120  00 00 00 00 12 00 00 00  3e 00 00 00 61 df e4 8b  |........>...a...|
00000130  ca 93 d2 11 aa 0d 00 e0  98 03 2b 8c 42 00 6f 00  |..........+.B.o.|

なんかググったら出てきた http://www.linux-kvm.org/downloads/lersek/ovmf-whitepaper-c770f8c.txt とか EDK2とかをみつつパースする。

最初のやつをみると、変数保存してる領域は128KBで、後半の64KBはスペアエリアになっているので実質最初の64KBだけ見ればいいらしい。 64KBのうちVariable storeが56KBでその後ろにEvent logとWorking blockが4KBずつある。

とりあえずSetVariableの実装を探す。

// MdeModulePkg/Universal/Variable/RuntimeDxe/VariableDxe.c

...

EFI_STATUS
EFIAPI
VariableServiceInitialize (
  IN EFI_HANDLE                         ImageHandle,
  IN EFI_SYSTEM_TABLE                   *SystemTable
  )
{
  EFI_STATUS                            Status;
  EFI_EVENT                             ReadyToBootEvent;
  EFI_EVENT                             EndOfDxeEvent;

...

  SystemTable->RuntimeServices->GetVariable         = VariableServiceGetVariable;
  SystemTable->RuntimeServices->GetNextVariableName = VariableServiceGetNextVariableName;
  SystemTable->RuntimeServices->SetVariable         = VariableServiceSetVariable;
  SystemTable->RuntimeServices->QueryVariableInfo   = VariableServiceQueryVariableInfo;

...

}

みたいなのがあったのでVariableServiceSetVariableを探す。

// MdeModulePkg/Universal/Variable/RuntimeDxe/Variable.c

EFI_STATUS
EFIAPI
VariableServiceSetVariable (
  IN CHAR16                  *VariableName,
  IN EFI_GUID                *VendorGuid,
  IN UINT32                  Attributes,
  IN UINTN                   DataSize,
  IN VOID                    *Data
  )
{

...

}

なんかいろいろやっているが、実際に変数を書いているのはUpdateVariableっぽい。

// MdeModulePkg/Universal/Variable/RuntimeDxe/Variable.c

EFI_STATUS
UpdateVariable (
  IN      CHAR16                      *VariableName,
  IN      EFI_GUID                    *VendorGuid,
  IN      VOID                        *Data,
  IN      UINTN                       DataSize,
  IN      UINT32                      Attributes      OPTIONAL,
  IN      UINT32                      KeyIndex        OPTIONAL,
  IN      UINT64                      MonotonicCount  OPTIONAL,
  IN OUT  VARIABLE_POINTER_TRACK      *CacheVariable,
  IN      EFI_TIME                    *TimeStamp      OPTIONAL
  )
{

...

}

こっちもなんかいろいろやっている。 今知りたかったのは変数をどういうフォーマットで書き出しているかというところだけなのでそれらしい構造体を探す。

// MdeModulePkg/Include/Guid/VariableFormat.h

///
/// Variable Store region header.
///
typedef struct {
  ///
  /// Variable store region signature.
  ///
  EFI_GUID  Signature;
  ///
  /// Size of entire variable store,
  /// including size of variable store header but not including the size of FvHeader.
  ///
  UINT32  Size;
  ///
  /// Variable region format state.
  ///
  UINT8   Format;
  ///
  /// Variable region healthy state.
  ///
  UINT8   State;
  UINT16  Reserved;
  UINT32  Reserved1;
} VARIABLE_STORE_HEADER;

///
/// Single Variable Data Header Structure.
///
typedef struct {
  ///
  /// Variable Data Start Flag.
  ///
  UINT16      StartId;
  ///
  /// Variable State defined above.
  ///
  UINT8       State;
  UINT8       Reserved;
  ///
  /// Attributes of variable defined in UEFI specification.
  ///
  UINT32      Attributes;
  ///
  /// Size of variable null-terminated Unicode string name.
  ///
  UINT32      NameSize;
  ///
  /// Size of the variable data without this header.
  ///
  UINT32      DataSize;
  ///
  /// A unique identifier for the vendor that produces and consumes this varaible.
  ///
  EFI_GUID    VendorGuid;
} VARIABLE_HEADER;

VARIABLE_STORE_HEADERがVariable storeのヘッダーで、OVMF_VARS.fdのファイル先頭がこれ。 それぞれの変数のヘッダーがVARIABLE_STORE_HEADER。

ただし、hexdumpしたやつとこの構造体が合わなくて、実際にはAttributesの後ろによくわからん28バイトが入っている。 VARIABLE_STORE_HEADERのほうも実際のファイル先頭と合わなくて、実際にはOvmfPkg/VarStore.fdf.incを参照したほうがよさそう。

VARIABLE_HEADERのStateは最初に変数が書き込まれるときにVAR_ADDED(0x3f)に設定されて、削除されるときにVAR_IN_DELETED_TRANSITION(0xfe)とかVAR_DELETED(0xfd)とかとビットの論理積をとっている。 Stateが0x3fのやつは生きてて0x3cのやつは削除済み。

ということで、雑にパースするやつ。

#!/usr/bin/python3
"""Parse OVMF_VARS.fd."""

import ctypes
import logging


logger = logging.getLogger(__name__)


VARIABLE_DATA = 0x55aa

VAR_IN_DELETED_TRANSITION = 0xfe
VAR_DELETED = 0xfd
VAR_HEADER_VALID_ONLY = 0x7f
VAR_ADDED = 0x3f


class VariableStoreHeader(ctypes.Structure):
    """Variable store header."""

    _pack_ = 1
    _fields_ = [('efi_firmware_volume_header', ctypes.c_ubyte * 16),
                ('filesystem_guid', ctypes.c_ubyte * 16),
                ('fv_length', ctypes.c_ulonglong),
                ('signature', ctypes.c_ubyte * 8),
                ('header_length', ctypes.c_ushort),
                ('checksum', ctypes.c_ushort),
                ('ext_header_offset', ctypes.c_uint),
                ('block_count', ctypes.c_uint),
                ('bytes_per_block', ctypes.c_uint),
                ('block_map_end', ctypes.c_uint * 2),
                ('efi_authenticated_variable_guid', ctypes.c_ubyte * 16),
                ('nv_storage_variable_size', ctypes.c_uint),
                ('formatted', ctypes.c_ubyte),
                ('healthy', ctypes.c_ubyte),
                ('reserved1', ctypes.c_ushort),
                ('reserved2', ctypes.c_uint)]


class VariableHeader(ctypes.Structure):
    """Variable header."""

    _pack_ = 1
    _fields_ = [('start_id', ctypes.c_ushort),
                ('state', ctypes.c_ubyte),
                ('reserved', ctypes.c_ubyte),
                ('attributes', ctypes.c_uint),
                ('padding', ctypes.c_ubyte * 28),
                ('name_size', ctypes.c_uint),
                ('data_size', ctypes.c_uint),
                ('vendor_guid', ctypes.c_byte * 16)]


def parse_header():
    """Parse OVMF_VARS header."""
    sz = ctypes.sizeof(VariableStoreHeader)
    logger.debug('store header size: 0x{:x}'.format(sz))

    with open('OVMF_VARS.fd', 'rb') as f:
        b = f.read(sz)
        header = VariableStoreHeader.from_buffer_copy(b)
        logger.debug(' '.join(map(lambda x: '{:02x}'.format(x),
                                  header.filesystem_guid)))


def parse_variables():
    """Parse variables."""
    sz = ctypes.sizeof(VariableHeader)
    logger.debug('variable header size: 0x{:x}'.format(sz))

    with open('OVMF_VARS.fd', 'rb') as f:
        # skip variable store header
        f.seek(ctypes.sizeof(VariableStoreHeader))

        while True:
            deleted = False

            b = f.read(sz)
            header = VariableHeader.from_buffer_copy(b)

            logger.debug('start id: {:x}'.format(header.start_id))
            logger.debug('state: {:x}'.format(header.state))
            logger.debug('attributes: {:x}'.format(header.attributes))
            logger.debug('name size: {:x}'.format(header.name_size))
            logger.debug('data size: {:x}'.format(header.data_size))

            # end of variables
            if header.start_id != VARIABLE_DATA:
                break

            if header.state != VAR_ADDED:
                deleted = True

            # ???
            if any(map(lambda x: bool(x), header.padding)):
                logger.warning('found nonzero value in padding.')
                logger.warning(' '.join(map(lambda x: '{:02x}'.format(x),
                                            header.padding)))

            b = f.read(header.name_size)
            namelen = header.name_size // 2
            char16str = (ctypes.c_ushort * namelen).from_buffer_copy(b)
            char8str = (ctypes.c_char * namelen)(*char16str)
            name = ctypes.cast(char8str, ctypes.c_char_p).value.decode('ascii')
            if not deleted:
                logger.info('name: {}'.format(name))

            b = f.read(header.data_size)
            if not deleted:
                logger.info(' '.join(map(lambda x: '{:02x}'.format(x), b)))

            # aligned by 4 bytes
            pos = f.tell()
            if pos % 4 != 0:
                f.seek(4 - (pos % 4), 1)


def main():
    """Execute main routine."""
    logger.addHandler(logging.StreamHandler())
    logger.setLevel(logging.INFO)

    parse_header()
    parse_variables()


if __name__ == '__main__':
    main()

おわり。

QMPを使う

いつも忘れるのでQMPの使い方のメモ。

ドキュメントはこのへん。

ソースツリーの中のドキュメントが前はqmp-commands.hxだったけど docs/qmp-commands.txtになって、今はqapi-schema.jsonになっているっぽい。 見つけるのに苦労した。

qemu起動するときに-qmp tcp:localhost:4444,server,nowaitとかオプションつけると ローカルホストの4444ポートで待ち受けるので適当にncとかでつなぐ。

$ qemu-system-x86_64 -enable-kvm -smp 1 -m 1024 \
    -qmp tcp:localhost:4444,server,nowait \
    -cdrom dfly-x86_64-4.8.0_REL.iso
$ nc localhost 4444
{"QMP": {"version": {"qemu": {"micro": 2, "minor": 6, "major": 2}, "package": " (qemu-2.6.2-8.fc24)"}, "capabilities": []}}

-> {"execute": "qmp_capabilities"}
<- {"return": {}}

->の行が入力。<-がQEMUからのレスポンス。

device_addでデバイスの追加、netdev_addでネットワークデバイスの追加とかできる。 drive_addはないっぽい(QEMUのコンソールからはできるんだけど)。

-> {"execute": "netdev_add", "arguments": {"type": "user", "id": "netdev0"}}
<- {"return": {}}
-> {"execute": "device_add", "arguments": {"driver": "e1000", "netdev": "netdev0", "mac": "52:54:00:bb:bb:bb"}}
<- {"return": {}}

screendumpで画面のスクリーンショットとったりsend-keyでキー入力できたりもする。

-> {"execute": "screendump", "arguments": {"filename": "ss.ppm"}}
<- {"return": {}}
-> {"execute": "send-key", "arguments": {"keys": [{"type": "qcode", "data": "a"}]}}
<- {"return": {}}

おわり。

nginxモジュールを作ってみるやつ(その3)

uni.firis.me

前回のつづき。 変数の展開をやってみた。

confのところでscript_compileしておいてハンドラーでscript_runすればいいらしい。

static char *ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
    ngx_http_core_loc_conf_t *clcf =
            ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    ngx_http_echo_loc_conf_t *elcf = conf;
    ngx_uint_t n;

    clcf->handler = ngx_http_echo_handler;
    elcf->text = ((ngx_str_t *)cf->args->elts)[1];

    n = ngx_http_script_variables_count(&elcf->text);
    if (n > 0) {
        ngx_int_t rc;
        ngx_http_script_compile_t sc = { 0 };

        sc.cf = cf;
        sc.source = &elcf->text;
        sc.lengths = &elcf->lengths;
        sc.values = &elcf->values;
        sc.variables = n;
        sc.complete_lengths = 1;
        sc.complete_values = 1;

        rc = ngx_http_script_compile(&sc);
        if (rc != NGX_OK)
            return NGX_CONF_ERROR;
    }

    return NGX_CONF_OK;
}

static ngx_int_t ngx_http_echo_handler(ngx_http_request_t *r) {
    ngx_http_echo_loc_conf_t *elcf =
            ngx_http_get_module_loc_conf(r, ngx_http_echo_module);
    ngx_int_t rc;
    ngx_chain_t *cl, *newline_cl;
    ngx_buf_t *buf, *newline_buf;
    ngx_str_t text, newline_str = ngx_string("\n");

    if (r->method != NGX_HTTP_GET && r->method != NGX_HTTP_HEAD)
        return NGX_HTTP_NOT_ALLOWED;

    if (elcf->lengths == NULL) {
        text = elcf->text;
    } else {
        u_char *compiled = ngx_http_script_run(r, &text, elcf->lengths->elts,
                                               0, elcf->values->elts);
        if (compiled == NULL)
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    buf = ngx_calloc_buf(r->pool);
    if (buf == NULL)
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    buf->pos = text.data;
    buf->last = text.data + text.len;
    buf->memory = 1;

    newline_buf = ngx_calloc_buf(r->pool);
    if (newline_buf == NULL)
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    newline_buf->pos = newline_str.data;
    newline_buf->last = newline_str.data + newline_str.len;
    newline_buf->memory = 1;
    newline_buf->last_buf = 1;

    cl = ngx_alloc_chain_link(r->pool);
    if (cl == NULL)
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    cl->buf = buf;

    newline_cl = ngx_alloc_chain_link(r->pool);
    if (newline_cl == NULL)
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    newline_cl->buf = newline_buf;
    cl->next = newline_cl;

    ngx_str_set(&r->headers_out.content_type, "text/plain");
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = text.len + newline_str.len;

    rc = ngx_http_send_header(r);
    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
        return rc;

    return ngx_http_output_filter(r, cl);
}

おわり。

nginxモジュールを作ってみるやつ(その2)

uni.firis.me

前回のつづき。 ディレクティブに引数があるパターンをやってみる。

$ mkdir echo
$ vim echo/config
$ vim echo/ngx_http_echo_module.c
# echo/config

ngx_addon_name=ngx_http_echo_module
HTTP_MODULES="$HTTP_MODULES ngx_http_echo_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_echo_module.c"
/* echo/ngx_http_echo_module.c */

#include <ngx_core.h>
#include <ngx_http.h>

static void *ngx_http_echo_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_echo_handler(ngx_http_request_t *r);

typedef struct {
    ngx_str_t text;
} ngx_http_echo_loc_conf_t;

static ngx_command_t ngx_http_echo_commands[] = {
    { ngx_string("echo"),
      NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
      ngx_http_echo,
      NGX_HTTP_LOC_CONF_OFFSET,
      0, NULL
    },
    ngx_null_command
};

static ngx_http_module_t ngx_http_echo_module_ctx = {
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    ngx_http_echo_create_loc_conf,
    NULL
};

ngx_module_t ngx_http_echo_module = {
    NGX_MODULE_V1,
    &ngx_http_echo_module_ctx,
    ngx_http_echo_commands,
    NGX_HTTP_MODULE,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NGX_MODULE_V1_PADDING
};

static void *ngx_http_echo_create_loc_conf(ngx_conf_t *cf) {
    ngx_http_echo_loc_conf_t *conf =
            ngx_pcalloc(cf->pool, sizeof(ngx_http_echo_loc_conf_t));
    if (conf == NULL)
        return NULL;
    return conf;
}

static char *ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
    ngx_http_core_loc_conf_t *clcf =
            ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    ngx_http_echo_loc_conf_t *elcf = conf;

    clcf->handler = ngx_http_echo_handler;
    elcf->text = ((ngx_str_t *)cf->args->elts)[1];

    return NGX_CONF_OK;
}

static ngx_int_t ngx_http_echo_handler(ngx_http_request_t *r) {
    ngx_http_echo_loc_conf_t *elcf =
            ngx_http_get_module_loc_conf(r, ngx_http_echo_module);
    ngx_int_t rc;
    ngx_chain_t *cl, *newline_cl;
    ngx_buf_t *buf, *newline_buf;
    ngx_str_t newline_str = ngx_string("\n");

    if (r->method != NGX_HTTP_GET && r->method != NGX_HTTP_HEAD)
        return NGX_HTTP_NOT_ALLOWED;

    buf = ngx_calloc_buf(r->pool);
    if (buf == NULL)
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    buf->pos = elcf->text.data;
    buf->last = elcf->text.data + elcf->text.len;
    buf->memory = 1;

    newline_buf = ngx_calloc_buf(r->pool);
    if (newline_buf == NULL)
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    newline_buf->pos = newline_str.data;
    newline_buf->last = newline_str.data + newline_str.len;
    newline_buf->memory = 1;
    newline_buf->last_buf = 1;

    cl = ngx_alloc_chain_link(r->pool);
    if (cl == NULL)
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    cl->buf = buf;

    newline_cl = ngx_alloc_chain_link(r->pool);
    if (newline_cl == NULL)
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    newline_cl->buf = newline_buf;
    cl->next = newline_cl;

    ngx_str_set(&r->headers_out.content_type, "text/plain");
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = elcf->text.len + newline_str.len;

    rc = ngx_http_send_header(r);
    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
        return rc;

    return ngx_http_output_filter(r, cl);
}

ビルドする。

$ ./configure --add-module=echo
$ make
$ diff -u conf/nginx.conf{.02,}
--- conf/nginx.conf.02  2017-06-08 13:26:23.727008967 +0900
+++ conf/nginx.conf 2017-06-08 13:26:52.974061265 +0900
@@ -49,6 +49,10 @@
             hello;
         }
 
+        location /echo {
+            echo "echo module";
+        }
+
         #error_page  404              /404.html;
 
         # redirect server error pages to the static page /50x.html
$ objs/nginx -p . -c conf/nginx.conf
$ curl http://127.0.0.1:8000/echo
echo module

変数展開したいときはどうやってやるんだろう。

nginxモジュールを作ってみるやつ

まずモジュールなしでビルドして動かすところまで。 特にconfigureのオプションもなしで普通にビルドするだけ。

$ curl -O https://nginx.org/download/nginx-1.13.1.tar.gz
$ tar -xf nginx-1.13.1.tar.gz
$ cd nginx-1.13.1
$ ./configure
$ make

objs/nginxに実行バイナリができる。 インストールはめんどくさいので適当にディレクトリ作ったりしてこのまま動かす。

$ echo hello > html/hello.html
$ mkdir logs
$ diff -u conf/nginx.conf{.00,}
--- conf/nginx.conf.00  2017-06-08 09:37:18.874240606 +0900
+++ conf/nginx.conf 2017-06-08 09:37:36.733276799 +0900
@@ -1,4 +1,4 @@
-
+daemon off;
 #user  nobody;
 worker_processes  1;
 
@@ -33,7 +33,7 @@
     #gzip  on;
 
     server {
-        listen       80;
+        listen       8000;
         server_name  localhost;
 
         #charset koi8-r;
$ objs/nginx -p . -c conf/nginx.conf
$ curl http://127.0.0.1:8000/hello.html
hello

ここまではOK。モジュールを作ってみる。

github.com

これを参考に(というかほぼそのまま)helloするだけのやつ。

$ mkdir hello
$ vim hello/config
$ vim hello/ngx_http_hello_module.c
# hello/config

ngx_addon_name=ngx_http_hello_module
HTTP_MODULES="$HTTP_MODULES ngx_http_hello_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hello_module.c"
/* hello/ngx_http_hello_module.c */

#include <ngx_core.h>
#include <ngx_http.h>

static char *ngx_http_hello(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r);

static ngx_command_t ngx_http_hello_commands[] = {
    { ngx_string("hello"),
      NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
      ngx_http_hello,
      0, 0, NULL
    },
    ngx_null_command
};

static ngx_http_module_t ngx_http_hello_module_ctx = { NULL };

ngx_module_t ngx_http_hello_module = {
    NGX_MODULE_V1,
    &ngx_http_hello_module_ctx,
    ngx_http_hello_commands,
    NGX_HTTP_MODULE,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NGX_MODULE_V1_PADDING
};

static char *ngx_http_hello(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
    ngx_http_core_loc_conf_t *clcf =
            ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_hello_handler;

    return NGX_CONF_OK;
}

static ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r) {
    ngx_int_t rc;
    ngx_chain_t out;
    ngx_buf_t *buf;
    ngx_str_t body = ngx_string("hello module\n");

    if (r->method != NGX_HTTP_GET && r->method != NGX_HTTP_HEAD)
        return NGX_HTTP_NOT_ALLOWED;

    buf = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if (buf == NULL)
        return NGX_HTTP_INTERNAL_SERVER_ERROR;

    buf->pos = body.data;
    buf->last = buf->pos + body.len;
    buf->memory = 1;
    buf->last_buf = 1;
    out.buf = buf;
    out.next = NULL;

    ngx_str_set(&r->headers_out.content_type, "text/plain");
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = body.len;

    rc = ngx_http_send_header(r);
    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
        return rc;

    return ngx_http_output_filter(r, &out);
}
$ ./configure --add-module=hello
$ make
$ diff -u conf/nginx.conf{.01,}
--- conf/nginx.conf.01  2017-06-08 10:31:04.078426749 +0900
+++ conf/nginx.conf 2017-06-08 10:31:46.636507922 +0900
@@ -45,6 +45,10 @@
             index  index.html index.htm;
         }
 
+        location /hello {
+            hello;
+        }
+
         #error_page  404              /404.html;
 
         # redirect server error pages to the static page /50x.html
$ objs/nginx -p . -c conf/nginx.conf
$ curl http://127.0.0.1:8000/hello
hello module

おわり。

はじめてのapacheモジュール

apacheのモジュール作ってみたのでメモ。

環境はCentOS 7.3。

# cat /etc/centos-release
CentOS Linux release 7.3.1611 (Core) 

必要なパッケージを入れる。

# yum install '@development tools' epel-release
# yum install httpd httpd-devel libapreq2 libapreq2-devel

思い出しながら書いてるのでなんか足りないかも。httpd-develに入ってるapxsというコマンドがいろいろ便利にやってくれる。

まず、"hello"を返すだけのやつ。apxs -gでテンプレート作ってくれるので、ほぼそのまま使える。

# apxs -g -n hello
Creating [DIR]  hello
Creating [FILE] hello/Makefile
Creating [FILE] hello/modules.mk
Creating [FILE] hello/mod_hello.c
Creating [FILE] hello/.deps
# ls hello/
Makefile  mod_hello.c  modules.mk

ここで作られるmod_hello.cの先頭に便利コメントがあるので読んでおくとよさげ。

hello_handler()だけちょっと直せば(putsのメッセージしか変えてないけども)おわり。

static int hello_handler(request_rec *r)
{
    if (strcmp(r->handler, "hello")) {
        return DECLINED;
    }
    r->content_type = "text/html";

    if (!r->header_only)
        ap_rputs("hello\n", r);
    return OK;
}

モジュールのビルドとhttpdのリロード。リロードする前にhttpdの設定ファイルを追加する。モジュールのビルドとインストールまではapxsコマンドがやってくれる便利。

# apxs -c -i mod_hello.c
# cat /etc/httpd/conf.d/hello.conf
LoadModule hello_module modules/mod_hello.so
<Location /hello>
    SetHandler hello
</Location>
# systemctl reload httpd

動いてるか確認する。

# curl http://127.0.0.1/hello
hello

大丈夫そう。

次、POSTで受け取ったデータをそのまま返すやつ。echoサーバー的な。

テンプレート作るのとかはほぼ同じコマンドでできる。ビルドするときにインクルードパスを追加するところだけ注意。

# apxs -g -n echo

ハンドラーを作る。

static int echo_handler(request_rec *r)
{
    if (strcmp(r->handler, "echo")) {
        return DECLINED;
    }
    r->content_type = "text/html";

    if (!r->header_only)
        return DECLINED;

    apreq_handle_t *apreq = apreq_handle_apache2(r);
    const apr_table_t *param;
    apreq_body(apreq, &param);

    ap_rprintf(r, "%s\n", apreq_params_as_string(r->pool, param, NULL, APREQ_JOIN_AS_IS));

    return OK;

ビルドとインストール。

# apxs -I /usr/include/apreq2 -c -i mod_echo.c

httpdの設定ファイル追加して、リロードしたら、動作確認。

# curl -d 'text=hellohello' http://127.0.0.1/echo
"hellohello"

おわり。

nfcpyでPASMOを読む

PASMOの履歴管理したいなーとおもったのでやってみる。

pythonでnfcpy使うのが一番楽そうなのかな。

Getting started — nfcpy 0.13.0 documentationを見るとまだpython3.xには未対応っぽい。

使ったNFCリーダーはAmazon | ソニー SONY 非接触ICカードリーダー/ライター PaSoRi RC-S380 | プリペイド電話カード オンライン通販

#!/usr/bin/python

import nfc

def on_connect(tag):
    print '\n'.join(tag.dump())

with nfc.ContactlessFrontend('usb') as clf:
    clf.connect(rdwr={'on-connect': on_connect})

これでNFCのタグをダンプできる。これを実行してPASMOをかざすとダンプできた。

デバイスにアクセスするのにroot権限必要かも(あとで調べる)。

$ sudo ./pasmo.py 
System 0003 (Suica)
Area 0000--FFFE
  Area 0040--07FF
    Random Service 1: write with key & read with key (0x0048 0x004A)
    Random Service 2: write with key & read w/o key (0x0088 0x008B)
...
      Random Service 96: write with key & read with key (0x1808 0x180A)
      Random Service 97: write with key & read with key (0x1848 0x184A)
      Random Service 98: write with key & read with key (0x1888 0x188A)

NFCを読めているらしい。PASMOのデータは

github.com

ここをみるとよさげ。