2007年01月
ネットワーク経由で La Fonera にパッケージをインストール
La Fonera (FON ソーシャル ルータ) のベースとなっている OpenWrt は、 パッケージ管理システムとして ipkg を利用している。 La Fonera 用の ipkg feed を 作ってみた。 この feed を使うには、 以下のように /etc/ipkg.conf に 「src gcd http://www.gcd.org/fonera」 を追加し、 「ipkg update」 を実行する。
root@OpenWrt:/# echo "src gcd http://www.gcd.org/fonera" >> /etc/ipkg.conf root@OpenWrt:/# ipkg update Downloading http://www.gcd.org/fonera/Packages Updated list of available packages in /usr/lib/ipkg/lists/gcd Done.
すると、feed のリスト (パッケージ名の一覧) が /usr/lib/ipkg/lists/gcd に保存される。 あとは、「ipkg install パッケージ名」などと実行するだけで、 パッケージをネットワーク経由で La Fonera にインストールできる。
例えば stone をインストールするには、 次のように「ipkg install stone」と実行するだけでよい。
root@OpenWrt:~# ipkg install stone Installing stone (2.3c-2.2.4.12) to root... Downloading http://www.gcd.org/fonera/stone_2.3c-2.2.4.12_mips.ipk Installing libopenssl (0.9.8d-1) to root... Downloading http://www.gcd.org/fonera/libopenssl_0.9.8d-1_mips.ipk Installing zlib (1.2.3-3) to root... Downloading http://www.gcd.org/fonera/zlib_1.2.3-3_mips.ipk Installing libpthread (0.9.28-8) to root... Downloading http://www.gcd.org/fonera/libpthread_0.9.28-8_mips.ipk Configuring libopenssl Configuring libpthread Configuring stone Configuring zlib Done.
stone の実行に必要な、libopenssl, zlib, libpthread パッケージの インストールも自動的に行なわれている。 このようにパッケージの依存関係も管理してくれるので便利なのであるが、 残念ながら現行の La Fonera 上の ipkg にはバグがある。 インストールしたパッケージをアンインストールしようとして、
ipkg remove stone
などと実行してはいけない。 /usr/bin/stone だけでなく、 /usr/bin ディレクトリまでもが削除されてしまう。 なぜこんなバグがあるかというと、 mini_fo の rmdir の実装にバグがあるからだ。 すなわち、空でないディレクトリに対して rmdir を行なっても、 ディレクトリが削除されてしまうことがある。 試しに、あまり重要そうでないディレクトリに対して rmdir してみる:
root@OpenWrt:/www/images# ls -a table . bottom-right.gif logo.gif top-left.gif .. bottom.gif right.gif top-right.gif bottom-left.gif left.gif sidebar.gif top.gif root@OpenWrt:/www/images# rmdir table root@OpenWrt:/www/images# ls table ls: table: No such file or directory
La Fonera のファイルシステムは mini_fo (mini fan-out) を使っている。 つまり squashfs を read only でマウントした「上」に、 jffs を重ねてマウントしている。 前述の例で言えば、 /www/images/table ディレクトリは squashfs 上にあり、 jffs 上には /www/images/table ディレクトリは存在しない。 「rmdir table」などファイルシステムに対する変更は、 「上」に重ねられた jffs に対して行なわれるが、 ディレクトリが空か否かの判断まで、 「上」のファイルシステムに対してのみ行なっているのだろう、 jffs 上では /www/images/table ディレクトリの中にはファイルが存在しない (ディレクトリが存在しないので当然だが) ため、 rmdir がエラーにならずにディレクトリの削除が行なわれてしまう。
La Fonera の ipkg (busybox に含まれている) のソース ipkg_remove.c を 確認してみると、
for (iter = installed_dirs.head; iter; iter = iter->next) {
file_name = iter->data;
if (rmdir(file_name) == 0) {
ipkg_message(conf, IPKG_INFO, " deleting %s\n", file_name);
removed_a_dir = 1;
str_list_remove(&installed_dirs, &iter);
}
}
などとなっている。 つまりディレクトリが空かどうかは判断せず、 いきなり rmdir してみて成功すればヨシとしている。 このため、「ipkg remove パッケージ名」を実行すると、 空でないディレクトリまで削除してしまうのだろう。
空でないディレクトリに対して rmdir を実行したら ENOTEMPTY を返すのがスジであるので、 mini_fo 側を修正すべきだとは思うのだが、 とりあえず sh スクリプト版の ipkg を拾ってきて、 rmdir するまえに中身の確認をするように変更し、 /usr/bin/ipkg を この修正済み sh スクリプトで置き換えてみた。
root@OpenWrt:~# wget http://www.gcd.org/fonera/ipkg-0.9-1.32.sh Connecting to www.gcd.org[60.32.85.216]:80 ipkg-0.9-1.32.sh 100% |*****************************| 27925 00:00 ETA root@OpenWrt:~# chmod 755 ipkg-0.9-1.32.sh root@OpenWrt:~# mv ipkg-0.9-1.32.sh /usr/bin/ipkg
などと置き換えるとよいだろう。 これで安全にパッケージをアンインストールできるようになる。
root@OpenWrt:~# ipkg remove stone ipkg_remove: Removing stone... ipkg_remove: Warning: Not removing the following directories since they are not empty: /usr /usr/bin Done.
/usr および /usr/bin ディレクトリが空でなかったので削除できなかった旨が、 表示されている。
La Fonera の自動アップデートのまとめ
La Fonera の自動アップデートは、 cron から呼び出される /bin/thinclient プログラムによって行なわれている。 このアップデートは ipkg とは独立した形で行なわれているので、 ipkg でパッケージのインストールを行なう場合は、 この自動アップデートで何が行なわれているか把握しておく必要がある。 確認できたアップデートについてまとめてみた。
- 0.7.0 rev 2 -> 0.7.0 rev 3 (upgrade.fon)
- 修正: /usr/lib/webif/validate.awk
- 0.7.0 rev 3 -> 0.7.0 rev 4 (upgrade.fon)
-
[WiFi] 暗号方式のデフォルトを WPA+TKIP に変更
修正: /etc/init.d/rcS /sbin/ifup /www/cgi-bin/webif/*.sh
変更: /lib/modules/2.4.32/wlan.o /lib/modules/2.4.32/ath_ahb.o
追加: /etc/hotplug.d/iface/10-ppp_hack - 0.7.0 rev 4 -> 0.7.1 rev 1 (upgrade.fon) 2006-11-21 0.7.1 rev 1
-
[Web interface] 多言語サポート
[Web interface] ポートフォワード機能
[NTP] ntpclient による時刻同期
修正: /bin/thinclient /etc/functions.sh /usr/lib/webif/*
変更: /usr/bin/webif-page
追加: /etc/config/ntpservers /etc/config/openports /etc/config/webif /etc/init.d/N45ntpclient /usr/lib/webif/lang /usr/sbin/adjtimex /usr/sbin/ntpclient /www/cgi-bin/webif/adv_pf.sh /www/cgi-bin/webif/language.sh - 0.7.1 rev 1 -> 0.7.1 rev 2 (upgrade.fon) 2007-01-04 01:00 JST
-
[Web interface] 任意のコマンドを実行させられてしまう脆弱性を修正
[NTP] crontab に複数の ntpclient 呼び出しが登録されてしまうバグを修正
修正: /etc/init.d/N45ntpclient /usr/lib/webif/validate.awk /www/cgi-bin/webif/*.sh
変更: /usr/bin/haserl
特に重要なのが、0.7.1 rev 2 で行なわれた、 Web インタフェースにおける脆弱性の修正だろう。 POST した入力データのチェックが厳密になった。 La Fonera に ssh でログインする最も手軽な方法が、 この脆弱性を利用する方法だっただけに、 0.7.1 rev 2 にアップデートする際は注意が必要である。
La Fonera (FON ソーシャル ルータ) のファーム ウェアのソース コードは、 Fon blog に書かれているように、 http://download.fon.com/firmware/fonera/latest/fonera.tar.bz2 から取得できる。 これを展開して make を実行すると、 「OpenWrt Configuration」ダイアログが表示される。 現行の La Fonera のファーム ウェアと同じものをビルドするだけなら Configuration の変更は不要。そのまま save して exit すればビルドが行なわれる。
stone を make するには、
libpthread と libopenssl が必要なので、
以下のように設定する。
まず、
Base system --->
を選択して、以下のように libpthread を make するように指定する:
<*> libpthread.......................................... POSIX threa
続いて、
Libraries --->
を選択して、以下のように libopenssl も make するように指定しておく:
<M> libopenssl..................................... Open source SSL < > openssl-util.............................. OpenSSL command lin <M> zlib................. Library implementing the deflate compressi
「Do you wish to save your new OpenWrt configuration?」 と聞かれるので「Yes」と答える。 するとクロスコンパイル環境とファームウェアのビルドがどんどん進む。
*** End of OpenWrt configuration. *** Execute 'make' to build the OpenWrt or try 'make help'. make[2] toolchain/install make[3] -C toolchain install make[4] -C toolchain/sed prepare make[4] -C toolchain/sed compile make[4] -C toolchain/sed install make[4] -C toolchain/kernel-headers prepare make[4] -C toolchain/kernel-headers compile make[4] -C toolchain/kernel-headers install make[4] -C toolchain/sstrip prepare make[4] -C toolchain/sstrip compile make[4] -C toolchain/sstrip install make[4] -C toolchain/uClibc prepare make[4] -C toolchain/binutils prepare make[4] -C toolchain/binutils compile make[4] -C toolchain/binutils install make[4] -C toolchain/gcc prepare make[4] -C toolchain/gcc compile make[4] -C toolchain/uClibc compile make[4] -C toolchain/uClibc install make[4] -C toolchain/gcc install make[4] -C toolchain/ipkg-utils prepare make[4] -C toolchain/ipkg-utils compile make[4] -C toolchain/ipkg-utils install make[4] -C toolchain/libnotimpl prepare make[4] -C toolchain/libnotimpl compile make[4] -C toolchain/libnotimpl install make[4] -C toolchain/lzma prepare make[4] -C toolchain/lzma compile make[4] -C toolchain/lzma install make[4] -C toolchain/squashfs prepare make[4] -C toolchain/squashfs compile make[4] -C toolchain/squashfs install make[4] -C toolchain/jffs2 prepare make[4] -C toolchain/jffs2 compile make[4] -C toolchain/jffs2 install
「install」と表示されているが、 これは「./staging_dir_mips」ディレクトリへのインストール。 「./staging_dir_mips」にクロスコンパイル環境がインストールされた後に、 これを使ってカーネルやライブラリ等のコンパイルが進む。
make[2] target/compile make[3] -C target compile make[4] -C target/utils prepare make[4] -C target/utils compile make[4] -C target/utils install make[4] -C target/linux prepare make[5] -C target/linux/ar531x-2.4 prepare make[4] -C target/linux compile make[5] -C target/linux/ar531x-2.4 compile make[6] -C target/linux/ar531x-2.4 modules make[6] -C target/linux/ar531x-2.4 packages make[4] -C target/image/ar531x compile make[2] package/compile make[3] -C package compile make[4] -C package compile-targets make[5] -C package/base-files compile make[5] -C package/bridge compile make[5] -C package/busybox compile make[5] -C package/chillispot compile make[5] -C package/dnsmasq compile make[5] -C package/dropbear compile make[5] -C package/foncheckrsa compile make[5] -C package/haserl compile make[5] -C package/madwifi compile make[5] -C package/hostapd compile make[5] -C package/iptables compile make[5] -C package/mini_fo compile make[5] -C package/mtd compile make[5] -C package/libpcap compile make[5] -C package/linux-atm compile make[5] -C package/ppp compile make[5] -C package/pptp compile make[5] -C package/iproute2 compile make[5] -C package/qos compile make[5] -C package/webif compile make[5] -C package/wireless-tools compile make[5] -C package/zlib compile make[5] -C package/openssl compile
「OpenWrt Configuration」ダイアログで指定しなかった パッケージまで表示されるが、 これは単に make がそのパッケージのディレクトリの Makefile を実行しただけで、 コンパイルなどは何も行なわずに抜けている。
make[2] package/install make[3] -C package install make[4] -C package install-targets make[5] -C package/base-files install make[5] -C package/bridge install make[5] -C package/busybox install make[5] -C package/chillispot install make[5] -C package/dnsmasq install make[5] -C package/dropbear install make[5] -C package/foncheckrsa install make[5] -C package/haserl install make[5] -C package/hostapd install make[5] -C package/iptables install make[5] -C package/madwifi install make[5] -C package/mini_fo install make[5] -C package/mtd install make[5] -C package/ppp install make[5] -C package/pptp install make[5] -C package/qos install make[5] -C package/iproute2 install make[5] -C package/webif install make[5] -C package/wireless-tools install make[2] target/install make[3] -C target install make[4] -C target/image/ar531x clean make[5] -C target/image/generic/lzma-loader clean make[4] -C target/utils prepare make[4] -C target/utils compile make[4] -C target/utils install make[4] -C target/linux prepare make[5] -C target/linux/ar531x-2.4 prepare make[4] -C target/linux compile make[5] -C target/linux/ar531x-2.4 compile make[4] -C target/linux install make[5] -C target/linux/ar531x-2.4 install make[4] -C target/image/ar531x compile make[4] -C target/image/ar531x install make[5] -C target/image/generic/lzma-loader clean compile
以上で、クロスコンパイル環境とファームウェアのビルドが全て完了。 所要時間は 30分ほど (私の Celeron D 345 マシンの場合)。
ビルドしたクロスコンパイル環境は、 「staging_dir_mips/bin」に PATH を通すだけで使用できる。
stone を make してみる。 まずは CVS レポジトリから最新版を checkout する:
senri % cvs -d:pserver:anonymous@cvs.sourceforge.jp:/cvsroot/stone login Logging in to :pserver:anonymous@cvs.sourceforge.jp:2401/cvsroot/stone CVS password: senri % cvs -z3 -d:pserver:anonymous@cvs.sourceforge.jp:/cvsroot/stone co stone cvs checkout: Updating stone U stone/GPL.txt U stone/Makefile U stone/README.en.txt U stone/README.txt U stone/cryptoapi.c U stone/dist U stone/global.h U stone/logmsg.mc U stone/md5.h U stone/md5c.c U stone/stone.1 U stone/stone.1.ja U stone/stone.c U stone/stone.cnf U stone/stone.sh U stone/stone.spec
次に make する:
senri % cd stone senri % make fon-ssl make CC="mips-linux-uclibc-gcc" SSL_LIBS="-lssl -lcrypto" TARGET=fon ssl_stone make[1]: Entering directory `stone' make FLAGS="-DUSE_POP -DUSE_SSL " LIBS=" -lssl -lcrypto" fon make[2]: Entering directory `stone' make CC="mips-linux-uclibc-gcc" FLAGS="-Wall -DPTHREAD -DUNIX_DAEMON -DPRCTL -DUSE_POP -DUSE_SSL " LIBS="-lpthread -lssl -lcrypto" stone make[3]: Entering directory `stone' mips-linux-uclibc-gcc -Wall -DPTHREAD -DUNIX_DAEMON -DPRCTL -DUSE_POP -DUSE_SSL -o stone stone.c -lpthread -lssl -lcrypto make[3]: Leaving directory `stone' mips-linux-uclibc-strip stone make[2]: Leaving directory `stone' make[1]: Leaving directory `stone'
なお、CVS から checkout した最新版でなくても、 例えば stone 2.3c において
senri:/usr/src/stone-2.3c % mips-linux-uclibc-gcc -Wall -DPTHREAD -DUNIX_DAEMON -DPRCTL -DUSE_POP -DUSE_SSL -o stone stone.c -lpthread -lssl -lcrypto
などとコンパイルすることにより La Fonera 版 stone を make することができる。 つまり特に変更することなく make できるわけで、 いかに La Fonera がフツーの Linux マシンであるかが分かる。
make した stone および、 libpthread, libopenssl を La Fonera へコピーすれば (libopenssl は SSL 版 stone の場合のみ必要)、 La Fonera 上で stone を実行することができる。
root@OpenWrt:~# stone
Jan 4 00:18:08.478674 1024 start (2.3c) [14946]
Jan 4 00:18:08.488958 1024 stone 2.3c http://www.gcd.org/sengoku/stone/
Jan 4 00:18:08.570372 1024 Copyright(C)2007 by Hiroaki Sengoku <sengoku@gcd.org>
Jan 4 00:18:08.579479 1024 using OpenSSL 0.9.8d 28 Sep 2006 http://www.openssl.org/
Usage: stone <opt>... <stone> [-- <stone>]...
opt: -h opt ; help for <opt> more
-h stone ; help for <stone>
-h ssl ; help for <SSL>, see -q/-z opt
あけましておめでとうございます。 今年もよろしくお願いします。
fj.comp.dev.misc に昨年12月26日に投稿された記事
「USB赤外線リモコン on Mac OS X」で、
河野真治 @ 琉球大学情報工学 さん曰く:
パソコン用学習リモコン PC-OP-RS1
昔は、Crossam 2+ と、そのUSB version があったんだけど、 ちょっと高い。もう売ってないし。 これは、5,000円切っている。 もちろん、ドライバはWindows しかありませんが、 シリアルドライバなんて、なんとでもなる。libusb使ってもいいし。
鎌倉にお参りした帰り、たまたま立ち寄った ラゾーナ川崎 のビックカメラにて、 上記 PC-OP-RS1 を見つけたので衝動買い。
「BUFFALOの学習リモコンPC-OP-RS1をLinux(fc3)で使う」を参考にしながら、 perl でテストプログラムを書いてみる。 PC-OP-RS1 は普通のシリアルデバイスとして Linux から見えるので、 CPAN の Device::SerialPort を使えば簡単にコントロールできる。 赤外線リモコンの信号を受信するには、
--> 0x72 <-- 0x59 (リモコン受信) <-- 0x53 <-- リモコンデータ * 240Byte <-- 0x45
とするだけ。 そして、受信した 240バイトのデータ(固定長)を、
--> 0x74 <-- 0x59 --> 0x31 ※ 1ch=0x31,2ch=0x32,3ch=0x33,4ch=0x34 <-- 0x59 --> リモコンデータ * 240Byte <-- 0x45
などと PC-OP-RS1 へ送ってやれば、
PC-OP-RS1 から赤外線が発射される。
ビデオの赤外線受信部近くに、PC-OP-RS1 ↓ の送信部を設置した
(両面テープでラックに固定)。
ラック下段にある PC の上の黒い箱状の ↑ モノが PC-OP-RS1 本体 (拡大写真)。
即席で作った perl スクリプト「irrc」(後述) を、 次のように「-r」オプション付で実行しておいて、 PC-OP-RS1 の受光部へ赤外線リモコンを向けてリモコンのボタンを押す。
% irrc -r ffffffff0f00000080ff000000fc030000f01fc07f000000fe01fc070000e03f000000ff01fe030000f01f000080ff00fe010000f80fe03f000000ff000000fc07f01fc03f000000ff01fc07f80fe03f807f00ff01fc03f80f0000c07f00ff00fe03f80fe01fc07f00ffffffff07000000c03f000000ff010000f80fe01f000000ff00fe030000f01f0000807f00ff010000f80f0000c03f80ff000000fc07f00f0000c07f000000fe03f807f01f000080ff00fe01fc07f00fe03f80ff00fe01fc070000e03f807f00ff01fc03f80fe03f80ffffffff01000000f01f0000000000000000000000000000000000feffff
すると、このように 240 バイトのデータが 16進数で表示される。 このデータを irrc スクリプトの先頭部分で、
$Ir{'aPower'} = [
pack("H*", "ffffffff ... 中略 .... 00feffff"),
];
などと定義する(ちなみに上記データは私の自宅のエアコンの電源ON/OFF)。 複数のボタンのシーケンスを定義する場合は、
$Ir{'AB'} = [
pack("H*", "ボタンA を押したときの送信データ"),
pack("H*", "ボタンB を押したときの送信データ"),
];
などと定義すればよい。 ここで定義したシーケンス名 (上記「aPower」や「AB」) を、 irrc スクリプトの引数として渡せば、 PC-OP-RS1 から赤外線が発射される。
以下、irrc スクリプト全体:
#!/usr/bin/perl
use strict;
use warnings;
use Device::SerialPort;
use Getopt::Std;
my %Ir;
$Ir{'vPower'} = [
pack("H*", "ffffffffffffffffffffff0700000000007ef0831ff8c00f7e00003f00800ffc00003f00801f00c00700f00300f8c10f7c00003f00801f00e00700f0831f00c00f7ee0033ff8c10ffc00003ff00100fc00007e00001f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
];
$Ir{'aPower'} = [
pack("H*", "ffffffff0f00000080ff000000fc030000f01fc07f000000fe01fc070000e03f000000ff01fe030000f01f000080ff00fe010000f80fe03f000000ff000000fc07f01fc03f000000ff01fc07f80fe03f807f00ff01fc03f80f0000c07f00ff00fe03f80fe01fc07f00ffffffff07000000c03f000000ff010000f80fe01f000000ff00fe030000f01f0000807f00ff010000f80f0000c03f80ff000000fc07f00f0000c07f000000fe03f807f01f000080ff00fe01fc07f00fe03f80ff00fe01fc070000e03f807f00ff01fc03f80fe03f80ffffffff01000000f01f0000000000000000000000000000000000feffff"),
];
our ($opt_v, $opt_r, $opt_d, $opt_c);
getopts("vrd:c:") || &help;
my $Verbose = $opt_v;
my $device;
if ($opt_d) {
$device = $opt_d;
} else {
$device = "/dev/ttyUSB0";
}
my $ch = 1;
if ($opt_c && $opt_c =~ /^[1-4]$/) {
$ch = ord($opt_c) - ord('0');
print STDERR "Ch: $ch\n" if $Verbose;
}
my $port = &openDev;
&sendData($port, "69"); # LED command
&expect("4f", &readData($port, 1));
if ($opt_r) {
my $data = &receive($port);
print unpack("H*", $data), "\n";
}
while ($_ = shift @ARGV) {
if (defined $Ir{$_}) {
print STDERR "Tx: $_\n" if $Verbose;
for my $ir (@{$Ir{$_}}) {
&transmit($port, $ch, $ir);
}
} else {
print STDERR "Unknown command: $_\n";
}
}
exit 0;
sub openDev {
my $port = new Device::SerialPort($device) || die;
$port->user_msg(1);
$port->error_msg(1);
$port->baudrate(115200);
$port->databits(8);
$port->parity("none");
$port->stopbits(1);
$port->handshake("none");
$port->read_const_time(100); # 0.1 sec
$port->read_char_time(5);
$port;
}
sub transmit {
my ($port, $ch, $data) = @_;
&sendData($port, "74"); # transmit
&expect("59", &readData($port, 1));
&sendData($port, sprintf("3%d", $ch % 10));
&expect("59", &readData($port, 1));
$port->write($data) || die;
&expect("45", &readData($port, 1));
}
sub receive {
my ($port) = @_;
&sendData($port, "72"); # receive
&expect("59", &readData($port, 1));
&expect("53", &readData($port, 1, -1));
my $data = &readData($port, 240);
&expect("45", &readData($port, 1));
$data;
}
sub sendData {
my ($port, $str) = @_;
$port->write(pack("H*", $str)) || die;
}
sub readData {
my ($port, $len, $timeout) = @_;
my $i = 0;
my $j = 0;
my $data;
if (! defined $timeout) {
$timeout = 10;
}
while ($i < $len) {
my ($l, $d) = $port->read(1);
if ($l > 0) {
$data .= $d;
$i += $l;
$j = 0;
} else {
$j++;
if ($timeout > 0 && $j > $timeout) {
print STDERR "TIMEOUT to read $len byte\n";
exit 1;
}
}
}
if ($Verbose) {
print STDERR "read: ", unpack("H*", $data), "\n";
}
$data;
}
sub expect {
my ($ex, $d) = @_;
my $str = unpack("H*", $d);
if ($str ne $ex) {
print STDERR "expect $ex, but got $str\n";
exit 1;
}
}
sub help {
print STDERR <<EOF;
Usage: psoprs1.pl [opt] <com>...
opt: -d <dev> set device
-c <ch> set channel
-r receive
-v verbose
EOF
print "com: ", join(" ", sort keys %Ir), "\n";
exit 1;
}
「irrc vPower」を実行すれば、ビデオの電源を ON/OFF し、 「irrc -c 2 aPower」を実行すれば、エアコンの電源を ON/OFF できる。 もちろんエアコンの電源を Linux から ON/OFF できてもあまり嬉しくないが、 CS/BS 放送や CATV のチューナのチャンネルを Linux から切り替えて、 そのチューナの出力を Linux マシンで予約録画できると便利。