Chienomi

Linux的にWindowsをバックアップしてみる

windows

ClonezillaやMondo Rescueを使えば一発だし簡単なのだけれど、ここはより手軽で確実な方法を模索する。

Clonezillaでいいのであれば(最大の欠点は、より小さいディスクへの復元ができないことだが)、Clonezillaがわかりやすく容易だし、Mondo Rescueも工夫次第でかなり柔軟だ。

しかし、今回はよりテクニカルかつ原始的に解決してみる。

バックアップを考える

ものすごく単純なバックアップ方法としてddがある。この場合、ディスクに記録されている全ビット情報をクローンする。完全なクローンが出来上がるが、20GBを使用している2TBディスクのクローンのために2TBの領域が必要となる。

これを緩和する方法として圧縮があるが、この場合使っていないが書き込まれている「ノイズ」が多い場合は有効に小さくならない。

NTFSであればntfscloneというプログラムによって、必要最低限の領域で正確にクローンができる。特殊イメージ形式は非常にコンパクトだ。

ここで、Windowsディスクの特性と、Windowsシステムの特性を見てみよう。

$ socat tcp-listen:20083 STDOUT
GPT fdisk (gdisk) version 1.0.1

Partition table scan:
  MBR: protective
  BSD: not present
  APM: not present
  GPT: present

Found valid GPT with protective MBR; using GPT.
Disk /dev/sdb: 625142448 sectors, 298.1 GiB
Logical sector size: 512 bytes
Disk identifier (GUID): 9AED5CE5-3A6D-469A-BCB0-51F33E062B6F
Partition table holds up to 128 entries
First usable sector is 34, last usable sector is 625142414
Partitions will be aligned on 2048-sector boundaries
Total free space is 2669 sectors (1.3 MiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048          206847   100.0 MiB   EF00  EFI system partition
   2          206848          468991   128.0 MiB   0C01  Microsoft reserved ...
   3          468992       625141759   297.9 GiB   0700  Basic data partition

これはUEFIシステムのWindows 7だ。

GPTが使用されており、sdb1がUEFI(ESP)で、FAT32またはFAT16である。

sdb2がMSR。sdb3がシステムパーティションだ。

ESPが2048ブロックから開始している。これより手前部分、GPTとUEFIであればなくてもそれほど苦労せずに復元できると思うのだが、一応残しておいたほうがいいだろう。

0, 1, 2はサイズが小さいため、単純にddでもそれほど問題ないだろう。

システムパーティションはWindows XP以降はNTFSであり、ntfscloneが利用可能だ。

Windowsのシステム起動については、ディスクレイアウトがバックアップ時と同じである必要がある。そのため、手前ブロックを含めて復元して同じレイアウトを再現する必要がある。

ただし、ディスクサイズに対してはある程度柔軟だ。大きいシステムパーティションが最後方にあるため、パーティション3を拡張したり切り詰めたりした上でntfscloneで復元することができる。MBRからGPTへの変換などは、同時にはできないと考えたほうが良い。

転送を考える

バックアップ先は色々考えられるだろう。光学ディスクへのバックアップ、外付けハードディスク、あるいはNAS。

今回の場合は、高性能でストレージ容量の大きいメインデスクトップへと転送する、という方法をとる。デスクトップとラップトップを併用している人などにとっては一般的な要求だろう。

ここでLAN接続の強みが出る。ネットワーク経由で転送することができ、USB2で転送するよりもはるかに速い。 ちなみに、USB1.1を採用するような古いマシンの場合でも通用する方法であり、100BASE-TXあたりのネットワークに接続できる(外部カードを使う方法を含めて)のであれば、USBドライブよりもはるかに高速だ。汎用性がある。

簡単さで考えればSSH経由が最も楽だ。例えば

$ ssh desktop.local -- sh -c 'cat > backup-sdb1' < /dev/sdb1

ただし、SSHによる負荷を嫌うのであれば、rshのほうがいいだろう。しかし、あまり最近はrshを許容していないのではないだろうか。

ほかにもZshを使うとか、FTPを使うとか、FTPFSを使うとか、方法は様々なのだが、ものすごくシンプルに考えると標準入出力渡しが良い。

nc(netcat)だ。

しかし、単純にサーバーで

$ nc -l -p 10000 > sdb1.img

クライアントで

$ nc desktop.local 10000 < /dev/sdb2

としても、転送終了でコネクションを切ってくれない。切るだけであれば、クライアントを

$ nc -c desktop.local 10000 < /dev/sdb2

(GNU netcatの場合)とすれば良いのだが、この場合ちゃんと転送が終わってから切ってくれないため、ちゃんとファイルが転送されない(!)

これを解決するには、クライアント側はsocatを使うようにすれば良い。

$ socat STDIN tcp:desktop.local:10000 < /dev/sdb2

別にサーバー側もsocatを使って良い。OPENよりもSTDOUTのほうが安定している。

$ socat tcp-listen:10000 STDOUT > sdb2.img

実際にやってみる

まずはオフセット分をコピーする。サーバー側:

$ nc -l -p 10000 > sdb0.img.xz

クライアント側:

$ dd if=/dev/sdb bs=512 count=2048 | xz -zev -T 4 | socat STDIN tcp:desktop.local:10000

このケースにおいてはクライアントに余力があるため、サーバーに余計な負担をかけないよう、xzはクライアントで行っているが、古いマシンならばサーバー側でしたほうがいいだろう。:

$ nc -l -p 10000 | xz -zev -T 8 > sdb0.img.xz

クライアント側:

$ dd if=/dev/sdb bs=512 count=2048 | socat STDIN tcp:desktop.local:10000

sdb1以降は少し楽。

$ xz -zev -T 4 < /dev/sdb1 | socat STDIN tcp:desktop.local:10000

sdb2も同様。sdb3はntfsclone。

$ ntfsclone --save-image --output - /dev/sdb3 | socat STDIN tcp:desktop.local:10000

NTFSクローンでもサーバー側ですることは変わらない。

クライアント側socatの代替

Pythonもやろうとしたが、辛いので諦めた。 とはいえ、この4つに対応していれば、だいたいの環境でいけるだろう。

もちろん、LuaやJavaScriptやPythonが好きな人は、即席でプログラムを書いても構わない。

Zsh

TCP Function Systemを使う。

$ autoload -U tcp_open
$ tcp_open desktop.local 10000
$ tcp_send -c < /dev/sdb1
$ tcp_close

TCP shootのほうが簡単

$ zmodload zsh/net/tcp &&  autoload -U tcp_point
$ tcp_shoot desktop.local 10000 < /dev/sdb1

あるいはzsh/net/tcpを使っても良い

$ zmodload zsh/net/tcp
$ ztcp -d3 desktop.local 10000 && ( cat /dev/sdb1 >&3 ) && ztcp -c 3
Perl
$ perl -MIO::Socket::INET -e 'BEGIN { $sock = IO::Socket::INET->new(PeerAddr => "desktop.local", PeerPort => 10000, Proto => "tcp")} while (read(STDIN, $buf, 524288)) { print $sock $buf }' < /dev/sdb1
Ruby
$ ruby -rsocket -e 'sock = TCPSocket.open("desktop.local", 10000); buf = "x" * 524288' -e 'sock.write buf while STDIN.read(524288, buf)' < /dev/sdb1
Bash

意外すぎる必殺技。Zshよりも簡単だったりするので恐ろしい。

$ cat /dev/sdb1 > /dev/tcp/desktop.local/10000

ただし、Bashはクライアントのみ。

書き戻しのnetcat代替

バックアップを使って書き戻す場合、bashが使えない。

仮にリモート側で

$ xz -dv sda0.img.xz | socat STDIN tcp:laptop:10000

とした状況としよう。

netcat

まずは基本

$ nc -l -p 10000 > /dev/sdb
socat

こちらも簡単

$ socat tcp-listen:10000 STDOUT > /dev/sdb
Zsh
$ zmodload zsh/net/tcp &&  autoload -U tcp_point
$ tcp_point 10000 > /dev/sda

もしくは

$ zmodload zsh/net/tcp
$ ztcp -ld3 10000 && ztcp -ad4 3 && cat <&4 > /dev/sdb && ztcp -c 4
Perl
$ perl -MIO::Socket::INET -e 'BEGIN{$l=IO::Socket::INET->new(LocalPort=>10000,Proto=>"tcp",Listen=>5,ReuseAddr=>1); $l=$l->accept} while (read($l, $buf, 524288)) { print $buf }' > /dev/sdb
Ruby
$ ruby -rsocket -e 's = TCPServer.open(10000).accept' -e 'buf = "x" * 524288 ' -e 'write buf while s.read(524288, buf)' > /dev/sdb
Bash

Bashはリスナーになれないので、リモート側を反転させる必要がある。

$ xz -dv sda0.img.xz | socat STDIN tcp-listen:10000

これを受け取る。

$ cat /dev/tcp/desktop.local/10000 > /dev/sdb

もちろん、この「クライアントがソケットを読む」デザインを他に適用しても良いのだが、多分あまりメリットがない。

この方法の意味と価値

まず、簡単の意味を考えなくてはならない。

この記事の内容は、基本的な知識を網羅していることを前提としている。当然ながら、システムバックアップをするためにLive Linuxを使おう、と発想するくらいには。

だが、理解するのは非常に容易だ。パイプとリダイレクションという、Unixerとしては初歩知識さえあれば十分理解できるレベルだ。

一般の人には難しいだろうと思うが、エンジニアを名乗るのであればこの程度の話は通じて然るべきだと思う。少なくとも、私はそう期待している。

「理解する気はないが、模倣して実行したいだけ」という場合は、わざわざこんな複雑な手順を取る必要はないと考えるだろう。実際、他にもバックアップソリューションはあるのだし、たとえコンピュータの後ろに手を伸ばしてでも、あるいは2度コピーする手間が生じたとしても、そのほうが良いと考えるだろう。

実際、即時参照できるネットワークドライブがあるにも関わらず、USBメモリーで渡せと言う人を、私は身近に知っている。

だが、思ったようにいかなかった時はどうすればいいだろう?例えば、Clonezillaが小さなディスクに復元できると思ったのにいざとなったらできなかった時だ。

特定のソリューションに依存するのは非常にリスクが高い。できるだけ汎用性のある方法で、かつ自分が理解できるもののほうがいい。

実際問題として、Mondo Rescueを使うという選択肢を取った場合は、バックアップ先をどうやってマウントするか、という問題が生じてしまう。

また、圧縮したいが、クライアントはリソースが非常に少ないのでできない、という場合に、一旦ディスクに保存してからの圧縮という方法をとらずに(恐らく復元する時は伸長してからの復元ということになる)行いたいといった要求に対応するのも、標準入出力を使うのであれば非常に容易だ。

技術を使う、知識を使うとはこういうことではないか。

特定の目的のために知識が技術を身に着けたところで、できるのはそれだけだ。それは単に慣れた、覚えたというだけの話であり、「知」ではないと思うのだ。

Windowsをアップグレードする前に元に戻せるようにスナップショットを取っておきたい、というのはごく当たり前にある状況だ。そのための機材が揃わないということも。

原理、物事の仕組みを理解し、方法を考えられることは非常に重要だ。すべてお膳立てされ、整った環境でなければできないのか?それがスキルなのか?

最低限ひとつの方法を知っていて、それで目的が達成できるということは有意なことだ。しかしそれは、楽をしようとして知らず犠牲を払っているということでもある。

覚えたひとつの方法でうまくいかない時、あるいは適切でないとき、適切な別の方法が考えられるか?そこが知なのだろう。

では、すべての人がそうした知識を持たなくてはならないのだろうか?私は、否であると考える。人には無限の時間が与えられているわけではない。知の獲得を志すことを是としても、そこにコンピュータに対するものが含まれているとは限らない。

だから、私の仕事があるのだ。ここに知がある。そして、それは独占するものではなく、分け与えられるべきものだ。私も生きなければならないので、無償で、というわけにはいかないが。

だからこそ、用意した方法が通用しない、しかし知を蓄える労力を払うべきという判断ではない、というのであれば、その時にこそ私を頼ってほしいのだ。

それだけの時間もお金もかけているのだ。それだけ真剣に努力を重ねてのものだ。

もちろん、自ら解決するための力をつけるために、私の手を借りてくれたって構わない。先人の肩を借りてその先までいくのは当然のことだ。このニュートンだって巨人の肩に乗るのだから。