##
しょうもないバグでも
えらい目にあうよ🥺
という話。
[ゆるバグ](https://yurubug.connpass.com/event/174479/)(2020/04/29) @furandon_pig
#### これは一般の人が体験した
実話を再現したものです
#### ただし、登場する地名、団体名、
個人名などは、架空もしくは仮名であり
事実とは一切関係ありません🙄
##
消
き
える
設定
せってい
ファイル
体験者
@furandon_pig(仮名)
## 背景 * とあるアプライアンス製品。 * 設定用CLIを提供している。 * `show ... config` なコマンドで値を設定。 * `save ... config` なコマンドで値を保存。 * 設定値は内部のconfigファイルで保存される。
## 発生していた怪奇現象 * 不意にマシンがリブートする。 * プロセス終了はwatchdog-timerで検知。 * リブート自体は想定通りの動作。 * が、特定のプロセスの異常終了理由が不明。 * 加えて、問題再現のタイミングがまちまち。 * 再現まで5分~2日という時間幅の広さ。 * show/saveを交互に実行していると再現する。
## ソースコードの例(1/2) ```c int show_any_config(void) { FILE *fp = fopen("config.txt", "r"); if (fp) { /* configを読みだして表示する */ fclose(fp); } } int save_any_config(void) { FILE *fp = fopen("config.txt", "w"); if (fp) { /* ファイルにconfigを書き込む */ fclose(fp); } } ```
## ソースコードの例(2/2) ```c typedef struct config { char *value; } config_t; int show_any_config(void) { FILE *fp = fopen("config.txt", "r"); config_t config; if (fp) { fread(&config, sizeof(config_t), 1, fp); puts(config.value); // ここで落ちている。 fclose(fp); } } ```
## ファイルサイズが...ZERO...😲 ``` $ ls -l ... -rw-r--r-- 1 user user 0 1 21 18:37 config.txt ```
## configが空になっている...?🤔
# 🤨 ```c int save_any_config(void) { FILE *fp = fopen("config.txt", "w"); ``` ``` NAME fopen, fdopen, freopen, fmemopen -- stream open functions ... FILE * fopen(const char * restrict path, const char * restrict mode); ... “w” Open for writing. The stream is position at the beginning of the file. Create the file if it does not exist. ```
# 😲 Open for writing.
The stream is position at the beginning of the file.
Create the file if it does not exist.
* `fopen(..., "w")` なサンプル。 ```c #include
int main(void) { FILE *fp = fopen("tmp.txt", "w"); if (fp) fclose(fp); return 0; } ``` * 呼ばれているシステムコール。 ```sh $ ktruss ./sample ... 21906 1 sample open("tmp.txt", 0x601, 0x1b6) = 3 ```
* `open(2)` の引数で指定されている値。 ```sh $ irb irb(main):001:0> printf("%016b\n", 0x601) 0000011000000001 ... irb(main):002:0> printf("%016b\n", 0x1b6) 0000000110110110 ``` * `0x0001` は `O_WRONLY` のようだ。 ```sh $ grep -B2 -A2 O_WRONLY /usr/include/sys/fcntl.h /* open-only flags */ #define O_RDONLY 0x0000 /* open for reading only */ #define O_WRONLY 0x0001 /* open for writing only */ #define O_RDWR 0x0002 /* open for reading and writing */ #define O_ACCMODE 0x0003 /* mask for above modes */ ```
* `0b011000000000` の値を調べてみる。 ```sh $ irb irb(main):001:0> printf("0x%04x\n", 0b010000000000) 0x0400 ... irb(main):002:0> printf("0x%04x\n", 0b001000000000) 0x0200 ``` * `0x400` と `0x200` に対応するマクロ定数。 ```sh $ egrep '0x0400|0x0200' /usr/include/sys/fcntl.h #define O_CREAT 0x0200 /* create if nonexistent */ #define O_TRUNC 0x0400 /* truncate to zero length */ ```
* `open(2)` における `O_TRUNC` の説明😯 If O_TRUNC is specified and the file exists,
the file is truncated to zero length.
## つまり...🤨 * saveの実行時、ファイルが一瞬空になる。 * そのタイミングでのshow実行。 * 空ファイルが読まれ、NULL参照になる。 *
ファイルの競合状態に起因する問題だった。
不具合の解消方法 * ファイルロックしてから `fopen(3)` する😐 * この方法で不具合は無事解消した。
まとめ(不具合調査で得られた知見) * 分かってしまえば(原因は)単純なものだった。 * (不具合の特定に一か月近く費やした...🥺) * 再現タイミングがまちまちな不具合。 *
競合状態に起因する不具合の可能性が高い。
ご清聴ありがとうございました😃