読者です 読者をやめる 読者になる 読者になる

実戦的和服術における荷物運搬法の検討

ドーモ 5月29日 呉服の日ですね. 呉服の日は24時間大バーゲンが行われたり, 和服界隈に盛り上がりがあり, 実戦的和服術者にとっては嬉しいかぎりです.

さて, 実戦的和服において荷物の運搬は大きな問題です. 一般的和服状況においては軽装を旨とし, あまりノートパソコンと本数冊を日常的には運搬しないように思います. しかしながら, 実戦的和服生活においては, 様々な場面で多様な荷物を運搬することが求められています. たとえば, ホームパーティーにワインなど持っていったり, 図書館で本を借りまくったり, 山奥にハッカソンをしにノートパソコンと参考文献のスゴクアツイホンと着替えを持っていったりしたいわけです. ところが, とかく和服で荷物というと小さな袋で財布と携帯ぐらいしか入らないやんという情報が出てきがちです. そんな小さな袋には入らない, より多くの大きな荷物を実戦的に運搬するにはどうしたらいいでしょうか.

本稿では和服で無理なく荷物を運搬する方法について, 和服そのもの, ショルダーバッグ, ミニバッグ, 風呂敷, リュックを検討する.

要求

まず, 自分がどのような荷物を運搬したいのかを整理する. 状況によって持ち運びたいもの・量は異なるが大きく分けて以下のようになる.

  • いつも
  • 日常
    • A4ノートパソコンを運びたい
    • 文庫本を2〜4冊+大きな本を1冊を運びたい
  • 図書館
    • 最大本を5冊ほど運びたい
    • A4ノートパソコンと, 本を6冊と, 着替えを運びたい
    • たまには無段階調整式竹馬(GO GO STICK TAKEUMA SW-12)やペンギンマスクを運びたい

山に行くのはあまりないし, 竹馬を運ぶことはもっとないので基本的に日常に向けて検討していく.

また, 財布やタブレット, 文庫本については低レイテンシでアクセスしたい.

基本装備

  • たもと・ふところ・帯のすきま
  • メリット
    • 追加装備なしでいつでも使える
    • 低レイテンシ
    • 改札でマジックができる
  • デメリット
    • 小物しか入らない
    • 着崩れの原因となる
    • 見た目が悪化する
    • 落下の危険もある
    • どこに入れたかわからなくなりやすい (個人の感想です)

まず基本装備である和服そのものの荷物運搬性能について見ていく.

洋服と違い, 一般的に和服にはポケットが実装されていない. そのため, 和服本体の運搬性能は低いと思われがちである.

しかし, 和服には「たもと」「ふところ」「帯のすきま」と3つの荷物保持空間がある. 「たもと」は袖の下の袋状の空間で, 男性着物の場合はここに穴がないので安心安全な荷物保持空間として使用できる. 「ふところ」はお腹のところの空間で, 落語などでてぬぐいが出てくる部分である. 「帯のすきま」は帯と帯, または帯と着物の間の空間である. たもとにはそれなりの大きさのものを入れることができる. ふところには, 薄手のものを入れることができる. 帯のすきまにはスマホや扇子を挿すことができる.

また, デニム着物など一部環境では, ポケットが実装されておりスマホぐらいは入ったりする.

和服本体に実装されているため, 追加装備コストがかからず, 低レイテンシでアクセスできるというメリットがある.

また, たもとに交通IC系アイテムを入れておくと, 袖でさっと改札を一なでするだけで改札を通れてかっこいいみたいな時もある. (個人の感想です)

その一方で, 多くの物を入れてしまうと着崩れを招く, 見た目が悪化するといったデメリットがある. たとえば, たもとに重いものを入れると袖の感じが変になるし, 場合によってはうっかり袖ラリアットしてしまう. ふところにふくらみのあるものを入れると, 帯まわりの着付けが乱れるし, おなかがぽっこりしてくる. 帯のすきまにはそもそも多くの物は入らない.

また, ふところや帯には落下の危険もある. たとえば, 帯にスマホをはさんでおくと, ふとした拍子にゆるんでスマホが飛んでいく. ふところについても, 帯を直したりなどしたタイミングでスマホがすべりおちやすい.

また, あまり体に影響がかからないものが多いからか, あちこち使っていると場所を見失うこともある. たとえば, 筆者はスマホなくしたーーーーーー!!!!ておくれ〜〜と思って, いざタブレットからスマホ探索するとお腹からアラームがしてきたこともある.

いつものショルダーバッグ

  • メリット
    • いつも通りの使い勝手
    • 全てのものに低レイテンシでアクセスできる
  • デメリット
    • 和服と見た目があわない
    • 相性によっては着物にダメージ
    • 最近肩がつらい

特にこだわらずに, 洋服時と同じバッグを使用するという装備である. 親しんだ使い勝手で, 低レイテンシで(バッグに入るなら)全てのものにアクセスできる.

しかしながら, その見た目が和服と合わなくて変だと数多く指摘される. また, 鞄と着物双方の素材によっては, 着物にダメージが蓄積され, 着物はすりきれほどけとびねじりちぎれ爆発四散サヨナラ!することもある (ニンジャの感想です). 筆者の環境でも, (中古品だけど)羽織がすりきれダメになったことがある.

また, 本を複数冊運ぶにおいて片方の肩に負担のかかるショルダーバッグはそろそろつらい, てか靴底めちゃ片方に傾いてすりきれとるやんけやっべえという問題もある.

帆布バッグ

  • メリット
    • 和服と見た目があう
    • いつも通りの使い勝手
    • 全てのものに低レイテンシでアクセスできる
  • デメリット
    • 大きいやつが少ない
      • ノートパソコン入れてぎりぎり
    • 肩はやっぱりつらいよ

ショルダーバッグの見た目とダメージの問題を解決するため, 帆布バッグを導入した. 帆布バッグは, 見た感じ和風でありつつ, 丈夫で既存ショルダーバッグとほぼ同様の性能で荷物を運搬できる.

しかし, 帆布バッグは入手個所が限られ, したがってサイズなど完全にしっくりくるものは見つけづらいという難点があった. 実際, 使用中の帆布バッグは既存ショルダーバッグより若干小さく, ノートパソコンの出し入れがちょっとめんどい, 本があまり入らないという問題がある.

また, 力学的に同じ特性を持つため, 肩はやっぱりつらい.

エストポーチ類を用いたハイブリッドシステム

重い物を持つには, たとえばリュックのように両肩・背中を活用することが大切である. しかし, リュックには背負う・下ろすといった動作が必要であり, アイテムへのアクセスレイテンシが悪化する.

ここで低レイテンシでアクセスしたいものは, スマホタブレット・財布や文庫本などの小物に限られていることに注目する. ウエストポーチ類によって小物を保持し, 大きなものは別途背負い, 機動的読書やスマホを確保しつつ, 体にやさしいハイブリッドシステムを実現する.

エストポーチ類は実は, 実戦的和服と相性がよい. 和服を着るにあたって, 帯をしめるが, この幅はそれなりに広くウエストポーチ類のベルトを自然に隠すことができる.

筆者環境では, 小さいショルダーバッグのベルトを短くして腰のまわりにフィットさせる実装をとっている. この中には鍵・財布・タブレットスマホ・文庫本3冊を保持できる.

以下では, ハイブリッドシステムにおいて, ウエストポーチ類の双対をなす背負う形式の荷物運搬方法を検討する.

汎用変形古典的荷物運搬用具風呂敷

  • メリット
    • 様々なサイズに対応
    • たためばめっちゃコンパクト
  • デメリット
    • 首がしまる
    • IOレイテンシが高い
    • 背負うと暑い

風呂敷は古来使われている荷物運搬方法である. 様々なサイズがあり, 多様な梱包方法があるので, 手に持つ・身体にまきつける・背負うなど多くの保持方法をサポートしている. また, たためばコンパクトに収納できるため, スケールアップ・スケールダウンが極めて容易である.

風呂敷で多くの荷物を運搬するのに関してはいくつか先行研究がある.

これらを参考として, 現在「お使い包み」の最後の結びをゆるくして首を通したりたすきがけにして, 荷物を運搬する手段をとっている.

この方法により, 肩・背中を活用しながら多くの荷物を運搬できる. しかし, 背負い方には今後も工夫が必要である. 現在の形, 特に首を結び目に通すスタイルは手をはなすと首がしまってトイレなどで大変苦しい. より手を活用しやすい背負い方を見つけていきたい. たとえば, 四つ結びスタイルも, 背負うタイプの運搬に使えそうで, 今後の検討課題となる.

時代劇に知見を求めて

そんで, 昨日これ見たんですよ. 満島ひかり氏がえも言われぬ雰囲気を出していてサイコーだった. 最初一人だけ声の通った稟とした感じの話し方なんだけど, それが変わっていくんだよね. いや〜

それはそうとして, 古典的に大きな荷物をどうしていたのかをざっくり時代劇を参考に見てみると, 行李や風呂敷に入れて背負子を使っているなあということがわかる.

リュック

そうすると, 世の中には背負子と行李・風呂敷が合体したような形態のものリュックがあると気がつく. リュックは背負いやすく, しかも風呂敷よりもアクセスレイテンシが低い. ただし, スケールアップ・スケールダウンはできない.

ところが, 着物にリュックは合わないとか, ショルダーバッグと同様に生地が痛むといった懸念がある. 着物にリュックが合わないに関しては前述のように, 昔から行李が使われていたわけだし, 柄さえうまくやればリュックでも違和感はないと考えている. そうすると問題はうまい生地…ということで, あまり和服がいたまなさそうで和服に合いそうなリュックを発見して, どのような使い心地になるのかを今後実地検証していきたい.

これとか

しかし, 和風でリュックでそれなり大きい(A4ノートPCするっと入れ)ってむずかしい

まとめ

実戦的和服術における荷物運搬法について, 低レイテンシがほしいアイテムをウエストポーチ類に置き, 高レイテンシでもよいアイテムを背負うハイブリッドアプローチをとることを検討している. 背負う側の実装について, 現在は風呂敷を用いているが, 高いレイテンシ・保持が困難といった課題がある. 今後, 背負う側の実装について和風でやわらかい感じのリュックサックを見つけ評価していきたい.

LVMのPhysical Volumeが見つからないとどうなるか

LVMのPhysical Volumeが見つからないとどうなるか

新しくLVMのLogical Volume作ろうとしたらエラーが出て作れなかった

WARNING: Device for PV fkpfAD-HZZG-JmI0-yOTu-TnMZ-J8Ul-B7Hg6D not found or rejected by a filter.
Cannot change VG libvirt_lvm while PVs are missing.

物理デバイスが消えてる? pvsコマンドで見てみよう.

$ sudo pvs
WARNING: Device for PV fkpfAD-HZZG-JmI0-yOTu-TnMZ-J8Ul-B7Hg6D not found or rejected by a filter.
PV VG Fmt Attr PSize PFree
/dev/sdc1 libvirt_lvm lvm2 a-- 1.82t 1.82t
/dev/sdd1 libvirt_lvm lvm2 a-- 1.82t 1.82t
[unknown] libvirt_lvm lvm2 a-m 1.82t 672.11g

たしかに"/dev/sdb1"であるべきところが"[unknown]"になっている.

dmesgでIOエラーなど出てないし, fdiskでも様子が見れていることを確認して, ふと思い出してみると, この前"dd if=/dev/zero of=/dev/sde"するのを間違えて, /dev/sdbにしてあわててCtrl-Cした後に, だいたいこんな感じだっただろとfdiskでパーティションを作り直したようなことを思いだした. その時のがうまくあってなかったんだろうな.

多分, /dev/sdbと/dev/sdcは同じ型だったしfdiskで見ればアタリはつけられそう.

$ sudo fdisk -l /dev/sdb
Disk /dev/sdb: 1.8 TiB, 2000398934016 bytes, 3907029168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disklabel type: gpt
Disk identifier: D2D7EE18-BD3C-4E32-926B-D606E7056837

Device Start End Sectors Size Type
/dev/sdb1 4194304 3907028991 3902834688 1.8T Linux filesystem

sudo fdisk -l /dev/sdc
Disk /dev/sdc: 1.8 TiB, 2000398934016 bytes, 3907029168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disklabel type: gpt
Disk identifier: 3E260EEF-CEAB-4747-94DB-5E7F2296BD3F

Device Start End Sectors Size Type
/dev/sdc1 3999744 3907026943 3903027200 1.8T Linux filesystem

たしかに/dev/sdb1は"4194304"セクタからで, /dev/sdcは"3999744"セクタからで, 開始位置がずれている.

ただ, これで本当に同じ開始位置でいいのかの自信はなかった.

そこでLVMのヘッダがきっと先頭あたりにあるだろうことを期待して, 確認することを考えた. まず/dev/sdc1をダンプ

$ sudo hexdump -C /dev/sdc1|head
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000200  4c 41 42 45 4c 4f 4e 45  01 00 00 00 00 00 00 00  |LABELONE........|
00000210  e9 1e eb 95 20 00 00 00  4c 56 4d 32 20 30 30 31  |.... ...LVM2 001|
00000220  67 57 68 61 65 4e 49 64  65 31 32 77 4c 44 73 79  |gWhaeNIde12wLDsy|
00000230  53 6c 41 48 58 66 6e 54  42 38 44 6f 73 34 68 57  |SlAHXfnTB8Dos4hW|
00000240  00 00 f0 46 d1 01 00 00  00 00 10 00 00 00 00 00  |...F............|
00000250  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000260  00 00 00 00 00 00 00 00  00 10 00 00 00 00 00 00  |................|
00000270  00 f0 0f 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

一方で/dev/sdb1の先頭のダンプ.

$ sudo hexdump -C /dev/sdb1|head
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
0004c490  fe a7 00 00 fe a7 00 00  fe a7 00 00 fe a7 00 00  |................|
*
0004d460  ff a7 00 00 ff a7 00 00  ff a7 00 00 ff a7 00 00  |................|
*
0004e430  40 ad 00 00 40 ad 00 00  40 ad 00 00 40 ad 00 00  |@...@...@...@...|
*
0004f400  61 ad 00 00 61 ad 00 00  61 ad 00 00 61 ad 00 00  |a...a...a...a...|
*

見たまんまext4の先頭のメタデータ(inode bitmapとかGroup Descriptorのあたり)で確かに以前よりもパーティションの開始位置が後ろにずれているっぽい.

とりあえず"LABELONE"を頼りにgrepしてみる.

sudo hexdump -C /dev/sdb|grep -C5 LABELONE
*
7a0e1b10  c6 18 00 00 c6 18 00 00  c6 18 00 00 c6 18 00 00  |................|
*
7a0e2ae0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
7a100200  4c 41 42 45 4c 4f 4e 45  01 00 00 00 00 00 00 00  |LABELONE........|
7a100210  33 c2 fa 36 20 00 00 00  4c 56 4d 32 20 30 30 31  |3..6 ...LVM2 001|
7a100220  66 6b 70 66 41 44 48 5a  5a 47 4a 6d 49 30 79 4f  |fkpfADHZZGJmI0yO|
7a100230  54 75 54 6e 4d 5a 4a 38  55 6c 42 37 48 67 36 44  |TuTnMZJ8UlB7Hg6D|
7a100240  00 00 f0 46 d1 01 00 00  00 00 10 00 00 00 00 00  |...F............|
7a100250  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

見つかった. 最初のエラーにあった"fkpfAD-HZZG-JmI0-yOTu-TnMZ-J8Ul-B7Hg6D"っぽい文字列も見える. 信頼できそう.

(0x7a100200-0x200(/dev/sdb1の"LABELONE"の位置))/512 = 3999744 ということでやっぱり/dev/sdbと同じパーティションテーブルでよかった.

あとはfdiskするだけ

$ sudo fdisk /dev/sdb

Welcome to fdisk (util-linux 2.28).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.


Command (m for help): p
Disk /dev/sdb: 1.8 TiB, 2000398934016 bytes, 3907029168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disklabel type: gpt
Disk identifier: D2D7EE18-BD3C-4E32-926B-D606E7056837

Device Start End Sectors Size Type
/dev/sdb1 4194304 3907028991 3902834688 1.8T Linux filesystem

Command (m for help): d
Selected partition 1
Partition 1 has been deleted.

Command (m for help): n
Partition number (1-128, default 1):
First sector (34-3907029134, default 2048): 3999744
Last sector, +sectors or +size{K,M,G,T,P} (3999744-3907029134, default 3907029134):

Created a new partition 1 of type 'Linux filesystem' and of size 1.8 TiB.

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

最後にpvscan

$ sudo pvscan
PV /dev/sdb1 VG libvirt_lvm lvm2 [1.82 TiB / 672.11 GiB free]
PV /dev/sdc1 VG libvirt_lvm lvm2 [1.82 TiB / 1.82 TiB free]
PV /dev/sdd1 VG libvirt_lvm lvm2 [1.82 TiB / 1.82 TiB free]
Total: 3 [5.45 TiB] / in use: 3 [5.45 TiB] / in no VG: 0 [0 ]

認識された. よかった.

i3 likeなwayland compositorのswayを使ってみている

Wayland何度か試してみてまあぼちぼち動くが、いいdesktop環境にはなってないなという感じだった。そんなところへ、いま使っている普通のタイルWMなi3とcompatibleなswayというwayland上desktop環境(wayland composiotr)を見つけたので試している。いまのところ結構いい感じに使えている。

github.com

インストールして、とりあえず起動してみると、i3のconfigを読んでいるらしく、いままで通りに使える。べんりだ。

そのままでも、だいたい動くんだけど、 .i3/configから.sway/configにcopyして調整した方がいい。

ちゃんと動くの?

わりとだいたいのものが動いている。Firefoxで動画とかも見れている。i3と操作が変わらないので、悪くなった感じはあまりない。

時々、GPU hangして落ちてしまって困るけど、これはXの時でも起きてるし、そもそもkernelがRCだからそっちもあやしい。ただ、Xの時はcompositorであるcomptonが死ぬだけで、i3は大丈夫なんだけど、swayだとGPU hangしたらswayごと死んでしまう。しかたないけど。

アプリは動くの?

swayはwlcというwayland compositorを作るlibraryを使っていて、これがXWaylandを立ててXのものもWaylandに乗せて動かしてくれるっぽい。べんり

X固有のものは?

Xに依存したものとして、xrandrとかxbacklightコマンドがある。これらは当然Wayland上で動かないので置きかえる必要がある。

xrandrに対応する機能はswayのconfigで実現できている。

HDMI接続の画面だったら

output HDMI-A-1 res 1920x1080 pos 1920 0

 のように書いておくと、メイン画面の右側になる、という感じ。

configに書いてなくても、

swaymsg -t command output HDMI-A-1 res 1920x1080 pos 1920 0

 というコマンドで動的に認識できる。

画面の名前は

swaymsg -t get_outputs

で、出てくるJSONを見ればわかる。

xbacklightコマンドは使えないので、適当に/sys/class/backlightを使ってがんばる。多分、時間が経ったら画面が消えるとかも現状ない感じがする。

あとタスクトレイが現状動いていない。まあなくてもいいやってことで忘れている。

壁紙は?

これもswayのoutputコマンドで設定できる

swaymsg -t command output eDP-1 bg wallpapers/foo.jpg fit

 こんな感じ。

i3との違いは?

i3でlauncherとしてrofiを使っている。これがswayだとうまく動かない。floatしてるwindowに対して入力できない?っぽい。裏にwindowいると、入力できるのでinputの当たり判定かなんかだろうか。"-normal-window"引数つけておけば動くのでそうしてる。

Rofi

入力deviceの設定も、swayのconfigで出きる。こんな感じ。device nameは"swaymsg -t get_inputs"で

input "2:7:SynPS/2_Synaptics_TouchPad" {
  accel_profile adaptive
  natural_scroll enabled
}

ところでWaylandのよさは出てるの?

よくわからない…。結局ほとんどのものはXWayland経由になってるし、directにwaylandしゃべってるやつは多分いない。入出力まわりよくなってんのかな? 劇的な体感はないけど、まあおもしろいし動くし使ってみている。Waylandでこんな動くんだな、と思うと楽しいのは楽しい。

「SDカード向け」ではないSDCardFSの正体

最近GoogleがSDカード向けにファイルシステムを作っている, というような話がありました.

juggly.cn

記事中に

ファイルシステムのネーミングからして、SD Card FS は SD / Micro SD カード専用のファイルシステムだと見られます。

とありますが, sdcardfsはそんな文字通りに「SDカードをターゲットにした」ものではありません.

そもそも

アドバンテージとして挙げられたのは一連のファイル操作のシステムコールをユーザー空間を行き来することなく実行できることで、一連の命令はカーネルとハードウェアの間でダイレクトにやり取りされ、これにより、コンテキストスイッチの影響を受けることがなくなり、ファイル操作のパフォーマンスが大幅に向上するそうです。

この部分がなんか変です. 普通「ユーザー空間を行き来」なんてしません. なにか間違って伝わっているような気がします.

では, SDCardFSとはどういうFSなのか, なぜ「SDカード性能が向上する」と言われているのか解き明かしていきます.

"/sdcard"の呪い

初期のAndroid端末の多くにはSDカードを挿すスロットがついていました. SDカード中のファイルへは, "/sdcard"からアクセスできました. アプリは"/sdcard"の下にファイルを作り, 読み書きすることで端末内部の「小さい領域」を節約して, 「外部デバイスの広い領域」を活用することができました.

結局, 多くのAndroid端末がSDカードスロットを持たなくなったようです*1. そのような端末でも, "/sdcard"は存在しています. この領域はSDカードを指す代わりに, 端末内部のストレージを指しています. こうしておけば, 外部ストレージを使いたいアプリが, SDカードの有無にかかわらず同じように動作することができますね.

一般にSDカードはVFATでフォーマットされています. 一方, Android (Linux)で用いられるファイルシステムExt4(時々F2FS)です. VFATとExt4とは機能に違いがあります. 特に2つの大きな違いがあります.

1つ目は, ファイル名の大文字・小文字の扱いです. VFATではファイル名中の大文字・小文字を区別しませんが, Ext4では大文字・小文字が厳密に区別されます. たとえば, "cat.JPG"という猫の画像ファイルを保存したとします. VFATであれば"JPG"を小文字にして, "cat.jpg"でアクセスしてもファイルを読むことができます. 一方Ext4では, "cat.jpg"でアクセスすると"JPG"部分の違いから, そんなファイルはない!と言われてしまいます.

2つ目の違いは, アクセス権限の取扱いです. Linuxファイルシステムでは伝統的なUNIXのファイルアクセス制御が使われています. すなわち, 各ファイルにその所有者, 所有グループがあり, 所有者・所有グループ・その他のユーザがそれぞれ読みこみ・書きこみ・実行の何ができるかがファイルシステムに記録されています. 一方で, もともとMS-DOSファイルシステムであるVFATには, そのようなアクセス権限情報は記録することができません.

1つ目の大文字・小文字の取扱いの違いは, Ext4上でアプリの互換性に影響をおよぼします. これまで「全部小文字でもアクセスできてた」アプリが, "/sdcard"が内部フラッシュを指す端末では急に動かなくなります. アプリの互換性はなるべく守らなければなりません.

2つ目のアクセス権限の違いは, VFAT上でAndroidにおけるセキュリティ機能の制限を生んでしまいます. Androidのセキュリティの詳細は割愛しますが, WRITE_EXTERNAL_STORAGEという権限を持ったアプリは"/sdcard"下のデータを読み書きすることができます. VFATの場合, ファイルの所有者がどのアプリかを記録できないので, OSレべルではアプリは"/sdcard"下のどんなファイルでも読めてしまいます. すなわち, 他のアプリの"/sdcard"下のデータを「盗み見る」ことも可能となってしまいます. とは言え, 「広いSDカード領域」を活用するためには, この権限が必要となるわけでアプリとしてはこの権限を要求しないわけにもいきません.

ここでジレンマに陥ります. 互換性を保とうと思えば, Ext4(などLinux固有のファイルシステム)を使うわけにはいきません. 一方でVFATではセキュリティ機能がうまくいきません. はてさて, どうしましょう.

ファイルシステムの「ラッピング」

VFATがアクセス権限を保存してくれない? なら, 動的に「アクセス権限を合成」してやればいいんじゃよ.

VFATでLinuxのセキュリティチェックをうまく適用できないのは, VFATがファイルの所有者情報を付けてくれないせいです. ならば, VFATをラップして, 所有者情報を付けてあげれば, OSのセキュリティチェックが適切に動きます.

イデアはこうです.

  • "/sdcard"下に, アプリごとにディレクトリを作る
  • アプリ固有のディレクトリ下のファイルに, アプリの所有者情報を合成する
  • アプリは自分のディレクトリの下なら自由に読み書きできるが, 他のアプリのファイルにはアクセスできなくなる (OSが保証)

f:id:meech:20170121053222p:plain

この「ラッピング」は現在のAndroidでは, LinuxFUSEという機能を使って実装されています. FUSEとはユーザランドで, Linuxファイルシステムを実装するための機能です. 基本的にLinuxファイルシステムは, カーネルの中で実装されていますが, カーネル内でのプログラムは, C言語しか使えなかったり, デバッグが困難であったりと嫌な点が多くあります(でも, めっちゃ楽しいけどね!). ユーザランドファイルシステムを実装できれば, お好きな言語で, 様々なライブラリを活用して, おもしろいファイルシステムを作ることができる, というわけです. 有名なものでは, sshを使ってリモートのファイルにアクセスするsshfs, WindowsのNTFSファイルシステムの読み書きを可能にするntfs3gなどがあります.

さらに, FUSEを使ってLinuxの通常ファイルシステム上で大文字・小文字を区別しないファイルアクセスを実現することもできます. すると, "/sdcard"の「中身」が本当にSDカード(VFAT)であっても, 内部フラッシュ(ext4など)であっても, 上記2つの問題, 大文字・小文字を区別しないアプリの互換性と, アクセス権限の設定を解決することができます.

sdcardfs: FUSEからの脱出

これで話はめでたしめでたし…で終わらず, ここからが本題です.

FUSEを使うことで, アプリからはこれまで通りにファイルアクセスするだけで, ファイルシステムを拡張することができます. ここで通常のファイルアクセスと, FUSE上のファイルアクセスとを比較してみましょう.

通常のファイルシステムのケースを見てみましょう. アプリのファイルアクセスは, まずVFSにハンドルされます. VFSExt4など, そのディレクトリを担当するファイルシステムのコードを呼びだします.

一方FUSEでは, アプリがファイルにアクセスすると, その呼び出しがVFSを通ってFUSEドライバに行きます. ここでアプリがアクセスしようとしたファイルの情報が, カーネルからユーザランドFUSEプログラムに伝えられます. FUSEプログラムを受け取った情報をもとに, ssh先からファイルを取ってくるなどして, その情報をカーネルに送りかえします. カーネルは受け取った結果を, もとのアプリに渡してファイルアクセスが完了します.

ここでFUSEが入っていることで, 一度カーネルからユーザランドに戻っていることがわかります. 今回のような既存のファイルシステムをラップしている場合には, FUSE以外は完全に元のままで, FUSEの部分が純粋にオーバヘッドとなっています.

f:id:meech:20170121044423p:plain

実際のところ, そのオーバヘッドはどの程度でしょうか. すでに一年前にこのAndroidFUSE問題について言及している以下のブログを見てみましょう.

fixbugfix.blogspot.jp

この記事中の実験では,

  • 大きなファイル(700MB弱)のコピーで, FUSEExt4に対して, およそ17%書きこみ速度が低下
  • 大量の小さなファイル(5KB x 10000個, 全約50MB)のコピーに, ext4では17秒. FUSEでは1分以上かかる

という結果が出ています. ここで, 合計サイズの小さなファイル群の方が時間がかかっているのは, ファイルアクセス数, すなわちFUSEドライバがFUSEプログラムを呼びだす回数が小さなファイルコピーの方が多くなるためです.

さらに, さきほどのブログでは, Ext4側とFUSE側とで二重にファイルキャッシュが作られ, メモリを無駄に消費する問題と, FUSEの実装がタイムスタンプの更新がうまくできない問題などが指摘されています.

さあ, ここで本命SDCardFSの登場です. FUSEを使ってラップを作ってみたが遅いし, うまくカーネルの機能と協調できない. じゃあどうするか? カーネルの中に入れればいいんだ.

もう説明もほぼいらないですね. SDCardFSの正体は, これまでFUSEを使いユーザランドで実装されていた「ファイルシステムのラッピング」をカーネルに持っていったものです. VFATとの互換性を保ち, アクセス権限を動的に付与する機能を, パフォーマンスを抑え, カーネルとの協調性を保って実装したものです.

f:id:meech:20170121044510p:plain

VFAT Ext4/F2FS FUSE SDCardFS
互換性 o x o o
アクセス権限 x o o o
オーバヘッド なし なし 大きい 小さい

結果として, FUSEを使っていたオーバヘッドが小さくなる分だけ性能が向上します. しかしながら, 「SD Card FS」はSDカード専用のファイルシステム, というものではありません. SDカードの特性をうまく使って高速化とか, そういうものではありません. F2FSと比べるとかそういうものではありません. SDカードがいた領域を, あたかもそのままSDカードであったかのように見せる, それを速くしました. そういうものです.

宣伝

ね, ファイルシステムおもしろいですよね. Linuxでは, いろんなファイルシステムが実装されています. そんなファイルシステムたちがどのようなデータ構造で, どのようにデータをディスクに保存しているか気になってきましたよね? ちょうど現在, 発売中のSoftware Design 2017年2月号に「Linuxファイルシステムの教科書」と題して, Ext3, Ext4, XFS, F2FS, Btrfsの実装について特集記事を書きました. ファイルシステムの中身をもっと知りたくなったあなたはぜひ購入してみてください.

参考文献

Androidの"/sdcard"については, 知識がなかったので以下を参考にしています

www.xda-developers.com

Linuxのストレージ・メモリ管理に関する会議の議題として, sdcardfsでやっていることさらにVFSに統合していこうという話が提案されている. case-insensitveな探索ができるようにしようとか, アプリケーションレベルでpermissionのハンドルを行うLSM(Linux Security Module)が実装できないかなどが話されるようだ.

'[LSF/MM TOPIC] Getting rid of Android's FUSE/wrapfs hackery' - MARC

で, おれはコードを読みたいんだ. コードはどこだ

(ここから先は上級向けで飛んでいきます)

よっしゃまかせろ. コードはここだ. 読んでくぞ. (以下ほとんど読む過程のdumpで整理されていない)

fs/sdcardfs - kernel/common.git - Git at Google

まず, ファイルシステムに限らずLinux kernel moduleを読む時の鉄則として"__init"の付いた関数を見つけよう. こいつがmoduleの初期化をする. moduleの初期化で, だいたい大事なデータ構造を作ったりしてるので眺めておくと理解がスムーズに行く.

こいつの場合, main.cのinit_sdcardfs_fsがそれ.

fs/sdcardfs/main.c - kernel/common.git - Git at Google

 err = sdcardfs_init_inode_cache();
    if (err)
        goto out;
    err = sdcardfs_init_dentry_cache();
    if (err)
        goto out;
    err = packagelist_init();
    if (err)
        goto out;
    err = register_filesystem(&sdcardfs_fs_type);

ざっと見ると, 以下の3つのデータ構造が使われてそう. package listってなんだろね.

  • inode cache
  • dentry cache
  • packagelist

ファイルシステムの場合, 次に見ていきたいのは, struct file_operations. こいつらがファイルシステム上のファイルの挙動を決める. (mount処理は, だいたいoptionをなめていろいろ初期化してるだけなので, 頭から読むようなものではない, と思う)

const struct file_operations sdcardfs_main_fops = {
    .llseek     = generic_file_llseek,
    .read       = sdcardfs_read,
    .write      = sdcardfs_write,
    .unlocked_ioctl = sdcardfs_unlocked_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl   = sdcardfs_compat_ioctl,
#endif
    .mmap       = sdcardfs_mmap,
    .open       = sdcardfs_open,
    .flush      = sdcardfs_flush,
    .release    = sdcardfs_file_release,
    .fsync      = sdcardfs_fsync,
    .fasync     = sdcardfs_fasync,
};

/* trimmed directory options */
const struct file_operations sdcardfs_dir_fops = {
    .llseek     = generic_file_llseek,
    .read       = generic_read_dir,
    .iterate    = sdcardfs_readdir,
    .unlocked_ioctl = sdcardfs_unlocked_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl   = sdcardfs_compat_ioctl,
#endif
    .open       = sdcardfs_open,
    .release    = sdcardfs_file_release,
    .flush      = sdcardfs_flush,
    .fsync      = sdcardfs_fsync,
    .fasync     = sdcardfs_fasync,
};

変数名から, それぞれファイルとディレクトリに対してのoperationsだとわかる. case-insensitve searchしてくれるんだよな, という知識で眺めると, .iteratesdcardfs_readdirが気になる.

static int sdcardfs_readdir(struct file *file, struct dir_context *ctx)
{
    int err;
    struct file *lower_file = NULL;
    struct dentry *dentry = file->f_path.dentry;

    lower_file = sdcardfs_lower_file(file);

    lower_file->f_pos = file->f_pos;
    err = iterate_dir(lower_file, ctx);
    file->f_pos = lower_file->f_pos;
    if (err >= 0)        /* copy the atime */
        fsstack_copy_attr_atime(d_inode(dentry),
                    file_inode(lower_file));
    return err;
}

意外と面白いところはない. lower_fileで下位のFSのファイルをとってきて, そいつをiterate_dir()でなめる. atimeを上のディレクトリにcopy upする. それだけっぽい

じゃあ, 多分openだな, ということで, sdcardfs_openに行く.

 if(!check_caller_access_to_name(parent->d_inode, dentry->d_name.name)) {
        printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n"
                         "  dentry: %s, task:%s\n",
                         __func__, dentry->d_name.name, current->comm);
        err = -EACCES;
        goto out_err;
    }

    /* save current_cred and override it */
    OVERRIDE_CRED(sbi, saved_cred);

check_caller_access_to_name()に親ディレクトリと, アクセスしようとしているファイル名を渡してアクセス可能かチェックし, OVERRIDE_CREDで権限を書きかえるという流れだろう.

/* Kernel has already enforced everything we returned through
 * derive_permissions_locked(), so this is used to lock down access
 * even further, such as enforcing that apps hold sdcard_rw. */
int check_caller_access_to_name(struct inode *parent_node, const char* name)
{
    /* Always block security-sensitive files at root */
    if (parent_node && SDCARDFS_I(parent_node)->perm == PERM_ROOT) {
        if (!strcasecmp(name, "autorun.inf")
            || !strcasecmp(name, ".android_secure")
            || !strcasecmp(name, "android_secure")) {
            return 0;
        }
    }

    /* Root always has access; access for any other UIDs should always
     * be controlled through packages.list. */
    if (from_kuid(&init_user_ns, current_fsuid()) == 0) {
        return 1;
    }

    /* No extra permissions to enforce */
    return 1;
}

ところがcheck_caller_access_to_nameはあまり大したことをしていない. "autorun.inf"とかのやばそうなファイルへのアクセスを消り, rootにアクセス権を与え, それ以外は弾いているだけ. ふ〜ん…コメントが気になるけど, 一度OVERRIDE_CREDを見る.

OVERRIDE_CREDは結局以下の関数でstruct credを書きかえる.

const struct cred * override_fsids(struct sdcardfs_sb_info* sbi)
{
    struct cred * cred;
    const struct cred * old_cred;

    cred = prepare_creds();
    if (!cred)
        return NULL;

    cred->fsuid = make_kuid(&init_user_ns, sbi->options.fs_low_uid);
    cred->fsgid = make_kgid(&init_user_ns, sbi->options.fs_low_gid);

    old_cred = override_creds(cred);

    return old_cred;
}

cred->fsuid というのがVFSでのUID, fsgidは言うまでもなくGID. FUSE版の方はファイルのattributeをアプリの所有に書きかえる感じだったのが, kernel版では実行主体のcredentialを書きかえてるのが面白い.

コメントで気になったderive_permissions_locked()を見ていく……と思ったけど, そんな関数はない. 似た名前のget_derive_permissions()というのがあやしい. 多分移植の時にコメント書き変えてないんでしょ. よくある. よくする.

結局get_derived_permission_newにたどりつく. 親ディレクトparentと, アクセスするファイルdentryで呼ばれる. newdentryはrenameの時のため.

void get_derived_permission_new(struct dentry *parent, struct dentry *dentry, struct dentry *newdentry)
{
…
    inherit_derived_state(parent->d_inode, dentry->d_inode);

    /* Derive custom permissions based on parent and current node */
    switch (parent_info->perm) {
        case PERM_INHERIT:
            /* Already inherited above */
            break;
…         
        case PERM_ANDROID_DATA:
        case PERM_ANDROID_OBB:
        case PERM_ANDROID_MEDIA:
            appid = get_appid(sbi->pkgl_id, newdentry->d_name.name);
            if (appid != 0) {
                info->d_uid = multiuser_get_uid(parent_info->userid, appid);
            }
            break;
    }
}

inherit_derived_stateで, 親からなんか引き継ぐ. ほとんどそのままだが, 親がPERM_ANDROID_DATAなどだとなんかしている.

    /* This node is "/Android/data" */
    PERM_ANDROID_DATA,
    /* This node is "/Android/obb" */
    PERM_ANDROID_OBB,
    /* This node is "/Android/media" */
    PERM_ANDROID_MEDIA,

なるほどね〜, "/Android/data"とかの直下のファイルだった場合ね. この時, ファイル名から"appid"をとってきて, それをinfo_uidに設定している. multiuser_get_uidは, Androidのマルチユーザ対応っぽい. ここでアプリ個別のディレクトリの所有者が記録されるのかな.

inherit_derived_state()に行く

static void inherit_derived_state(struct inode *parent, struct inode *child)
{
    struct sdcardfs_inode_info *pi = SDCARDFS_I(parent);
    struct sdcardfs_inode_info *ci = SDCARDFS_I(child);

    ci->perm = PERM_INHERIT;
    ci->userid = pi->userid;
    ci->d_uid = pi->d_uid;
    ci->under_android = pi->under_android;
}

いかにもsdcardfs専用構造っぽいstruct sdcardfs_inode_infoが出てきた. SDCARDFS_Iは, struct inode*からstruct sdcardfs_inode_info*に変換するマクロだろ. FS, だいたいこんな書き方する.

permPERM_INHERIT, userdid, d_uid, under_androidは上から引き継ぎ. すなわち, こいつの下もこいつの親からの情報をひきついでいくことになる. それっぽい.

ここまでは, struct sdcardfs_inode_info しかいじられていない. これではアクセス制御になってないな.

get_derived_permissionの呼びだしあたりがあやしいのでgrepする.

struct dentry *sdcardfs_lookup(struct inode *dir, struct dentry *dentry,
                 unsigned int flags)
{
…
    /* save current_cred and override it */
    OVERRIDE_CRED_PTR(SDCARDFS_SB(dir->i_sb), saved_cred);
…
    ret = __sdcardfs_lookup(dentry, flags, &lower_parent_path, SDCARDFS_I(dir)->userid);
    if (IS_ERR(ret))
    {
        goto out;
    }
    if (ret)
        dentry = ret;
    if (dentry->d_inode) {
        fsstack_copy_attr_times(dentry->d_inode,
                    sdcardfs_lower_inode(dentry->d_inode));
        /* get drived permission */
        mutex_lock(&dentry->d_inode->i_mutex);
        get_derived_permission(parent, dentry);
        fix_derived_permission(dentry->d_inode);
        mutex_unlock(&dentry->d_inode->i_mutex);
    }

ここでもcredを変えて, __sdcardfs_lookupして, attributeとかをcopy upして, get_derived_permissionしてる. fix_derived_permissionかなあ

#define fix_derived_permission(x)    \
    do {                        \
        (x)->i_uid = make_kuid(&init_user_ns, SDCARDFS_I(x)->d_uid);  \
        (x)->i_gid = make_kgid(&init_user_ns, get_gid(SDCARDFS_I(x)));   \
        (x)->i_mode = ((x)->i_mode & S_IFMT) | get_mode(SDCARDFS_I(x));\
    } while (0)

あぁ〜確かに, inodeのuidとかgidが, struct sdcardfs_inode_infoに入ってたものに書きかえられてる. さっき, こっちではcredを書きかえるのか〜と思ったけれど, あれはlower FSを読む権限を獲得してるだけで, やっぱりファイルの所有情報書き変えてんのね.

アクセス権限のとこはわかったけど, case-insensitiveのとこはどうなってんだろ. やっぱdentry.cかな.

const struct dentry_operations sdcardfs_ci_dops = {
    .d_revalidate   = sdcardfs_d_revalidate,
    .d_release  = sdcardfs_d_release,
    .d_hash     = sdcardfs_hash_ci,
    .d_compare  = sdcardfs_cmp_ci,
    .d_canonical_path = sdcardfs_canonical_path,
};

専用の dentry_operationsがある. .d_compare = sdcardfs_cmp_ci とかそれっぽい.

static int sdcardfs_cmp_ci(const struct dentry *parent,
        const struct dentry *dentry,
        unsigned int len, const char *str, const struct qstr *name)
{
    if (name->len == len) {
        if (strncasecmp(name->name, str, len) == 0)
            return 0;
    }
    return 1;
}

なるほど見たまんま.

最後に, get_appidを見ておくか…

appid_t get_appid(void *pkgl_id, const char *app_name)
{
    struct packagelist_data *pkgl_dat = pkgl_data_all;
    struct hashtable_entry *hash_cur;
    unsigned int hash = str_hash(app_name);
    appid_t ret_id;

    mutex_lock(&pkgl_dat->hashtable_lock);
    hash_for_each_possible(pkgl_dat->package_to_appid, hash_cur, hlist, hash) {
        if (!strcasecmp(app_name, hash_cur->key)) {
            ret_id = (appid_t)hash_cur->value;
            mutex_unlock(&pkgl_dat->hashtable_lock);
            return ret_id;
        }
    }
    mutex_unlock(&pkgl_dat->hashtable_lock);
    return 0;
}

まあそりゃhashtableひくだけだよね. tableはpackage_to_appidってやつ. insert_str_to_int_lock()が追加している.

このpackagelist.cを見ているとどうやらconfigfsを使ってアプリ名->appidのマッピングが入っているっぽい.

static struct configfs_subsystem sdcardfs_packages_subsys = {
    .su_group = {
        .cg_item = {
            .ci_namebuf = "sdcardfs",
            .ci_type = &sdcardfs_packages_type,
        },
    },
};

static int configfs_sdcardfs_init(void)
{
    int ret;
    struct configfs_subsystem *subsys = &sdcardfs_packages_subsys;

    config_group_init(&subsys->su_group);
    mutex_init(&subsys->su_mutex);
    ret = configfs_register_subsystem(subsys);

configfs中のrootは"sdcardfs"

static struct configfs_group_operations sdcardfs_packages_group_ops = {
    .make_item  = sdcardfs_packages_make_item,
};

static struct config_item_type sdcardfs_packages_type = {
    .ct_item_ops    = &sdcardfs_packages_item_ops,
    .ct_group_ops   = &sdcardfs_packages_group_ops,
    .ct_attrs   = sdcardfs_packages_attrs,
    .ct_owner   = THIS_MODULE,
};

その中にディレクトリを作ることができる (ct_group_ops から)

static struct configfs_attribute package_appid_attr_add_pid = {
    .ca_owner = THIS_MODULE,
    .ca_name = "appid",
    .ca_mode = S_IRUGO | S_IWUGO,
    .show = package_appid_attr_show,
    .store = package_appid_attr_store,
};

static struct configfs_attribute *package_appid_attrs[] = {
    &package_appid_attr_add_pid,
    NULL,
};

その中に"appid"というファイルがある. ここに読み書きできる.

まとめると, "/sys/kernel/config/sdcardfs/app.name/appid"というファイルに数字を書くと, "app.name"->42といったマッピングがさっきのhashtableに登録される.

てことは, 誰かがユーザランド側でこれをやってくれるんだね. なるほどね.

ここまでコードがんがんはしてないけれど、データ構造がわかる感じになるので, もいちどよろしく.

*1:実際どの程度かわからぬですが, 確かに手元のNexus7にはSDカードスロットがない

Software Design 2017年2月号にファイルシステムの特集記事を書きました

Linuxファイルシステムの教科書」と題して、Software Design 2017年2月号第2特集を書きました。

Ext3, Ext4, XFS, F2FS, Btrfsと主たるFSを一通りおさえて、それぞれのデータ構造、そしてどうやってFSが壊れないように保護しているのかを書いています。

読んでFSに詳しくなろう。

ソフトウェアデザイン 2017年 02 月号 [雑誌]
 

 

Atomのlinter-spellで日本語が指摘されないようにするlinter-spell-cjkを書いた

最近AtomでGoを書いたりなどして、なかなかいいねと思ったので、日本語文書もこのままAtomでいくぞ〜と思ったら、spell checkに使っているlinter-spellが大暴れ。いたるところに下線がひかれ、なにがなんだかわっかんない。

こんな感じよ

https://raw.githubusercontent.com/naota/linter-spell-cjk/master/before.png

 

しかも、何が悪いんだと表示されるtipsを見ても、たとえば「くのがいやでーす」は、英語の辞書にないからだめだよ?ということしか言ってこない。こんなにあちこち線をひかれてもなにもわからん。ぐぐってみると、"spell check pluginを無効にしようね"とかあって、嘘でしょそんな〜っ、英語の部分はきっちりspell checkされたいでしょと思うわけです。

ってことで、日本語とかが指摘されないようにするプラグインlinter-spell-cjkを作った。こんな感じにすっきりしてくれる。これなら"exaciting"ってtypoってたことがわかってしまう。

https://raw.githubusercontent.com/naota/linter-spell-cjk/master/after.png

 

 

とりあえず公開した

 

atom.io

 

画像を見るとわかるように、現状では「Atomを」みたいに英語と日本語をまたがって引かれていた線は消えていない。このへんは将来の課題ということで・・・

 

 

 

しかし、linter-spell側にいろいろ手を入れずにその変更をするのは困難だと思われる。linter-spellは、まずprimary dictionary engineに全文を渡す。primaryは、tokenizeして、spell checkして、スペルミス候補の単語リストを返す。返ってきた単語は、追加辞書がさらにチェックして、「スペルミスではない」とか「追加修正候補はこれだ」とか言ってくる。linter-spell-cjkは、この追加辞書として機能して、渡された単語が全て日本語文字で構成されていたら、「これは辞書にあるOKな単語だ」と返している。

日英交ざった単語が降ってきた場合、1) 英語の部分をとりだす 2) 英語の部分をprimary辞書でcheck 3) 間違ってたら、候補を返す, ということをしたい。(1)はregexでOK。(2)はprimaryの辞書は現状わからんが、English hunspellを呼ぶよ、みたいな感じでよさそう。(3)が問題で、いまのlinter-spellだとrangeを変えられないので「primaryは、ここが単語って言ってきたけど、本当はもっと狭いこの部分が単語で、これが修正候補だよ」ということができない。

ということで、linter-spell側でprimary辞書くれて、rangeを狭めることができるようになるといいなあ。

あと、このpackageでは regexpunicode propertyを使ってて、単純にatomのbabelだと自動でそのへんをtranspileしてくれん(babel pluginが認識されてない?)ので、手でtranspileしてるんだけど、これってなんとかなんないんでしょうか。教えてください、atom packageにくわしい人

 

Gentoo made easy 〜 もしくはパッケージを更新しない自由について 〜

これはGentoo Advent Calendar 12/08分です。日付・・・・?なんのことです?

www.adventar.org

みなさん, emergeしてますか. emergeで困るのは謎のエラーです. たとえば以下のようなものを見たことがありませんか

Total: 35 packages (29 upgrades, 1 downgrade, 5 reinstalls), Size of downloads: 211,908 KiB

!!! Multiple package instances within a single package slot have been pulled
!!! into the dependency graph, resulting in a slot conflict:

app-text/hunspell:0

  (app-text/hunspell-1.5.3:0/1.5::gentoo, ebuild scheduled for merge) pulled in by
    (no parents that aren't satisfied by other packages in this slot)

  (app-text/hunspell-1.4.2:0/1.4::gentoo, installed) pulled in by
    >=app-text/hunspell-1.2.1:0/1.4= required by (app-text/enchant-1.6.0:0/0::gentoo, installed)
                             ^^^^^^^
    (and 1 more with the same problem)

NOTE: Use the '--verbose-conflicts' option to display parents omitted above

It may be possible to solve this problem by using package.mask to
prevent one of those packages from being selected. However, it is also
possible that conflicting dependencies exist such that they are
impossible to satisfy simultaneously.  If such a conflict exists in
the dependencies of two different packages, then those packages can
not be installed simultaneously. You may want to try a larger value of
the --backtrack option, such as --backtrack=30, in order to see if
that will solve this conflict automatically.

For more information, see MASKED PACKAGES section in the emerge man
page or refer to the Gentoo Handbook.

これはどのようなことが起きているのでしょうか? なんでemergeはビルドしてくれないのでしょうか.

sub-SLOT dependency

さっきのエラーを解説する前に, sub-SLOT dependencyという機能について見ていきます.

多くのプログラムはライブラリを使用します. すると, そのパッケージはそのライブラリに依存することになります. より詳しく言えば, 「ビルドした時点でのライブラリのABI」に依存することになります.

たとえば, パッケージXがライブリLを使うとして, L-1.0がある状態でXがビルドされると, XはL-1.0のABIに依存することになります. すなわち, ライブラリパッケージLが, L-1.0からL-1.1にアップデートされると, Xの動作は壊れてしまう可能性があります.

この問題の1つの解決策として, preserved-rebuildという機能がGentooでは提供されていました. これはXがL-1.1に対してリビルドされるまで, L-1.0のライブラリを残しておくという機能です.

しかし, この機能はpreserve-rebuild状態になったパッケージがリビルドされるまで, ライブラリが残ってしまいます.

こうした場合に依存関係を「壊して」, パッケージXをリビルドさせる方法はないでしょうか? ひとつの方法として, パッケージXに明示的に"=xxx-yyy/L-1.0"に依存させるという方法があります. すると, L-1.1にバージョンが上がると, Xの依存が壊れるので, 1) Lのバージョンを上げないか, 2) Xをリビルドするかで解決することになります.

だが, しかし, それは, Gentoo ではないんだなあ

Gentooは選択なので, パッケージXをL-1.0に対してビルドするか, L-1.1に対してビルドするかを選択できるべきです.

そこで, sub-SLOT dependencyという機能が導入されています. この機能はGentooのSLOT機能を拡張し, ABIに関する依存関係を記録できるようにしたものです. この機能が特徴的なのは, 依存関係がビルド時に書きこまれる, ということです.

たとえば, パッケージXに"xxx-yyy/L:0="と書くと, ビルド時に"xxx-yyy/L:0/1.0"(/の後はsub-SLOT)があれば, 依存に"xxx-yyy/L:0/1.0"が書きこまれます. 同様にビルド時のパッケージが"xxx-yyy/L:0/1.1"であれば, 依存に"xxx-yyy/L:0/1.1"が書かれます.

なぜemergeが文句を言うのか

それでは, sub-SLOT dependencyが分かったとして, なぜemergeが文句を言うのか見ていきましょう. エラーを再掲すると

app-text/hunspell:0

  (app-text/hunspell-1.5.3:0/1.5::gentoo, ebuild scheduled for merge) pulled in by
    (no parents that aren't satisfied by other packages in this slot)

  (app-text/hunspell-1.4.2:0/1.4::gentoo, installed) pulled in by
    >=app-text/hunspell-1.2.1:0/1.4= required by (app-text/enchant-1.6.0:0/0::gentoo, installed)
                             ^^^^^^^
    (and 1 more with the same problem)

こういうエラーです. なにを言っているのかちゃんと見てみましょう.

ここでは, "app-text/hunspell"が"app-text/hunspell-1.5.3:0/1.5"にupdateしようとしているのに対して, "app-text/enchant-1.6.0"が">=app-text/hunspell-1.2.1:0/1.4="と, hunspellのsub-SLOTに依存しているため, enchantの依存関係が壊れるのでemergeを行なうことができないよ!!と言っています.

Portageは現状, sub-SLOTによるリビルドを自動的に実行しません. これは, ある種パッケージのバージョン固定(ここではhunspell)を守るためと言うことができます.

いや, でも, 実際のところ, わりとどうでもいいこと多いよね. 自動的にやってほしくない?

autoemergeによる解決

っということで, こうしたコンフリクトを見つけて, 自動的に解決するスクリプトを書きました.

ここにあるよ. https://github.com/naota/emerge-wrapper/

この autoemerge スクリプトは, 以上のようなエラーを自動で解決します. しかも, この解決はemergeに"--reinstall-atoms="を自動的に追加して, 指定したatomにマッチするパッケージをrebuildしていいよ〜とPortageに伝えることで, 依存の解決を目指し, emergeの機能に閉じているので, 安心して使うことができます.

では, さきほどのケースではどのように使いどのように動くのか見ていきましょう. 中心となるのは"autoemerge"というスクリプトです. 以下のようにしてautoemergeを動かします. emergeと同じ引数を認識します. (というか, そのままemergeに渡していきます)

$ sudo python3.5 ./autoemerge -uDN -j2 world --keep-going --with-bdeps=y

するとemergeが依存を解決し, 以下のエラーを出します.

dev-lang/ocaml:0

  (dev-lang/ocaml-4.04.0:0/4.04.0::gentoo, ebuild scheduled for merge) pulled in by
    >=dev-lang/ocaml-4.04_beta:=[ocamlopt?] required by (dev-ml/camlp4-4.04_p1:0/4.04_p1::gentoo, ebuild scheduled formerge)
    ^^               ^^^^^^^^^

  (dev-lang/ocaml-4.03.0-r1:0/4.03.0::gentoo, ebuild scheduled for merge) pulled in by
    >=dev-lang/ocaml-3.12:0/4.03.0=[ocamlopt] required by (dev-ml/sexplib-113.33.00:0/113.33.00::gentoo, installed)
                         ^^^^^^^^^^
    (and 4 more with the same problem)

autoemergeはこれを解析して, 以下のようなログを出して, 適宜"--reinstall-atoms==dev-ml/sexplib-113.33.00"などをつけて再度依存関係を解析させます

reinstall (dev-ml/sexplib-113.33.00:0/113.33.00::gentoo, installed) for (dev-lang/ocaml-4.03.0-r1:0/4.03.0::gentoo, ebuild scheduled for merge)
reinstall (dev-ml/ocaml-re-1.7.1:0/1.7.1::gentoo, installed) for (dev-lang/ocaml-4.03.0-r1:0/4.03.0::gentoo, ebuild scheduled for merge)
reinstall (dev-ml/pcre-ocaml-7.2.3:0/7.2.3::gentoo, installed) for (dev-lang/ocaml-4.03.0-r1:0/4.03.0::gentoo, ebuild scheduled for merge)
reinstall (dev-ml/pcre-ocaml-7.2.3:0/7.2.3::gentoo, installed) for (dev-lang/ocaml-4.03.0-r1:0/4.03.0::gentoo, ebuild scheduled for merge)
reinstall (dev-ml/ocaml-re-1.7.1:0/1.7.1::gentoo, installed) for (dev-lang/ocaml-4.03.0-r1:0/4.03.0::gentoo, ebuild scheduled for merge)

この作業は再帰的に実行され, 全ての依存が解決された時点で, exec()システムコールでemergeが実行され, あたかも最初から適切な--reinstall-atomsをつけていたかのように, emergeが実行されます.

その他, emergeによってUSEフラグが更新された時に自動的にdispatch-confを呼びだして, emergeを再実行するなど, 手動でemergeを実行する様々な手間が省略されています.

さあ, みなさんautoemergeを使ってらっく〜にGentooを更新していきましょう. (バグが出たら, 笑って報告してね)

sub-SLOTを認め, ビルドしない自由を持つのがGentooなのだ

この後は特に読まなくていいとこです

そもそも, sub-SLOT dependencyが必要なのは, パッケージに"=xxx-yyy/L-1.0"などと書けないためです. ところがこれは, 世の多くのパッケージマネージャでは問題になりません. なぜでしょうか.

なぜなら, そうしたパッケージマネージャは基本的に, 1つのパッケージに対してひとつのバージョンしか提供せず, パッケージリポジトリが規定するライブラリとアプリケーションとの組み合わせをある種強制する, いわば「帝国主義的パッケージマネージャ」であるからです

だがしかし, そのような帝国主義的パッケージマネージャでは生きてはいけないのだ. たとえばお持ちのプログラムがPerl-5.20に依存すればどうなるか? ほしいパッケージが最新のディストリビューションリポトリにはあるが, そのリポジトリではperl-5.22がふってくるならどうすればいいのか. リポジトリに屈して, Perl-5.20および全てのそこに依存するパッケージを自分でビルドするのか? はたまたお手持ちのスクリプトをがんばってPerl-5.22に対応させ, リポジトリ内の最新のもろもろのパッケージに対応させるのか? どちらかを行わせる, これが帝国主義リポジトリの限界なのだ.

だが, われわれGentooには自由がある. お望みであれば, 好きにperlを5.20に留め, それでいて関係ないパッケージは最新にする, それがGentooの自由だ. 帝国主義リポジトリは打倒されるべきではないか? 立ち上がれ, 市民よ. Gentooの光を世界に広めるのだ.