序
Windowsからext4を読みたい!と思ったら、いくつか方法がありますが、主な方法は
- WSL2などVMを使って物理ドライブをマウントする
- 専用ドライバなどをインストールしてエクスプローラーからアクセスできるようにする
- 専用GUIソフトを使用してファイラー感覚で操作できるようにする
だと思います。3つ目の方法は内部的にはext4を扱えるようにするライブラリを使って、アクセスする方式になります。
ここで使用されるライブラリの1つがlwext4になります。
今回はこれをmsys2環境から動かしてみたい!という内容です。
1. 背景
ことの経緯とその関連、あと恐らく馴染みの薄いmsys2についての説明を少しだけしておきます。
1-1. 経緯
以下の記事
のコメント欄で「ext3ではfsyncが不要なのか?」という疑問(私が出したわけではありません)に結論を出すべく調査をしていたのですが、その際に、fsync終了後にディスクアクセスがある不思議な現象を確認したので、これを調べるための情報を漁っているうちに辿り着いたライブラリがlwext4でした。このコードを読めばext2/3/4が分かるのでは?という淡い期待を持ってまずは動かしてみよう!としたわけです。Linuxで動かしても旨味がないのですが、Windowsから別パーティションやディスクイメージ内のext4にアクセスできたら嬉しいでしょう?
1-2. ext2~ext4について
私が初めてLinuxに触れた頃にはもうext2がありました。ジャーナリングのない素朴なファイルシステムです。昔はLinuxもドライバとかにバグが多く、よく落ちたりしてましたが、そんな際にジャーナリングがないと、壊れたファイルシステムの修復(ざっくりと…)を行うfsckに時間がかかりすぎて仕方なく、ext2にジャーナリング機能を付けたものがext3なのです(他にも変更点ある)。これにサイズ上限を上げたりした(他にも変更点ある)のがext4で、このext4が現在Linuxで一番オーソドックスなファイルシステムになります。詳細はwikipediaで。
https://ja.wikipedia.org/wiki/Ext2
https://ja.wikipedia.org/wiki/Ext3
https://ja.wikipedia.org/wiki/Ext4
なお、ext3についているジャーナリングは普通メタデータだけなので、そんなに高機能ではありません(ext4は知らない)。
1-3. ext4を扱えるライブラリについて
いくつかあるのですが、有名なlibext2はもう10年前から更新がありません。色々調べたところ、どれもあんまり活発に開発されてないのですが、まだマシ(3年前までコミットがある。でも最終リリースは10年前)だったのがこのlwext4になります。C#やらRustやらからも使おうとしてる人がいたようだし(動くのかどうか知らない)、ジャーナルについても扱えそうだったので取りあえずこれにした感じです。
1-4. msys2環境について
WindowsでUn◯xライクなCLI環境を構築するために使用されるものです。最近ではWSL2があるので、割と下火ですが、元来Mingwを使うための標準的な環境で、cygwinともどもVisualStudioが売り物しかなかった時代からWindowsでバイナリを作れた優れモノでした。cygwinはWindows APIとの間にU◯ixライクなエミュレーション層を挟むライブラリなのですが、mingwは純粋にWindows用のバイナリを作るための環境だったので、OSSなソフトはこぞってこれでWindows用のソフトを作っていました(VSだとお金かかるので)。その関係でmsys2はcygwinほどではなかったけど、割と利用者のいる環境でした。cygwinはエミュレーション層があるので、U◯ixライクなOS上で動くソフトの移植が比較的楽で、それなりのシェアがあったのです。
最近ではWSL2/dockerの登場もあって完全にcygwinはシェアを落とし、MSに依存しないWindowsの開発環境として、またgit bash環境で馴染みが少しある人も増えたmsys2が、相対的に少しずつ知名度を上げてきてるという感じです。
2. msys2環境の構築
ここ見て入れましょう。簡単です。
起動可能な環境はUCRT64がオススメです。gcc/clangが選べますが、そこは好み。伝統的にはgccで、応用機能的にはclangですが、基本機能では甲乙付け難く、個人的には問題が起きたときに楽なのがgccなので、普段最新機能はgccで試し、clangはサブ的な使い方になってます。UCRT/MINGWの違いは実際には使用しているVisualC++のCランタイムの違いであり、MINGWは古すぎます。VS2015からUCRTです。
3. lwext4のビルド
ここからが鬼門です。
3-1. 準備
まずはパッケージを最新にして必要なパッケージを入れます。
$ pacman -Syuu
$ pacman -S make git gcc cmake p7zip
入れたら次はgithubからlwext4のソースコードを取ってきます。普通はリリース用のzipから構築すると思いますが、10年前なので流石に使う気がしません。最後の更新も3年前ならmasterの最新でいいでしょう。
$ git clone https://github.com/gkostka/lwext4.git
3-2. 初回ビルドの洗礼
まずはreadme.mdの記載どおりにビルドしてみます。
$ cd lwext4/
$ make generic
$ cd build_generic
$ make
...
[ 75%] Linking C executable lwext4-server.exe
C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/14.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe:
CMakeFiles/lwext4-server.dir/lwext4_server.c.o: in function `device_register':
C:/msys64/home/user/ext4/tmp/lwext4/fs_test/lwext4_server.c:396:(.text$device_register+0x69): undef
ined reference to `file_windows_name_set'
...
collect2.exe: error: ld returned 1 exit status
make[2]: *** [fs_test/CMakeFiles/lwext4-server.dir/build.make:102: fs_test/lwext4-server.exe] Error 1
make[1]: *** [CMakeFiles/Makefile2:201: fs_test/CMakeFiles/lwext4-server.dir/all] Error 2
make: *** [Makefile:156: all] Error 2
$
言われたとおりにやってるのに…とお思いのあなた、違います。OSSというのは個人の制作物なので、ドキュメントは間違ってて当然なのです。開発者はドキュメント作成なんておまけ作業としか思っておらず、プログラムを公開したのも、コードを読んで理解できる人のためで、出来ればドキュメントなんて作成したくないのです。体裁を整えるため渋々書いてるだけなので、少々間違ってても、自力で何とかできない人ははじめから相手にしていません。もちろん人気プロジェクトになれば話は違うのですが。
同じ理由で、issueなども自分でパッチまで書いてPR投げるような人以外はよほど重大なバグでもない限り相手にしません。
3-3. 二度目の洗礼
気を取り直して原因究明のために再度調査します。
$ make
src/CMakeFiles/lwext4.dir/compiler_depend.make:4: *** multiple target patterns. Stop.
make[1]: *** [CMakeFiles/Makefile2:396: src/CMakeFiles/lwext4.dir/all] Error 2
make: *** [Makefile:156: all] Error 2
$
なんかエラーが違います。さっきはリンクエラーだったのに、今回はどうやらMakefileに問題があるっぽいです。つまり問題は今のところ2つあります。
- 初回ビルド時、lwext4-serverのリンクに失敗する
- 2回目以降ビルド時、Makefileにエラーが起きる
3-4. Makefileの問題への対処
まずはMakefileの問題が何とかならないとエラーの確認すらできないので、これに対処します。
メッセージにあるファイルを見るとこうなっています。
...
src/CMakeFiles/lwext4.dir/ext4.c.o: /home/user/ext4/tmp/lwext4/src/ext4.c \
src/C:/msys64/home/user/ext4/tmp/lwext4/build_generic/include/generated/ext4_config.h \
...
これはどうやらext4.oを作るために必要なファイルを自動生成した結果のようです。gccにはそういう機能があって、コンパイル時に-MD
とか指定すると、拡張子が.d
のファイルを生成してくれて、これをMakefileに取り込むと、makeがその依存関係を追っかけてくれる、という寸法です。
https://gcc.gnu.org/onlinedocs/gcc-14.2.0/gcc/Preprocessor-Options.html#index-MD
https://qiita.com/dmystk/items/3f82b1eb763c9b9b47e8
これを見ただけでなぜそこまで判断出来るのかというと、こんな具体的で長い依存関係を人間が手で書いたりしないからなのと、CMakeFilesという名前と依存パッケージから、内部で使っていると思しきcmakeが自動生成した依存関係を使用するからです。で、問題はこの中のパスに現れるC:
というドライブ名です。コロンはU◯ixライクなシステムだとパス中ではありえない文字であり、これはMakefileでターゲットとその依存先を分かつ記号になっています。つまりmakeがMakefileを解釈する際に特別視する記号なわけです。エラーは複数ターゲットパターンとかいう謎の文言だったのも恐らくこれが原因と分かります。msys2環境固有の問題で、mingw向けのgccは基本Windowsのファイルパスを扱うので、こんな形になってしまうわけです。
ここまでは見た瞬間に分かるわけですが、対処となると厄介です。一番単純な対処はコロンの前に\を付けることなのですが、何せこのファイルを直接編集しても、自動生成されるファイルなので、ビルドする度に元に戻ってしまいます。
挙げ句、Makefile自体もcmakeで自動生成されてるわけで、Makefileを編集してもcmakeを実行する度に書き換えなくてはなりません。なので出来ればCMakeLists.txtで対処したいわけです。しかし、cmake自体は依存関係の生成自体のオプションを細かくいじれるように出来ていません。ただ、実際コロンが出てきてビルドエラーになるのはmsys2環境のcmakeの問題なので、本来はココで対処すべきです。ちょっとサンプルプログラムを作ってcmakeを試したりもしましたが、生成されるファイルが全然違ってて(多分バージョン指定が違う)、コロンの問題は起きませんでした。つまりcmake自体はバージョンアップで対処されてるけど、lwext4のCMakeLists.txtが根本的に古すぎて対処できないということのようです。
そこで依存関係を自動生成する目的はソースファイルとヘッダファイルの依存関係の記述なので、基本ヘッダを変えたらmake cleanする、という運用ルールにして、依存関係の取り込みを削除する、という方法にしてみました。
$ find . -type f -name '*.make' | \
xargs egrep -l -E '^include\s+.*compiler_depend.make$' | \
while read f;do \
echo "[$f]"; \
sed -i 's/^\(include.*compiler_depend\)\.make/# \1/' $f; \
done
※Makefileを直接変えているので、cmake実行の度に上記が必要になります
こうすることにより、makeでリンクエラーを再現させることが出来るようになります。
3-5. リンクエラーの問題への対処
問題が起きているのはlwext4-serverというサーバーで、ライブラリが欲しい私には明らかに必要ではないのですが、エラーは流石に残したくありません。
原因は何なのか調べてみると…
何かライブラリにシンボルが足りないようです。最初のエラーに出ているfile_windows_name_set
を探してみると、
$ find .. -type f -name '*.c' -o -name '*.h' | xargs grep file_windows_name_set
どうやらblockdev/windows/file_windows.c
に定義があるようです。ライブラリとしては./blockdev/libblockdev.a
と./src/liblwext4.a
しか作っていないので、当該シンボルは./blockdev/libblockdev.a
に入っていないと困ります。
$ objdump -t blockdev/libblockdev.a | grep file_windows_name_set
$
ないようです。blockdevの構成を調べてみると
$ find ../blockdev/ -type d
../blockdev/
../blockdev/linux
../blockdev/windows
$
blockdevに共通コードがあって、linuxとwindows用にそれぞれ別のコードがあるようです。msys2環境では恐らくある程度どちらも使えると思うのですが、今はlinux側がビルド対象になっててwindows側が外れていそうです。cmakeを追っていくと…
...
if (WIN32)
set(BLOCKDEV_TYPE windows)
else()
set(BLOCKDEV_TYPE linux)
endif()
...
ありました。これを指定しているのはどこかというと、
...
generic:
$(call generate_common,$@)
...
mingw:
$(call generate_common,$@,-DWIN32=1)
...
よく見るとmingwというターゲットがあります。ただmake mingwしてしまうと名前のせいで全然ダメだったので、
...
generic:
$(call generate_common,$@,-DWIN32=1)
...
mingw:
$(call generate_common,$@,-DWIN32=1)
...
こう変えてしまい、genericのままにします。ではおもむろにcmakeから
$ cd ..
$ make generic
$ cd build_generic
$ make
...
[ 78%] Building C object fs_test/CMakeFiles/lwext4-client.dir/lwext4_client.c.o
C:/msys64/home/user/ext4/tmp/lwext4/fs_test/lwext4_client.c:41:12: error: static declaration of 'in
et_pton' follows non-static declaration
41 | static int inet_pton(int af, const char *src, void *dst);
| ^~~~~~~~~
...
make: *** [Makefile:156: all] Error 2
サーバーのビルドは出来たようですが、今度はクライアントが…誰も使ってないんでしょうね。エラーの内容はファイルスコープなのにヘッダでexternされてるというものです。.cからstaticを取れば(あまり良くないですが)OKです。これを修正することで全てのビルドが行われます。
次のインストール手順はuninstallできなくなるだけなので実施しません。
3-6. 確認
testします。
$ make test
まあ普通に動かないのですが、こちらは多分分かると思うのでパッチだけ置いときます。
diff --git a/fs_test.mk b/fs_test.mk
index 3ea1ac2..ae41996 100644
--- a/fs_test.mk
+++ b/fs_test.mk
@@ -577,7 +577,7 @@ server_ext4:
$(LWEXT4_SERVER) -i ext_images/ext4
server_kill:
- -killall lwext4-server
+ ps -ef | grep lwext4-server | grep -v grep | awk '{print $$2;}' | while read p;do kill $$p;done
fsck_images:
sudo fsck.ext2 ext_images/ext2 -v -f
@@ -590,9 +590,9 @@ images_small:
dd if=/dev/zero of=ext_images/ext2 bs=1M count=128
dd if=/dev/zero of=ext_images/ext3 bs=1M count=128
dd if=/dev/zero of=ext_images/ext4 bs=1M count=128
- sudo mkfs.ext2 ext_images/ext2
- sudo mkfs.ext3 ext_images/ext3
- sudo mkfs.ext4 ext_images/ext4
+ ./build_generic/fs_test/lwext4-mkfs.exe --input ext_images/ext2 -v -e 2
+ ./build_generic/fs_test/lwext4-mkfs.exe --input ext_images/ext3 -v -e 3
+ ./build_generic/fs_test/lwext4-mkfs.exe --input ext_images/ext4 -v -e 4
images_big:
rm -rf ext_images
@@ -600,9 +600,9 @@ images_big:
dd if=/dev/zero of=ext_images/ext2 bs=1M count=1024
dd if=/dev/zero of=ext_images/ext3 bs=1M count=1024
dd if=/dev/zero of=ext_images/ext4 bs=1M count=1024
- sudo mkfs.ext2 ext_images/ext2
- sudo mkfs.ext3 ext_images/ext3
- sudo mkfs.ext4 ext_images/ext4
+ ./build_generic/fs_test/lwext4-mkfs.exe --input ext_images/ext2 -v -e 2
+ ./build_generic/fs_test/lwext4-mkfs.exe --input ext_images/ext3 -v -e 3
+ ./build_generic/fs_test/lwext4-mkfs.exe --input ext_images/ext4 -v -e 4
test_set_small: t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 t13 t14 t15 t16 t17 t18 t19 t20
test_set_full: t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 t13 t14 t15 t16 t17 t18 t19 t20 t21 t22 t23 t24 t25 t26
これで動きます。イメージの中身が空で切ないので、別のテスト/デモを動かしておきます。
$ ./build_generic/fs_test/lwext4-generic -i ext_images/ext2
$ ./build_generic/fs_test/lwext4-generic -i ext_images/ext3
$ ./build_generic/fs_test/lwext4-generic -i ext_images/ext4
こちらがlwext4単体のテストです。
4. サンプル実装
指定したイメージファイルを読み取り、lsとファイル抽出だけするコードです。開いたファイルは普通修復されてしまうのですが、そのコードをコメントして抜いてあります。ライセンスはライブラリがGPLなので同じくGPLです。
extern "C" {
#include <ext4.h>
#include <linux/file_dev.h>
}
#include <iostream>
#include <string>
#include <optional>
#include <stdexcept>
#include <sstream>
#include <list>
#include <memory>
#include <vector>
#include <fstream>
struct mounted_image {
static std::optional<mounted_image> mount(const std::string& path) noexcept {
try {
mounted_image img{path};
return img;
}
catch(std::runtime_error& e) {
std::cerr << e.what() << std::endl;
return std::nullopt;
}
}
mounted_image(mounted_image&& org) noexcept {
if (this == &org) return;
if (org.optpath) {
optpath = std::move(org.optpath.value());
org.optpath.reset();
}
}
~mounted_image() noexcept {
if (optpath) {
ext4_cache_write_back("/", 0);
auto r = ext4_journal_stop("/");
if (r != EOK) {
std::cerr << "ext4_journal_stop: fail " << r << std::endl;
}
r = ext4_umount("/");
if (r != EOK) {
std::cerr << "ext4_umount: fail " << r << std::endl;
}
}
}
static const char *entry_to_str(uint8_t type) noexcept
{
switch (type) {
case EXT4_DE_UNKNOWN: return "[unk] ";
case EXT4_DE_REG_FILE: return "[fil] ";
case EXT4_DE_DIR: return "[dir] ";
case EXT4_DE_CHRDEV: return "[cha] ";
case EXT4_DE_BLKDEV: return "[blk] ";
case EXT4_DE_FIFO: return "[fif] ";
case EXT4_DE_SOCK: return "[soc] ";
case EXT4_DE_SYMLINK: return "[sym] ";
default: break;
}
return "[???]";
}
struct opened_dir {
opened_dir(const std::string& path) noexcept(false)
: pd{std::make_unique<ext4_dir>()}
{
auto r = ext4_dir_open(&(*pd), path.c_str());
if (r != EOK) {
std::stringstream ss;
ss << "ext4_dir_open: rc = " << r;
throw std::runtime_error(ss.str());
}
}
opened_dir(const opened_dir&) = delete;
opened_dir(opened_dir&& org) {
if (this == &org) return;
pd = std::move(org.pd);
de = org.de;
}
~opened_dir() noexcept {
if (pd) {
ext4_dir_close(&(*pd));
}
}
bool next() noexcept {
de = ext4_dir_entry_next(&(*pd));
return de != nullptr;
}
const ext4_direntry& current() {return *de;}
private:
opened_dir() = delete;
std::unique_ptr<ext4_dir> pd;
const ext4_direntry *de;
};
static std::optional<opened_dir> opendir(const std::string& path) noexcept {
try {
return opened_dir{path};
}
catch(const std::runtime_error& e) {
std::cerr << e.what() << std::endl;
return std::nullopt;
}
}
struct opened_file {
opened_file(const std::string& path, const std::string& mode) noexcept(false)
: pf{std::make_unique<ext4_file>()}
{
auto r = ext4_fopen(&(*pf), path.c_str(), mode.c_str());
if (r != EOK) {
std::stringstream ss;
ss << "ext4_fopen ERROR = " << r;
throw std::runtime_error(ss.str());
}
}
opened_file(opened_file&& org) noexcept {
if (this == &org) return;
pf = std::move(org.pf);
}
~opened_file() noexcept {
if (pf) {
ext4_fclose(&(*pf));
}
}
auto read(char* buf, size_t cap_sz, size_t* pread_sz) {
return ext4_fread(&(*pf), buf, cap_sz, pread_sz);
}
private:
std::unique_ptr<ext4_file> pf;
opened_file() = delete;
opened_file(const opened_file&) = delete;
};
static std::optional<opened_file> openfile(const std::string& path, const std::string& mode) noexcept {
try {
return opened_file(path, mode);
}
catch(const std::runtime_error& e) {
std::cerr << e.what() << std::endl;
return std::nullopt;
}
}
private:
std::optional<std::string> optpath;
mounted_image() = delete;
mounted_image(const mounted_image&) = delete;
mounted_image(const std::string& path) noexcept(false) {
file_dev_name_set(path.c_str());
auto bd = file_dev_get();
if (!bd) {
throw std::runtime_error("open_filedev: fail");
}
//ext4_dmask_set(DEBUG_ALL);
auto r = ext4_device_register(bd, "ext4_fs");
if (r != EOK) {
std::stringstream ss;
ss << "ext4_device_register: rc = " << r;
throw std::runtime_error(ss.str());
}
r = ext4_mount("ext4_fs", "/", true);
if (r != EOK) {
std::stringstream ss;
ss << "ext4_mount: rc = " << r;
throw std::runtime_error(ss.str());
}
optpath = path;
// r = ext4_recover("/");
// if (r != EOK && r != ENOTSUP) {
// std::stringstream ss;
// ss << "ext4_recover: rc = " << r;
// throw std::runtime_error(ss.str());
// }
r = ext4_journal_start("/");
if (r != EOK) {
std::stringstream ss;
ss << "ext4_journal_start: rc = " << r;
throw std::runtime_error(ss.str());
}
ext4_cache_write_back("/", 1);
}
};
auto get_filename(const std::string& path) {
auto pos = path.find_last_of('/');
if (pos == std::string::npos) {
return std::string(path);
} else {
return path.substr(pos + 1);
}
}
int main(int argc, char* argv[]) {
if (argc < 3) {
std::cerr << argv[0] << " IMAGE_FILE COMMAND ARGS" << std::endl;
return 1;
}
std::string image_path{argv[1]};
auto optimg = mounted_image::mount(image_path);
if (! optimg) {
std::cerr << "\"" << image_path << "\" cannnot be mounted!" << std::endl;
return 1;
}
auto img = std::move(optimg.value());
std::string command{argv[2]};
std::string arg{argc < 4 ? "" : argv[3]};
if (command == "ls") {
auto optod = img.opendir(arg);
if (! optod) return 1;
auto od = std::move(optod.value());
std::cout << "ls " << arg << std::endl;
while (od.next()) {
const ext4_direntry& de = od.current();
std::cout << img.entry_to_str(de.inode_type);
std::cout.write(reinterpret_cast<const char*>(de.name), de.name_length);
std::cout << std::endl;
}
} else if (command == "get") {
auto filename = get_filename(arg);
if (filename == "") {
std::cerr << "filename is empty." << std::endl;
return 1;
}
auto optof = img.openfile(arg, "r");
if (! optof) return 1;
auto of = std::move(optof.value());
std::vector<char> buf(4096);
size_t sz = 0;
std::ofstream fout(filename, std::ios_base::out | std::ios_base::binary);
do {
if (of.read(buf.data(), buf.size(), &sz) != EOK) {
std::cerr << "read error" << std::endl;
return 1;
}
fout.write(buf.data(), sz);
} while(sz != 0);
std::cout << filename << " was created in this directory." << std::endl;
} else {
std::cerr << command << " is an unkown command." << std::endl;
return 1;
}
return 0;
}
$ g++ -g -Wall -pedantic -std=c++17 -I./include -I../include/ -I../blockdev/ hoge.cpp -o hoge -Lsrc -Lblockdev -lblockdev -llwext4
$ cd ..
$ MSYS2_ARG_CONV_EXCL=\* ./build_generic/hoge.exe ext_images/ext2 ls /
ls /
[dir] .
[dir] ..
[dir] lost+found
[dir] dir1
[fil] hello.txt
[fil] test1
$ MSYS2_ARG_CONV_EXCL=\* ./build_generic/hoge.exe ext_images/ext2 get /hello.txt
hello.txt was created in this directory.
$ cat hello.txt
Hello World !
$ MSYS2_ARG_CONV_EXCL=\* ./build_generic/hoge.exe ext_images/ext3 ls /
...
$ MSYS2_ARG_CONV_EXCL=\* ./build_generic/hoge.exe ext_images/ext4 ls /
(おまけ)fsyncの実験ファイルでの調査
$ MSYS2_ARG_CONV_EXCL=\* ./build_generic/hoge.exe ext_images/loopdisk.img.snapshot ls /
ls /
[dir] .
[dir] ..
[dir] lost+found
$ MSYS2_ARG_CONV_EXCL=\* ./build_generic/hoge.exe ext_images/loopdisk.img ls /
ls /
[dir] .
[dir] ..
[dir] lost+found
[fil] dummy
$ MSYS2_ARG_CONV_EXCL=\* ./build_generic/hoge.exe ext_images/loopdisk.img get /dummy
dummy was created in this directory.
$ hexdump -C dummy
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00001000
$
想定通りの結果になっている。
※ただし、ext4_recover()
呼び出しをしていない
5. まとめ
なんとかlwext4を使ってext2~4のイメージファイルからファイルを抽出したり出来た。
技術的にはMBRとGPTの解析も付けて、パーティションを直に見るのも可能(ただし要管理者権限)なのですが、本来あんまり見えてほしくないものかもしれないし、これのために暗号化やめたりとかしてもアホらしいので今回は見送りました。