BBR-4MG の BusyBox を差し替える

いまだに現行品である BBR-4MG ですが、先日パソコンショップにて新品が 3,480 円で売られているのを見てニヤリとしてしまいました。世間では DD-WRT や OpenWRT などが流行っているようですが BBR-4MG のように破格 (105 円、もうすぐ 108 円になりますが) で手に入り、しかも自分の手で Linux のカーネルやユーザランドをいじくることができるルータというのは貴重だと思います。

前回、BBR-4MG で Linux を動かすところまではできましたが、びんずめ堂さんの Linux では BusyBox のバージョンが古いため NTP など一部のコマンドが実装されておらず不便を感じます。そこで BusyBox を最新版に差し替えてやろうと思います。当然、ツールチェーンが古いため簡単にはコンパイルさせてくれません。いばらの道とは分かっていながら、ここはひとつ BusyBox のソースを書き換えて無理やりコンパイルしてみようと思います。

まずは以下の URL から BusyBox のソースを頂いてきます。

http://busybox.net/downloads/

現時点での最新安定版 busybox-1.22.1.tar.bz2 を使用します。

cd
wget http://busybox.net/downloads/busybox-1.22.1.tar.bz2
tar jxf busybox-1.22.1.tar.bz2
mv busybox-1.22.1 EdiLinux/AP/
cd EdiLinux/AP/busybox-1.22.1/

まずは標準の設定を読み込みます。

make defconfig

次に GUI で設定を変更します。

make menuconfig

標準の設定から不要なアプレットを削除し、必要に応じて設定を変更します。今回はなるべく標準に近い状態を維持しつつ、コンパイルエラーが発生し、なおかつ修正が困難なアプレットは除外していきます。そもそも BBR-4MG は空き領域が少ないため、最終的には最低限必要なアプレットのみにするつもりです。

  • Busybox Settings
    • General Configuration
      • Use the devpts filesystem for Unix98 PTYs = OFF
    • Build Options
      • Build with Large File Support (for accessing files > 2 GB) = OFF
        * uClibc の Large File Support に合わせる
      • Cross Compiler prefix = mipsel-uclibc-
        * コンパイラの接頭辞を指定する
    • Busybox Library Tuning
      • Support infiniband HW = OFF
        * これが ON だとコンパイル時にエラーが出る
  • Coreutils
    • id = OFF
    • touch
      • Add support for -h = OFF
    • stat = OFF
  • Editors
    • vi
      • Allow vi to display 8-bit chars (otherwise shows dots) = ON
        * vi で日本語を表示させる
  • Linux System Utilities
    • blockdev = OFF
    • Support over 4GB disks = OFF
      * uClibc の Large File Support に合わせる
    • fsck_minix = OFF
    • mkfs_minix = OFF
    • setarch = OFF
  • Miscellaneous Utilities
    • nandwrite = OFF
    • nanddump = OFF
    • ubiattach = OFF
    • ubidetach = OFF
    • ubimkvol = OFF
    • ubirmvol = OFF
    • ubirsvol = OFF
    • ubiupdatevol = OFF
    • ionice = OFF
  • Networking Utilities
    • nbd-client = OFF
    • brctl = OFF
      * カーネルがサポートしていないと動作しない
    • httpd
      • Use sendfile system call = OFF
        * これが ON だと動作しない
    • ifupdown
      • Use ip applet
        • Use busybox ip applet = OFF
    • ip
      • ip link = OFF

さて、make を打ってみます。

make
mipsel-linux-gcc: unrecognized option `-static-libgcc'
In file included from include/busybox.h:8,
                 from applets/applets.c:9:
include/libbb.h:1682: array size missing in `masks'
scripts/Makefile.build:197: recipe for target 'applets/applets.o' failed
make[1]: *** [applets/applets.o] Error 1
Makefile:372: recipe for target 'applets_dir' failed
make: *** [applets_dir] Error 2

「-static-libgcc」とかいうオプションがないと怒られたので消します。

vi Makefile.flags
# CFLAGS += $(call cc-option,-funsigned-char -static-libgcc,)
CFLAGS += $(call cc-option,-funsigned-char,)

さらに include/libbb.h の 1682 行目、masks 配列のサイズが指定されてないと怒られたので過去のソースを参考に [0] という指定で回避します。

vi include/libbb.h
typedef struct masks_labels_t {
        const char *labels;
        const int masks[0];
} masks_labels_t;

make してみます。

editors/awk.c:200: array size missing in `nv'
scripts/Makefile.build:197: recipe for target 'editors/awk.o' failed
make[1]: *** [editors/awk.o] Error 1
Makefile:741: recipe for target 'editors' failed
make: *** [editors] Error 2

editors/awk.c の 200 行目、これも先ほどと同じエラーなので配列のサイズを指定して回避します。

vi editors/awk.c
typedef struct nvblock_s {
        int size;
        var *pos;
        struct nvblock_s *prev;
        struct nvblock_s *next;
        var nv[0];
} nvblock;

これでどうでしょうか、make してみると udhcp 関連で大量にエラーが発生しました。ここからは本格的にソースを修正していきます。

vi networking/udhcp/dhcpc.h
#ifndef UDHCP_DHCPC_H
#define UDHCP_DHCPC_H 1

/* ここから */
#include 
#define PACKET_AUXDATA 8
/* ここまで */

PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
        uint16_t last_secs;
} FIX_ALIASING;

/* ここから */
struct tpacket_auxdata {
        __u32           tp_status;
        __u32           tp_len;
        __u32           tp_snaplen;
        __u16           tp_mac;
        __u16           tp_net;
        __u16           tp_vlan_tci;
        __u16           tp_padding;
};
/* ここまで */

/* server_config sits in 1st half of bb_common_bufsiz1 */
#define client_config (*(struct client_config_t*)(&bb_common_bufsiz1[COMMON_BUFSIZE / 2]))

次に files.c の修正です。66 行目、初期化の要素が定数ではないと怒られました。#define と static の初期化順序の関係でしょうか?若干無理やりな気もしますが static で宣言されている部分を普通の宣言に変更してみます。

vi networking/udhcp/files.c
	const char *def;
};

/* ここから */
// static const struct config_keyword keywords[] = {
// 	/* keyword        handler           variable address               default */
// 	{"start"        , udhcp_str2nip   , &server_config.start_ip     , "192.168.0.20"},
// 	{"end"          , udhcp_str2nip   , &server_config.end_ip       , "192.168.0.254"},
// 	{"interface"    , read_str        , &server_config.interface    , "eth0"},
// 	/* Avoid "max_leases value not sane" warning by setting default
// 	 * to default_end_ip - default_start_ip + 1: */
// 	{"max_leases"   , read_u32        , &server_config.max_leases   , "235"},
// 	{"auto_time"    , read_u32        , &server_config.auto_time    , "7200"},
// 	{"decline_time" , read_u32        , &server_config.decline_time , "3600"},
// 	{"conflict_time", read_u32        , &server_config.conflict_time, "3600"},
// 	{"offer_time"   , read_u32        , &server_config.offer_time   , "60"},
// 	{"min_lease"    , read_u32        , &server_config.min_lease_sec, "60"},
// 	{"lease_file"   , read_str        , &server_config.lease_file   , LEASES_FILE},
// 	{"pidfile"      , read_str        , &server_config.pidfile      , "/var/run/udhcpd.pid"},
// 	{"siaddr"       , udhcp_str2nip   , &server_config.siaddr_nip   , "0.0.0.0"},
// 	/* keywords with no defaults must be last! */
// 	{"option"       , udhcp_str2optset, &server_config.options      , ""},
// 	{"opt"          , udhcp_str2optset, &server_config.options      , ""},
// 	{"notify_file"  , read_str        , &server_config.notify_file  , NULL},
// 	{"sname"        , read_str        , &server_config.sname        , NULL},
// 	{"boot_file"    , read_str        , &server_config.boot_file    , NULL},
// 	{"static_lease" , read_staticlease, &server_config.static_leases, ""},
// };
// enum { KWS_WITH_DEFAULTS = ARRAY_SIZE(keywords) - 6 };
/* ここまで */

void FAST_FUNC read_config(const char *file)
{
	/* ここから */
	struct config_keyword keywords[] = {
		/* keyword        handler           variable address               default */
		{"start"        , udhcp_str2nip   , &server_config.start_ip     , "192.168.0.20"},
		{"end"          , udhcp_str2nip   , &server_config.end_ip       , "192.168.0.254"},
		{"interface"    , read_str        , &server_config.interface    , "eth0"},
		/* Avoid "max_leases value not sane" warning by setting default
		 * to default_end_ip - default_start_ip + 1: */
		{"max_leases"   , read_u32        , &server_config.max_leases   , "235"},
		{"auto_time"    , read_u32        , &server_config.auto_time    , "7200"},
		{"decline_time" , read_u32        , &server_config.decline_time , "3600"},
		{"conflict_time", read_u32        , &server_config.conflict_time, "3600"},
		{"offer_time"   , read_u32        , &server_config.offer_time   , "60"},
		{"min_lease"    , read_u32        , &server_config.min_lease_sec, "60"},
		{"lease_file"   , read_str        , &server_config.lease_file   , LEASES_FILE},
		{"pidfile"      , read_str        , &server_config.pidfile      , "/var/run/udhcpd.pid"},
		{"siaddr"       , udhcp_str2nip   , &server_config.siaddr_nip   , "0.0.0.0"},
		/* keywords with no defaults must be last! */
		{"option"       , udhcp_str2optset, &server_config.options      , ""},
		{"opt"          , udhcp_str2optset, &server_config.options      , ""},
		{"notify_file"  , read_str        , &server_config.notify_file  , NULL},
		{"sname"        , read_str        , &server_config.sname        , NULL},
		{"boot_file"    , read_str        , &server_config.boot_file    , NULL},
		{"static_lease" , read_staticlease, &server_config.static_leases, ""},
	};
	enum { KWS_WITH_DEFAULTS = ARRAY_SIZE(keywords) - 6 };
	/* ここまで */
	parser_t *parser;
	const struct config_keyword *k;
	unsigned i;

今度は ash.c の 5193 行目でエラーが発生しました。配列のサイズを指定して回避します。

struct redirtab {
        struct redirtab *next;
        int nullredirs;
        int pair_count;
        struct two_fd_t two_fd[0];
};

struct statfs の宣言が複数あると怒られたので確認してみると libbb.h 内にある # include で既に statfs が宣言されていますが mkfs_ext2.c 内の #include でも再宣言されているようです。libbb.h はオプションで分岐できるようなので HAVE_SYS_STATFS_H を無効にします。

vi include/platform.h
// #define HAVE_SYS_STATFS_H 1

最後に romfs.c の 38 行目で配列のサイズを指定します。

vi util-linux/volume_id/romfs.c
struct romfs_super {
        uint8_t magic[8];
        uint32_t size;
        uint32_t checksum;
        uint8_t name[0];
} PACKED;

ようやく make が通りました!

make install

早速 BBR-4MG に転送したいところですが、ファイルサイズが 1.6 MB ほどあり BBR-4MG の空き領域を考えると厳しそうです。びんずめ堂さんの Linux に含まれている BusyBox を参考に不要なアプレットを削ります。BusyBox のバージョンは 1.00 で使用できる機能は下記のようになっていました。

BusyBox v1.00 (2005.10.10-01:34+0000) multi-call binary

Usage: busybox [function] [arguments]...
   or: [function] [arguments]...

        BusyBox is a multi-call binary that combines many common Unix
        utilities into a single executable.  Most people will create a
        link to busybox for each function they wish to use and BusyBox
        will act like whatever it was invoked as!

Currently defined functions:
        [, [[, ash, busybox, cat, chmod, chown, cp, crond, crontab, cut, date, dd, df, du, echo, env, expr, false, gzip, head, httpd, id, ifconfig, init,
        insmod, kill, killall, linuxrc, ln, login, ls, lsmod, mkdir, mke2fs, mkfs.ext2, mkfs.ext3, mknod, mount, mv, netstat, nslookup, passwd, ping, printf,
        ps, pwd, reboot, rm, rmdir, rmmod, route, sh, sleep, sync, tail, tar, tee, telnet, telnetd, test, tftp, touch, tr, traceroute, true, tty, udhcpc,
        udhcpd, umount, uname, uniq, uptime, usleep, vi, wc, wget, which, whoami, yes

make menuconfig でこれらのアプレットのみを含めるように設定を変更します。地味な作業になりますが BusyBox に含まれている機能が色々と分かるので割と楽しむことができました。さあ、BBR-4MG に転送して BusyBox を動かしてみましょう!

chmod +x busybox
./busybox
./busybox: can't load library 'libm.so.6'

動きませんでした。

一部のモジュールが libm.so を必要としているようですが、びんずめ堂さんの Linux には含まれていません。そもそも uClibc が古すぎて libm.so が存在しません。かといって最新版の uClibc を古い環境でコンパイルするなんて想像するだけでぞっとします。仕方がないので libm.so を使っていそうな箇所を排除してみましょう。

make menuconfig で怪しい箇所を OFF にしてみます。

  • Editors
    • awk
      • Enable math functions (requires libm) = OFF
  • Miscellaneous Utilities
    • dc
      • Enable power and exp functions (requires libm) = OFF
  • Shells
    • hush = OFF

GUI から設定できる部分はこれだけのようですが、まだ libm.so がリンクされているようです。ここまで来てしまった以上もう後戻りはできません。気合を入れてソースを読みます。appletlib.c の中で malloc.h をインクルードしている部分が怪しいです。これは mallopt が呼ばれる際に必要になるようですが、そもそも M_TRIM_THRESHOLD か M_MMAP_THRESHOLD のオプションが無効の場合は mallopt 自体、呼び出されませんのでコメントにしてみましょう。

vi libbb/appletlib.c
#if !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \
    || defined(__APPLE__) \
    )
// # include  /* for mallopt */
#endif

これで libm.so に依存しないバイナリが出来上がったはずです。再度、BBR-4MG に転送します。

BusyBox v1.22.1 (2014-03-09 21:09:26 JST) multi-call binary.
BusyBox is copyrighted by many authors between 1998-2012.
Licensed under GPLv2. See source distribution for detailed
copyright notices.

Usage: busybox [function [arguments]...]
   or: busybox --list[-full]
   or: busybox --install [-s] [DIR]
   or: function [arguments]...

        BusyBox is a multi-call binary that combines many common Unix
        utilities into a single executable.  Most people will create a
        link to busybox for each function they wish to use and BusyBox
        will act like whatever it was invoked as.

Currently defined functions:
        ash, cat, chmod, chown, cp, crond, crontab, cut, date, dd, df, dhcprelay, dmesg, du, dumpleases, echo, egrep, env, expr, false, fgrep, grep, gzip, halt,
        head, httpd, ifconfig, init, kill, killall, killall5, linuxrc, ln, login, ls, mkdir, mke2fs, mkfs.ext2, mknod, mount, mv, netstat, nslookup, ntpd, passwd,
        ping, ping6, poweroff, printf, ps, pwd, reboot, rm, rmdir, route, sh, sleep, sync, tail, tar, tee, telnet, telnetd, top, touch, tr, traceroute,
        traceroute6, true, tty, udhcpc, udhcpd, umount, uname, uniq, uptime, usleep, vi, wc, wget, which, whoami, yes

動きました!

このまま ramdisk.bz2 の BusyBox のバイナリを差し替えたいところではありますが、そう簡単にはいきません。BBR-4MG の電源を入れた瞬間に "can't open /dev/tty2: No such device" というエラーが多発して文鎮化してしまいました。

調べてみたところ BusyBox は inittab が存在しない場合に勝手に独自の設定を使用するようで、その設定が tty2 - tty4 のデバイスを見に行くようになっていました。inittab を作成してやることで回避できそうです。ここからは ramdisk.bz2 を展開してマウントし、その中身を編集します。

vi etc/inittab
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
#tty2::askfirst:-/bin/sh
#tty3::askfirst:-/bin/sh
#tty4::askfirst:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r

これで BBR-4MG に ramdisk.bz2 を転送すると動くようになりました。

最新版の BusyBox になったことで udhcpc が新しくなったためバックグラウンド実行の方法が変わったようです。"&" によるバックグランド実行をやめて "-b" というオプションを指定してやる必要があるようです。

vi etc/init.d/rcS
udhcpc -b -i eth0
udhcpc -b -i eth1

ついでに ntpd も追記しておきましょう。

ntpd -p ntp1.jst.mfeed.ad.jp -p ntp2.jst.mfeed.ad.jp -p ntp3.jst.mfeed.ad.jp

ramdisk の BusyBox の差し替えは /bin の下にある dnrd と iptables をどこかに退避させておいて /bin, /sbin, /usr/bin, /usr/sbin をごっそり _install ディレクトリのものと差し替えたあと dnrd と iptables を戻しました。linuxrc は busybox へのシンボリックリンクなのでコピーは不要です。あと ramdisk を bz2 に圧縮する際にきちんと umount してから圧縮しないと BBR-4MG に転送した後に「空き領域がありません」というエラーが出て mkdir できない現象に悩まされて時間を浪費します。

ようやく BBR-4MG の BusyBox が最新版になりました。最新版では多くの機能が使用できるようになっていますので必要なアプレットを BusyBox に追加して BBR-4MG をもっと便利に活用してみましょう!

なんだろう、すごく無意味ですこの遊び!

今回修正したソースは下記の URL に置いておきます。

http://www.haijin-boys.com/uploads/busybox_mips-1.22.1.tar.bz2

以上、105 円で楽しめる BBR-4MG の紹介でした。ちなみに簡単に Linux 化できるのは初期型 (CPU : ADM5120P) のみで BBR-4MG V2 という型番の場合はカニさんチップに変更されているので厳しそうです。最近流通しているのは V2 かもしれませんので新品を購入される際はご注意ください。

コメントを残す