2016-04-23

Emacs25(MinGW64)+cygwin-mount.elでtramp経由でリモートファイルを開けない

diredでリモートのディレクトリリストは表示されてもファイルを開こうとすると次のようなエラーが。

Tramp: Encoding remote file ‘/scpx:user@example.jp:/home/user/tmp/test1.txt’ with ‘openssl enc -base64 <%s’...done
Tramp: Decoding local file ‘c:/app/cygwin/tmp/tramp.6920y_V.txt’ with ‘base64-decode-region’...done
Tramp: Inserting ‘/scpx:user@example.jp:/home/user/tmp/test1.txt’...failed
Removing old name: No such file or directory, /app/cygwin/app/cygwin/tmp/tramp.6920y_V.txt

調べていくとカレントディレクトリ(default-directory)によって結果が変わった。 scratchバッファからリモートファイルをfind-fileすると問題なく開いた。 tramp上のパスをdiredで開いて、そこから開こうとするとエラーになった。

  1. find-file
  2. insert-file-contents
  3. tramp-handle-insert-file-contents
  4. tramp-sh-handle-file-local-copy
  5. with-temp-file
  6. write-region

と追っていくと、次のようなコードでエラーが再現。

(let ((default-directory "/scpx:user@example.jp:/home/user/tmp"))
  (write-region nil nil "c:/app/cygwin/tmp/a.txt"))

default-directoryがローカルなら何事もなく成功する。

write-regionはCで書かれているので見てみると、中でexpand-file-nameを使ってファイル名を展開し、そのファイルに対して出力している。

絶対パスにしているならカレントディレクトリによって動作が変わるはずはない。

エラーメッセージとして/app/cygwin/app/cygwin/tmp/tramp.6920y_V.txtのような全くおかしなパスが表示されているので、念のためexpand-file-nameを調べたところ、おかしな挙動に気がついた。

  • (expand-file-name "c:/a.txt" "c:/")c:/a.txt
  • (expand-file-name "c:/a.txt" "/ssh:/")/a.txt

あれれ?

expand-file-nameもCで書かれていたので中身を見てみると、default-directoryを元にfind-file-name-handlerして実際にexpand-file-nameを処理する関数を求めていた。

あー、ローカルとリモートでファイルシステムが違うんだ。default-directoryがtrampパスの時tramp-sh-handle-expand-file-nameが使われるんだ。 で、tramp-sh-handle-expand-file-nameはWindowsのことをこれっぽっちも考慮していないからc:/aが/aになってしまうのね。

C:/app/cygwin/tmp/a.txt が tramp-sh-handle-expand-file-name によって /app/cygwin/tmp/a.txt になり、これがcygwin-mount.elによってcygwinルートディレクトリを基準にしたパスとして展開され、/app/cygwin/app/cygwin/tmp/a.txtになるわけだ。ヤレヤレ。

というか、そもそもexpand-file-nameにローカルの絶対パスを渡したときにtrampのexpand-file-nameが使われるってどうなのよ。 ここは展開対象パスがローカルの絶対パスなのだから、default-directoryに関わらずローカルのハンドラによってexpandするべきなんじゃないのか? というわけで、expand-file-nameにadvice-addしたのだけど、何故かうまく行かなかった。Cから呼んでいるからだろうか。

仕方ないのでtramp-sh-handle-expand-file-name側にadvice-addしてc:/がc:/のまま/にならないようにしたところ、リモートのファイルがtramp経由で開けるようになった。

(advice-add
 'tramp-sh-handle-expand-file-name :around
 (lambda (orig-func name &optional dir)
   (if (save-match-data (string-match "^[a-z]+:[/\\]" name))
       (expand-file-name name (file-name-directory name))
     (apply orig-func (list name dir)))))

ちなみに、リモート・ローカル間でコピーできない現象は次のページの tramp-do-copy-or-rename-file-out-of-band へのadviceで治った。

NTEmacs @ ウィキ - tramp を tramp-method “scp” で使うための設定 - @ウィキモバイル

最後に、trampのコードを追うためにはファイル名ハンドラのことを知っていないといけないのでしっかり押さえておくこと。

Pingback / Trackback