とりあえず、現在の母艦であるMac proにリポジトリを立てる形で運用を開始できた。
Ubuntu DesktopはParallelsで動かしている。
Subversionを介して、各種設定ファイル(.emacs, .bashrc, .clinit.cl etc)と制作中のプロジェクトファイルを共有できるようにした。
各種設定ファイルは設定ファイル内の環境判定分岐にて、環境毎の設定を実施する形にした。
またSubversionでリビジョン管理するものとしないものとがディレクトリ木構造の中で混在しているので、それはsvn:ignoreの設定にて対応することにした。
このポータブル制作環境自体を作るスクリプトをpe-init.shとして作成した。
このスクリプトを走らせれば、vanillaなUbuntuやOSXを使いなれた制作環境に自動的に変えてくれる。
環境毎の使い勝手の設定は例えばpe-setup.gnome.shなどとして用意した。これはgconftoolでkey_themeを変更したりする。
結構使えるようになってきたが、まだまだ細部の詰めが必要。
目標としては次のレベル分けをしている。これらの環境を確立することだけではなく、スクリプトを書いて、これらの環境がvanillaから自動的に構築できることが必要。
* あらゆる制作ファイルをSubversionで管理。どの環境からも使えるように。 [完了]
* shell, screen, terminal, emacs, firefoxを日本語入力含めて最低限使えるように。[完了]
* キーマッピングがどの環境でも完全に同じである。[これから]
* Emacsのライブラリがどの環境でも完全に同一である。[これから]
* Common Lispの環境が基本的に等価である。[これから]
* SSH環境が等価である。[これから]
これができると結構実用的に環境フリーになれる。
これを可能にしているベースは、生活環境がほぼEmacsの中であり、日本語入力がSKKであるということだろう。
それであっても、今回やってみて、「使える」ということと「実用に耐える」ということはおおきく違うなぁ、というのが実感だ。
特に大きいのが、Xというかgnomeだ。Linuxはサーバーとしてはもう随分長い付き合いだが、Desktopはまともに使ったことがない。これはこれでかなり勉強しないといけない状態。
2009年3月2日月曜日
2009年2月27日金曜日
【Subversion】UbuntuとOSXを使った現象論の整理
点滴をうったら多少元気になった。。。
Subversionの深掘りをしたいわけではなく、使うにあたってちゃんと知りたい、というのが目的であった。
その目的からすると、特に懸案であった文字エンコーディングの内部機構はずいぶんおさえられたので、あとは現象論でおさえればよいように思えてきた。現象論を整理してみよう。
日本語のみ探る。
ふむ。リポジトリとしてのOSXは、パッチ無しでも問題なさそうだ。
OSXの文字エンコーディング問題というのは、あくまでOSX上のWorking directoryの問題のようだ。
整理してみよう。
リポジトリ Ubuntu/GNU/Linux
クライアント パッチ済OSX
クライアント Ubuntu/GNU/Linux
という組み合わせは今までもいろいろなところで運用している。なのでこれは整合している。
今回分かったのは、
リポジトリ パッチ無しOSX/パッチ有りOSX
クライアント パッチ済OSX
クライアント Ubuntu/GNU/Linux
という組み合わせでも整合しそうだ、ということだ。
そして問題はクライアント側に集中しており、
ということがある。1番目が致命的。
短期的視点では、OSXがNFDを採用したのは失敗に思える。
よろよろ。
Subversionの深掘りをしたいわけではなく、使うにあたってちゃんと知りたい、というのが目的であった。
その目的からすると、特に懸案であった文字エンコーディングの内部機構はずいぶんおさえられたので、あとは現象論でおさえればよいように思えてきた。現象論を整理してみよう。
日本語のみ探る。
- パッチ無しリポジトリOSX
- メッセージ
- from:OSX:OK:NFC
- to:Ubuntu:OK
- from:Ubuntu:OK:NFC
- to:OSX:OK
- from:OSX:OK:NFC
- ファイル名
- from:OSX:NG:?:svn addはできるがsvn statusにて不整合状態とありsvn ciできない。
- from:Ubuntu:OK:db/revsはNFC
- to:OSX:NG:ファイルシステム上はNFD。Terminalでは化けないが、ScreenやEmacsでは化ける。ただしsvn statusすると、端末にかかわらず
$ svn status
? が.txt
! が.txt
$
という状態になる。
- from:OSX:NG:?:svn addはできるがsvn statusにて不整合状態とありsvn ciできない。
- メッセージ
ふむ。リポジトリとしてのOSXは、パッチ無しでも問題なさそうだ。
OSXの文字エンコーディング問題というのは、あくまでOSX上のWorking directoryの問題のようだ。
整理してみよう。
リポジトリ Ubuntu/GNU/Linux
クライアント パッチ済OSX
クライアント Ubuntu/GNU/Linux
という組み合わせは今までもいろいろなところで運用している。なのでこれは整合している。
今回分かったのは、
リポジトリ パッチ無しOSX/パッチ有りOSX
クライアント パッチ済OSX
クライアント Ubuntu/GNU/Linux
という組み合わせでも整合しそうだ、ということだ。
そして問題はクライアント側に集中しており、
- svn statusでNFC/NFD不整合が発生し、working directoryをまともに扱えない。
- TerminalはNFDのファイル名を化けなく表示できるが、screen -U、Emacsのdired、Emacsのshell modeは文字化けをおこす。
ということがある。1番目が致命的。
短期的視点では、OSXがNFDを採用したのは失敗に思える。
よろよろ。
【Subversion】Subversionにおけるエンコーディングの取扱い
これくらいのまとめを書くのに実は結構な量のソースを読んで七転八倒した。
ソース以外の設計図がないというのはつらい。
だいぶ見えてはきている。
さて、ここからどこまで調べるか。。。。もういいとするか。。。
しかし、精魂尽きた。。。土曜日のShibuya.lispに行けるのだろうか。。。
ソース以外の設計図がないというのはつらい。
Subversionのエンコーディング取扱
--------------------------
Subversionとして文字コード取り扱いの基礎となるのは、
static svn_error_t *
convert_cstring(const char **dest,
const char *src,
xlate_handle_node_t *node,
apr_pool_t *pool);
である。ここでxlate_handle_node_t *nodeが文字コード
の変換を規程するapr_xlate_t *handleを持っている。ち
なみにapr_xlateはAPRのI18N変換ライブラリである。
typedef struct xlate_handle_node_t {
apr_xlate_t *handle;
/* FALSE if the handle is not valid, since its pool is being
destroyed. */
svn_boolean_t valid;
/* The name of a char encoding or APR_LOCALE_CHARSET. */
const char *frompage, *topage;
struct xlate_handle_node_t *next;
} xlate_handle_node_t;
handleは次のよう。
struct apr_xlate_t {
apr_pool_t *pool;
char *frompage;
char *topage;
char *sbcs_table;
iconv_t ich;
};
ここでchar *frompageが変換元文字コードの指定、char
*topageが変換先文字コードの指定である。
さて、handleはget_ntoU_xlate_handle_nodeが作る。
static svn_error_t *
get_ntou_xlate_handle_node(xlate_handle_node_t **ret, apr_pool_t *pool)
{
return get_xlate_handle_node(ret, SVN_APR_UTF8_CHARSET,
SVN_APR_LOCALE_CHARSET,
SVN_UTF_NTOU_XLATE_HANDLE, pool);
}
これはWrapperであり、本体は次のもの。
static svn_error_t *
get_xlate_handle_node(xlate_handle_node_t **ret,
const char *topage, const char *frompage,
const char *userdata_key,
apr_pool_t *pool);
この中で、まずhandleをopenして、
apr_xlate_open(&handle, topage, frompage, pool);
apr_xlate_openによって、handleの中身がどのように
作られるのかというと、
new->ich = iconv_open(topage, frompage);
ということで、とどのつまりtopageとfrompageの組み
合わせで指定してiconvから取得しているのだ。
続いてxlate_handle_node t **retを初期化する。
*ret = apr_palloc(pool, sizeof(xlate_handle_node_t));
(*ret)->handle = handle;
(*ret)->valid = TRUE;
(*ret)->frompage = ((frompage != SVN_APR_LOCALE_CHARSET)
? apr_pstrdup(pool, frompage) : frompage);
(*ret)->topage = ((topage != SVN_APR_LOCALE_CHARSET)
? apr_pstrdup(pool, topage) : topage);
(*ret)->next = NULL;
とする。
さてここで作成されたxlate_handle_node_t型オブジェク
トのhandleメンバがapr_xlate_conv_bufferの引数
convsetとして使われる。
APU_DECLARE(apr_status_t) apr_xlate_conv_buffer(apr_xlate_t *convset,
const char *inbuf,
apr_size_t *inbytes_left,
char *outbuf,
apr_size_t *outbytes_left);
この関数の中身で文字コード変換をしている実体は、
iconvである。(ただしwi32にiconvがないので、それは、
apr_iconvという同梱されているものをつかう)
translated = iconv(convset->ich, (ICONV_INBUF_TYPE)&inbufptr,
inbytes_left, &outbufptr, outbytes_left);
さて、
convert_cstringの中身はわかった。iconvである。
それのtopageとfrompageがどう与えられるかを確認する。
代表的なのは、
svn_error_t *
svn_utf_cstring_to_utf8(const char **dest,
const char *src,
apr_pool_t *pool)
{
xlate_handle_node_t *node;
svn_error_t *err;
SVN_ERR(get_ntou_xlate_handle_node(&node, pool));
err = convert_cstring(dest, src, node, pool);
put_xlate_handle_node(node, SVN_UTF_NTOU_XLATE_HANDLE, pool);
SVN_ERR(err);
SVN_ERR(check_cstring_utf8(*dest, pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_utf_cstring_from_utf8(const char **dest,
const char *src,
apr_pool_t *pool)
{
xlate_handle_node_t *node;
svn_error_t *err;
SVN_ERR(check_utf8(src, strlen(src), pool));
SVN_ERR(get_uton_xlate_handle_node(&node, pool));
err = convert_cstring(dest, src, node, pool);
put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool);
return err;
}
の2つである。これらはほぼシンメトリックだ。
これらを呼び出しているのは例えば、svn_path_*だ。
svn_error_t *
svn_path_cstring_to_utf8(const char **path_utf8,
const char *path_apr,
apr_pool_t *pool)
{
svn_boolean_t path_is_utf8;
SVN_ERR(get_path_encoding(&path_is_utf8, pool));
if (path_is_utf8)
{
*path_utf8 = apr_pstrdup(pool, path_apr);
return SVN_NO_ERROR;
}
else
return svn_utf_cstring_to_utf8(path_utf8, path_apr, pool);
}
svn_error_t *
svn_path_cstring_from_utf8(const char **path_apr,
const char *path_utf8,
apr_pool_t *pool)
{
svn_boolean_t path_is_utf8;
SVN_ERR(get_path_encoding(&path_is_utf8, pool));
if (path_is_utf8)
{
*path_apr = apr_pstrdup(pool, path_utf8);
return SVN_NO_ERROR;
}
else
return svn_utf_cstring_from_utf8(path_apr, path_utf8, pool);
}
ここで重要なのは、APRの内部処理がUTF-8かどうかによっ
て、振舞いをかえているということだ。(ちなみに
svn_path_cstring_to_utf8がOSXでsvnがちゃんとうごく
ようにするためのcore foundationのパッチをあてると
ころ)
get_path_encoding(&path_is_utf8, pool)
これは、与えられたpath_*のエンコーディングではなく、
APRの内部エンコーディングがどうなっているかを問合
わせている。
これが、UTF-8の場合は、
svn_path_cstring_to_utf8 は
*path_utf8 = apr_pstrdup(pool, path_apr); するだけ。
svn_path_cstring_from_utf8 は
*path_apr = apr_pstrdup(pool, path_utf8); するだけ。
UTF-8じゃない場合は、
svn_path_cstring_to_utf8 は
svn_utf_cstring_to_utf8する。
svn_path_cstring_from_utf8 は
svn_utf_cstring_from_utf8する。
ということ。すなわち、
* APRの内部エンコーディングがUTF-8であるというこ
とは、その環境(OSなど)のエンコーディングが
UTF-8であるということの証左である。
* SVNの内部ではエンコーディングはUTF-8である。
* ただし、APR判定で環境がUTF-8の場合は、外部から
与えられたUTF-8バイト列を無変換で内部に取り込
む。APR判定で環境がUTF-8でない場合は、
svn_utf_cstring_*等によって変換処理をして内部
に取り込む。
ということだ。違う言い方をすると、
* UTF-8として内部に取り込まれる方式が二種類ある
が、いずれもNFD/NFCについては気にしておらず、
Subversionの中で、ファイル名やパス文字列を
NFD/NFCのどちらで扱っているかは、Subversionの
中では規程されておらず、Subversionを利用してい
る環境に依存する。
ということだな。
それでは、svn clientがどのようにしてworking
directoryに新規ファイルを追加するのか。そのときに
path名をどう扱っているのかを追ってみよう。
まずsvn addコマンドの本体は次の関数である。
/* This implements the `svn_opt_subcommand_t' interface. */
svn_error_t *
svn_cl__add(apr_getopt_t *os,
void *baton,
apr_pool_t *pool);
この中でいろいろな処理をするが、今関心である文字列
についていえば、
apr_array_header_t *targets;
なる構造が重要である。これを
SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
opt_state->targets,
pool));
によって作成した後、
for (i = 0; i < targets->nelts; i++)
{
const char *target = APR_ARRAY_IDX(targets, i, const char *);
svn_pool_clear(subpool);
SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
SVN_ERR(svn_cl__try
(svn_client_add4(target,
opt_state->depth,
opt_state->force, opt_state->no_ignore,
opt_state->parents, ctx, subpool),
NULL, opt_state->quiet,
SVN_ERR_ENTRY_EXISTS,
SVN_ERR_WC_PATH_NOT_FOUND,
SVN_NO_ERROR));
}
にて、svn_client_add4を読んで、entriesへの情報の追
加処理を実施している。
さて、ここでtargetsがどのように作られるか確認しよ
う。
svn_error_t *
svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets,
apr_getopt_t *os,
apr_array_header_t *known_targets,
apr_pool_t
*pool);
は、
svn_opt_args_to_target_array3(targets, os,
known_targets, pool);
のwrapperである。svn_opt_aargs_to_target_array3を
みてみよう。さすがにエンコーディングに関するコメン
トがあるので、そのまま掲載する。
svn_error_t *
svn_opt_args_to_target_array3(apr_array_header_t **targets_p,
apr_getopt_t *os,
apr_array_header_t *known_targets,
apr_pool_t *pool)
{
int i;
svn_error_t *err = SVN_NO_ERROR;
apr_array_header_t *input_targets =
apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
apr_array_header_t *output_targets =
apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
/* Step 1: create a master array of targets that are in UTF-8
encoding, and come from concatenating the targets left by apr_getopt,
plus any extra targets (e.g., from the --targets switch.) */
for (; os->ind < os->argc; os->ind++)
{
/* The apr_getopt targets are still in native encoding. */
const char *raw_target = os->argv[os->ind];
SVN_ERR(svn_utf_cstring_to_utf8
/* *****************************************
ここで一発utf8変換(しないかもだけど、をか
ける。UTF-8であることは保証される。
*****************************************/
((const char **) apr_array_push(input_targets),
raw_target, pool));
}
if (known_targets)
{
for (i = 0; i < known_targets->nelts; i++)
{
/* The --targets array have already been converted to UTF-8,
because we needed to split up the list with svn_cstring_split. */
const char *utf8_target = APR_ARRAY_IDX(known_targets,
i, const char *);
APR_ARRAY_PUSH(input_targets, const char *) = utf8_target;
/* ********************************
ここでknown_tagetsをばらして、
input_tagetsに吸収している。
******************************** */
}
}
/* Step 2: process each target. */
for (i = 0; i < input_targets->nelts; i++)
{
const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *);
const char *peg_start = NULL; /* pointer to the peg revision, if any */
const char *target; /* after all processing is finished */
int j;
/* Remove a peg revision, if any, in the target so that it can
be properly canonicalized, otherwise the canonicalization
does not treat a ".@BASE" as a "." with a BASE peg revision,
and it is not canonicalized to "@BASE". If any peg revision
exists, it is appended to the final canonicalized path or
URL. Do not use svn_opt_parse_path() because the resulting
peg revision is a structure that would have to be converted
back into a string. Converting from a string date to the
apr_time_t field in the svn_opt_revision_value_t and back to
a string would not necessarily preserve the exact bytes of
the input date, so its easier just to keep it in string
form. */
for (j = (strlen(utf8_target) - 1); j >= 0; --j)
{
/* If we hit a path separator, stop looking. This is OK
only because our revision specifiers can't contain
'/'. */
if (utf8_target[j] == '/')
break;
if (utf8_target[j] == '@')
{
peg_start = utf8_target + j;
break;
}
}
if (peg_start)
utf8_target = apr_pstrmemdup(pool,
utf8_target,
peg_start - utf8_target);
/* URLs and wc-paths get treated differently. */
if (svn_path_is_url(utf8_target))
/* *******************************
ここは(scheme)://(optional_stuff)という形
式をみているだけ。
******************************* */
{
/* No need to canonicalize a URL's case or path separators. */
/* Convert to URI. */
target = svn_path_uri_from_iri(utf8_target, pool);
/* ***************************
ここはいわゆるURI-encodeをするだけ。
UTF-8の部分。
*************************** */
/* Auto-escape some ASCII characters. */
target = svn_path_uri_autoescape(target, pool);
/* ***************************
ここもいわゆるURI-encodeをするだけ。
ASCIIの部分。
*************************** */
/* The above doesn't guarantee a valid URI. */
if (! svn_path_is_uri_safe(target))
return svn_error_createf(SVN_ERR_BAD_URL, 0,
_("URL '%s' is not properly URI-encoded"),
utf8_target);
/* Verify that no backpaths are present in the URL. */
if (svn_path_is_backpath_present(target))
return svn_error_createf(SVN_ERR_BAD_URL, 0,
_("URL '%s' contains a '..' element"),
utf8_target);
/* strip any trailing '/' */
target = svn_path_canonicalize(target, pool);
/* ***************************
target 一丁あがり。
*************************** */
}
else /* not a url, so treat as a path */
{
const char *apr_target;
const char *base_name;
char *truenamed_target; /* APR-encoded */
apr_status_t apr_err;
/* canonicalize case, and change all separators to '/'. */
SVN_ERR(svn_path_cstring_from_utf8(&apr_target, utf8_target,
pool));
/* *************************************
APRの内部表現に変換。
内部表現がUTF-8ならコピーするだけ。
************************************* */
apr_err = apr_filepath_merge(&truenamed_target, "", apr_target,
APR_FILEPATH_TRUENAME, pool);
if (!apr_err)
/* We have a canonicalized APR-encoded target now. */
apr_target = truenamed_target;
else if (APR_STATUS_IS_ENOENT(apr_err))
/* It's okay for the file to not exist, that just means we
have to accept the case given to the client. We'll use
the original APR-encoded target. */
;
else
return svn_error_createf(apr_err, NULL,
_("Error resolving case of '%s'"),
svn_path_local_style(utf8_target,
pool));
/* convert back to UTF-8. */
SVN_ERR(svn_path_cstring_to_utf8(&target, apr_target, pool));
/* *************************************
APRの内部表現からUTF-8に変換。
内部表現がUTF-8ならコピーするだけ。
************************************* */
target = svn_path_canonicalize(target, pool);
/* ***************************
target 一丁あがり。
後続にskip処理があるけどね。
*************************** */
/* If the target has the same name as a Subversion
working copy administrative dir, skip it. */
base_name = svn_path_basename(target, pool);
/* FIXME:
The canonical list of administrative directory names is
maintained in libsvn_wc/adm_files.c:svn_wc_set_adm_dir().
That list can't be used here, because that use would
create a circular dependency between libsvn_wc and
libsvn_subr. Make sure changes to the lists are always
synchronized! */
if (0 == strcmp(base_name, ".svn")
|| 0 == strcmp(base_name, "_svn"))
{
err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED,
err, _("'%s' ends in a reserved name"),
target);
continue;
}
}
/* Append the peg revision back to the canonicalized target if
there was a peg revision. */
if (peg_start)
target = apr_pstrcat(pool, target, peg_start, NULL);
APR_ARRAY_PUSH(output_targets, const char *) = target;
/* ***************************
targetをoutput_tagetsに登録。
*************************** */
}
/* kff todo: need to remove redundancies from targets before
passing it to the cmd_func. */
*targets_p = output_targets;
/* ***************************
targetsできあがり。
*************************** */
return err;
}
これでtargetsがどうできるのか理解できた。結局、
UTF-8の環境ならば、pathがNFDならNFDであるし、NFCな
らNFCということだ。
さて、これを受け取ってadd4が処理を実施する。
add4の呼び出し部分は
(svn_client_add4(target,
opt_state->depth,
opt_state->force, opt_state->no_ignore,
opt_state->parents, ctx, subpool),
であった。さきのtargetsの要素がtargetとして渡され
ている。
svn_client_add4 は、
svn_error_t *
svn_client_add4(const char *path,
svn_depth_t depth,
svn_boolean_t force,
svn_boolean_t no_ignore,
svn_boolean_t add_parents,
svn_client_ctx_t *ctx,
apr_pool_t *pool);
であり、これの主たる処理は、
err = add(path, depth, force, no_ignore, adm_access, ctx, pool);
である。引数のpathがそのままaddの引数のpathになる。
addのIFは、
static svn_error_t *
add(const char *path,
svn_depth_t depth,
svn_boolean_t force,
svn_boolean_t no_ignore,
svn_wc_adm_access_t *adm_access,
svn_client_ctx_t *ctx,
apr_pool_t *pool);
であり、addの対象がファイルであるときは(今はファイ
ルの場合のみを追う)。
err = add_file(path, ctx, adm_access, pool);
が処理本体となる。add_fileを見てみよう。
static svn_error_t *
add_file(const char *path,
svn_client_ctx_t *ctx,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
apr_hash_t* properties;
apr_hash_index_t *hi;
const char *mimetype;
svn_node_kind_t kind;
svn_boolean_t is_special;
/* Check to see if this is a special file. */
SVN_ERR(svn_io_check_special_path(path, &kind, &is_special, pool));
if (is_special)
mimetype = NULL;
else
/* Get automatic properties */
/* This may fail on write-only files:
we open them to estimate file type.
That's why we postpone the add until after this step. */
SVN_ERR(svn_client__get_auto_props(&properties, &mimetype, path, ctx,
pool));
/* Add the file */
SVN_ERR(svn_wc_add2(path, adm_access, NULL, SVN_INVALID_REVNUM,
ctx->cancel_func, ctx->cancel_baton,
NULL, NULL, pool));
/* **************************************
ここでsvn_wc_add2を読んでいる。これが本体
*************************************** */
/* ... 後略 ... */
}
というわけでpathをそのまま引き継ぎつつ、今度は
svn_wc_add2を呼んでいる。
svn_wc_add2をみてみよう。
svn_error_t *
svn_wc_add2(const char *path,
svn_wc_adm_access_t *parent_access,
const char *copyfrom_url,
svn_revnum_t copyfrom_rev,
svn_cancel_func_t cancel_func,
void *cancel_baton,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
apr_pool_t *pool)
{
const char *parent_dir, *base_name;
const svn_wc_entry_t *orig_entry, *parent_entry;
svn_wc_entry_t tmp_entry;
svn_boolean_t is_replace = FALSE;
svn_node_kind_t kind;
apr_uint64_t modify_flags = 0;
svn_wc_adm_access_t *adm_access;
SVN_ERR(svn_path_check_valid(path, pool));
/* *******************************
ここはpathに制御文字が入ってないか確認してる
だけ。
******************************* */
/* Make sure something's there. */
SVN_ERR(svn_io_check_path(path, &kind, pool));
/* *******************************
svn_io_check_pathの本体はio_check_path。
ここはpathにsvn_path_cstring_from_utf8
を一回かけた上で、
apr_stat(&finfo, path_apr, flags, pool);
をやる。
apr_statはflagsの値によって、
lstatまたはstatでファイルにあたる。
man 2 stat によると、引数として渡されるfname
のエンコーディングにたいする記述は存在しない。
なので、UTF-8としてもここでNFCで問い合わせるべ
きなのか、それともNFDで問い合わせるべきなのか
はOS次第である。OSXの場合は、このpathはNFDな
ので、statに与えるのもNFDである。statがそれで
正常に動作するかはわからない。後で実験してみよ
う。
******************************* */
/* ... 中略 ... */
if (adm_access)
SVN_ERR(svn_wc_entry(&orig_entry, path, adm_access, TRUE, pool));
/* *******************************
orig_entryにsvn_wc_entry_t オブジェクトを設
定する。その際、ファイルの場合、
SVN_ERR(svn_wc__adm_retrieve_internal(&dir_access, adm_access, path, pool));
を呼ぶ。
ここで、svn_wc_adm_retrieve_internalは
adm_accessをさぐりつつ、dir_accessを構成す
る。
if (associated->set)
*adm_access = apr_hash_get(associated->set, path, APR_HASH_KEY_STRING);
があるので、associatedが指し示す
svn_wc_adm_access_t型構造がset(hash)を持つ
ならば、、、ここよくわからない。というか、
svn_wc_adm_access_t型の使われ方をもっと理解
しないと理解が無理。そしてそれを理解するこ
とは、Subversion全てを理解することのような
気がする。その時間はかけられない。どうする
か。
******************************* */
else
orig_entry = NULL;
/* ... 中略 ... */
/* Split off the base_name from the parent directory. */
svn_path_split(path, &parent_dir, &base_name, pool);
/* *************************************
ここで、pathからbase_nameをとりだす。
ファイルの場合これがファイル名となる。
************************************* */
/* ... 中略 ... */
/* Now, add the entry for this item to the parent_dir's
entries file, marking it for addition. */
SVN_ERR(svn_wc__entry_modify(parent_access, base_name, &tmp_entry,
modify_flags, TRUE, pool));
/* *******************************
ここでbase_nameにて、entriesファイルの書き換え
を実行する。
******************************* */
/* ... 後略 ... */
}
さて、base_nameに辿りつくまでの間いろいろあるのだ
が、エンコーディングの変換は実施されていなさそうだ。
ひとつ気になるのは、すでにentriesに登録済のファイ
ルとのバッティングを調べるところがあるのだが、そこ
で何と何を比べているのかということをわかっていない
ということだ。もしかしたらそこで比較する際にエンコー
ディングの問題が発生しうるかもしれない。
ただ、OSXにおいても、たとえば"が.txt"をsvn addする
こと自体はできたはずなので、それは発生しないという
ことにしておく。OSXで問題が発生するのはsvn status
からだ。
さて、
SVN_ERR(svn_wc__entry_modify(parent_access, base_name, &tmp_entry,
modify_flags, TRUE, pool));
を見なければいけない。
svn_wc__entry_modifyのIFは、
svn_error_t *
svn_wc__entry_modify(svn_wc_adm_access_t *adm_access,
const char *name,
svn_wc_entry_t *entry,
apr_uint64_t modify_flags,
svn_boolean_t do_sync,
apr_pool_t *pool);
であり、まず、
apr_hash_t *entries, *entries_nohidden;
svn_boolean_t entry_was_deleted_p = FALSE;
/* Load ADM_ACCESS's whole entries file. */
SVN_ERR(svn_wc_entries_read(&entries, adm_access, TRUE, pool));
SVN_ERR(svn_wc_entries_read(&entries_nohidden, adm_access, FALSE, pool));
というようにentriesファイルを読み込む。
nameについては、
if (name == NULL)
name = SVN_WC_ENTRY_THIS_DIR;
こんな処理をした上で、
/* If the entry wasn't just removed from the entries hash, fold the
changes into the entry. */
if (! entry_was_deleted_p)
{
fold_entry(entries, name, modify_flags, entry,
svn_wc_adm_access_pool(adm_access));
if (entries != entries_nohidden)
fold_entry(entries_nohidden, name, modify_flags, entry,
svn_wc_adm_access_pool(adm_access));
}
として、entriesにnameという名前でentrをfold_entry
する。ちなみにfold_entryが何かというと、
/* Update an entry NAME in ENTRIES, according to the combination of
entry data found in ENTRY and masked by MODIFY_FLAGS. If the entry
already exists, the requested changes will be folded (merged) into
the entry's existing state. If the entry doesn't exist, the entry
will be created with exactly those properties described by the set
of changes. Also cleanups meaningless fields combinations.
POOL may be used to allocate memory referenced by ENTRIES.
*/
static void
fold_entry(apr_hash_t *entries,
const char *name,
apr_uint64_t modify_flags,
svn_wc_entry_t *entry,
apr_pool_t *pool);
ということ、この関数にてnameのエンコーディングがい
じられることはない。
そして最後に、
SVN_ERR(svn_wc__entries_write(entries, adm_access, pool));
にて、svn_wc__entries_writeにてentryを組み込んだ
entriesをファイルEntriesに書き出す。
ということで、svn_wc__entries_write を調べる必要が
ある。
svn_wc__entries_writeをみてみよう。
svn_error_t *
svn_wc__entries_write(apr_hash_t *entries,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
svn_error_t *err = SVN_NO_ERROR;
svn_stringbuf_t *bigstr = NULL;
apr_file_t *outfile = NULL;
apr_hash_index_t *hi;
svn_wc_entry_t *this_dir;
SVN_ERR(svn_wc__adm_write_check(adm_access));
/* Get a copy of the "this dir" entry for comparison purposes. */
this_dir = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR,
APR_HASH_KEY_STRING);
/* If there is no "this dir" entry, something is wrong. */
if (! this_dir)
return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
_("No default entry in directory '%s'"),
svn_path_local_style
(svn_wc_adm_access_path(adm_access), pool));
/* Open entries file for writing. It's important we don't use APR_EXCL
* here. Consider what happens if a log file is interrupted, it may
* leave a .svn/tmp/entries file behind. Then when cleanup reruns the
* log file, and it attempts to modify the entries file, APR_EXCL would
* cause an error that prevents cleanup running. We don't use log file
* tags such as SVN_WC__LOG_MV to move entries files so any existing file
* is not "valuable".
*/
SVN_ERR(svn_wc__open_adm_file(&outfile,
svn_wc_adm_access_path(adm_access),
SVN_WC__ADM_ENTRIES,
(APR_WRITE | APR_CREATE),
pool));
if (svn_wc__adm_wc_format(adm_access) > SVN_WC__XML_ENTRIES_VERSION)
{
apr_pool_t *subpool = svn_pool_create(pool);
bigstr = svn_stringbuf_createf(pool, "%d\n",
svn_wc__adm_wc_format(adm_access)); //#### ここでentriesファイルの中身たる文字列バッファを作成。
/* Write out "this dir" */
write_entry(bigstr, this_dir, SVN_WC_ENTRY_THIS_DIR, this_dir, pool); //#### 起点dir情報を書く。
for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) //#### 引数で与えられたentries(hash)をひとつづつ処理する。
{
const void *key;
void *val;
svn_wc_entry_t *this_entry;
svn_pool_clear(subpool);
/* Get the entry and make sure its attributes are up-to-date. */
apr_hash_this(hi, &key, NULL, &val); //#### apr_hash_thisでkeyに値を仕込む。apr_hash_thisは要調査。
this_entry = val;
/* Don't rewrite the "this dir" entry! */
if (! strcmp(key, SVN_WC_ENTRY_THIS_DIR ))
continue;
/* Append the entry to BIGSTR */
write_entry(bigstr, this_entry, key, this_dir, subpool); //#### ここでthis_entryを文字列に書き出している。keyが引数になっている。
}
svn_pool_destroy(subpool);
}
else
/* This is needed during cleanup of a not yet upgraded WC. */
write_entries_xml(&bigstr, entries, this_dir, pool);
SVN_ERR_W(svn_io_file_write_full(outfile, bigstr->data,
bigstr->len, NULL, pool), //#### ここでbigstrをoutfileに書き出し。
apr_psprintf(pool,
_("Error writing to '%s'"),
svn_path_local_style
(svn_wc_adm_access_path(adm_access), pool)));
err = svn_wc__close_adm_file(outfile,
svn_wc_adm_access_path(adm_access),
SVN_WC__ADM_ENTRIES, 1, pool);
svn_wc__adm_access_set_entries(adm_access, TRUE, entries);
svn_wc__adm_access_set_entries(adm_access, FALSE, NULL);
return err;
}
この関数の要点は、apr_hash_thisでhashから順次entry
を取り出して、write_entryでそれをバッファに書いて、
svn_io_file_write_fullでそれをファイルに書くという
こと。
apr_hash_thisは、"Get the current entry's details
from the iteration state." とのこと。
とすると、
apr_hash_this(hi, &key, NULL, &val);
は、keyについてそのままわたすだけ。
続いてwrite_entryを調べてみよう。
/* Append a single entry ENTRY to the string OUTPUT, using the
entry for "this dir" THIS_DIR for comparison/optimization.
Allocations are done in POOL. */
static void
write_entry(svn_stringbuf_t *buf,
svn_wc_entry_t *entry,
const char *name, //#### ここでkeyが渡される。
svn_wc_entry_t *this_dir,
apr_pool_t *pool)
{
const char *valuestr;
svn_revnum_t valuerev;
svn_boolean_t is_this_dir = strcmp(name, SVN_WC_ENTRY_THIS_DIR) == 0;
svn_boolean_t is_subdir = ! is_this_dir && (entry->kind == svn_node_dir);
assert(name);
/* Name. */
write_str(buf, name, pool); //#### 渡されたkeyをそのままwrite_strに渡す。
/* Kind. */
switch (entry->kind)
{
case svn_node_dir:
write_val(buf, SVN_WC__ENTRIES_ATTR_DIR_STR,
sizeof(SVN_WC__ENTRIES_ATTR_DIR_STR) - 1);
break;
case svn_node_none:
write_val(buf, NULL, 0);
break;
case svn_node_file:
case svn_node_unknown:
default:
write_val(buf, SVN_WC__ENTRIES_ATTR_FILE_STR,
sizeof(SVN_WC__ENTRIES_ATTR_FILE_STR) - 1);
break;
}
/* Revision. */
if (is_this_dir || (! is_subdir && entry->revision != this_dir->revision))
valuerev = entry->revision;
else
valuerev = SVN_INVALID_REVNUM;
write_revnum(buf, valuerev, pool);
/* URL. */
if (is_this_dir ||
(! is_subdir && strcmp(svn_path_url_add_component(this_dir->url, name,
pool),
entry->url) != 0))
valuestr = entry->url;
else
valuestr = NULL;
write_str(buf, valuestr, pool); //#### URLについてもentry->urlをそのままwrite_str。
/* Repository root. */
if (! is_subdir
&& (is_this_dir
|| (this_dir->repos == NULL
|| (entry->repos
&& strcmp(this_dir->repos, entry->repos) != 0))))
valuestr = entry->repos;
else
valuestr = NULL;
write_str(buf, valuestr, pool);
//... 中略 ...
/* Remove redundant separators at the end of the entry. */
while (buf->len > 1 && buf->data[buf->len - 2] == '\n')
buf->len--;
svn_stringbuf_appendbytes(buf, "\f\n", 2);
}
nameとentry->urlを処理しているのはwrite_strであった。
なのでwrite_strを調べてみよう。
/* If STR is non-null, append STR to BUF, terminating it with a
newline, escaping bytes that needs escaping, using POOL for
temporary allocations. Else if STR is null, just append the
terminating newline. */
static void
write_str(svn_stringbuf_t *buf, const char *str, apr_pool_t *pool)
{
const char *start = str;
if (str)
{
while (*str)
{
/* Escape control characters and | and \. */
if (svn_ctype_iscntrl(*str) || *str == '\\')
{
svn_stringbuf_appendbytes(buf, start, str - start);
svn_stringbuf_appendcstr(buf,
apr_psprintf(pool, "\\x%02x", *str));
start = str + 1;
}
++str;
}
svn_stringbuf_appendbytes(buf, start, str - start); //#### エスケープされた制御文字以外は、ここでappendbytesするだけ。
}
svn_stringbuf_appendbytes(buf, "\n", 1);
}
write_strは、svn_stringbuf_appendbytesでバイトを足すだけだ。
この枝はこれで葉。svn_wc__entries_writeにおける処理の次の枝は、
svn_io_file_write_full だ。
svn_id_file_write_full を見てみよう。
svn_error_t *
svn_io_file_write_full(apr_file_t *file, const void *buf,
apr_size_t nbytes, apr_size_t *bytes_written,
apr_pool_t *pool)
{
apr_status_t rv = apr_file_write_full(file, buf, nbytes, bytes_written);
#ifdef WIN32
#define MAXBUFSIZE 30*1024
if (rv == APR_FROM_OS_ERROR(ERROR_NOT_ENOUGH_MEMORY)
&& nbytes > MAXBUFSIZE)
{
apr_size_t bw = 0;
*bytes_written = 0;
do {
rv = apr_file_write_full(file, buf,
nbytes > MAXBUFSIZE ? MAXBUFSIZE : nbytes, &bw); //#### 実質ここでファイルに書き出している。
*bytes_written += bw;
buf = (char *)buf + bw;
nbytes -= bw;
} while (rv == APR_SUCCESS && nbytes > 0);
}
#undef MAXBUFSIZE
#endif
return do_io_file_wrapper_cleanup
(file, rv,
N_("Can't write to file '%s'"),
N_("Can't write to stream"),
pool);
}
これは、apr_file_write_fullのラッパーであった。
apr_file_write_fullは、
------
apr_status_t apr_file_write_full( apr_file_t * thefile,
const void * buf,
apr_size_t nbytes,
apr_size_t * bytes_written
)
Write data to the specified file, ensuring that all of the data is written before returning.
Parameters:
thefile The file descriptor to write to.
buf The buffer which contains the data.
nbytes The number of bytes to write.
bytes_written If non-NULL, this will contain the number of bytes written.
------
なのでたぶんbufのバイト列を書き出すだけということ
だろう。
さて、svn_wc__entries_writeとは何だったのか。
* svn_wc__entries_writeは、既に作成済みのentries(ハッ
* シュ)をentriesファイルに書き出す処理を実施するが、
* その処理過程にて、entries(ハッシュ)に格納されてい
* るnameなどは無加工である。
またずいぶん長かったが、svn addにてファイルを
working directoryに足すとき、.svn/entitiesファイル
に書かれるファイル名は、コマンドラインでの
$ svn add が.txt
の"が.txt"がNFDならNFD、NFCならNFCということがわかっ
た。
実際にOSXでsvn add が.txtしたときのentriesファイル
をみてみると、
$ od -t x1 -t c entries
... 前略 ...
0000360 36 34 31 61 63 61 64 66 33 0a 0c 0a e3 81 8c 2e
6 4 1 a c a d f 3 \n \f \n が ** ** .
0000400 74 78 74 0a 66 69 6c 65 0a 0a 0a 0a 61 64 64 0a
t x t \n f i l e \n \n \n \n a d d \n
0000420 0c 0a
\f \n
0000422
$
このようにNFCになっている。ということはコマンドラ
インの引数の取り扱いがOSXではNFCであるということだ
ろうか?
GDBで確認してみよう。
int main(int argc, char *argv [])
{
return (0);
}
をコンパイルして"が.txt"を引数にして実行して、GDBでみ
てみると、
(gdb) x/3cx argv[1]
0xbffff3c5: 0xe3 0x81 0x8c
たしかにNFCだ。しかしこれはGDBの所作かもしれないが、
まあよしとしちゃう。(疲弊しているのだ。。。)
ここがOSXでのNFD/NFC問題の一方の原因なのかもしれな
い。
readdirでディレクトリの中身を読取ると、ファイル名は
NFDで返ってくる。同じファイル名でsvn addするとその
引数はNFCで渡される。
だいぶ見えてはきている。
- Subversionの内部エンコーディングはUTF-8。ただし、それがNFDかNFCかは常に関知していなくて、それぞれそのまま扱う。
- Subversionの中でエンコーディングの変換をする場合は、APRの内部エンコーディング=環境のエンコーディングという仮定のもと、外部から受け取ったpathについてはUTF-8に変換する。
- 変換エンジンはiconvである。
- OSXは、コマンドラインからアプリへの引数の受け渡しはNFCのようだ。さきに確認したようにreaddirはNFDでファイル名を返す。このあたりでアンマッチを発生させている予感。
- あとstat(2)はNFDなのかNFCなのかも気になる。
- OSXでsubversionがまともに動くようにするためのpatchは、svn_path_cstring_to_utf8について、core foundationにあるNFCへの変換関数を必ず通るようにする、というものだ。
- ここで必ずNFCにするようにしておけば、Subversionの内部においては、常にNFCであることが担保されるのであろう。
- そうすると、ファイルシステム上のファイルは、本体であろうがtext-base/配下であろうがNFDであり、それをsubversionの目で見ると、entriesの内容含めてすべてNFCに統一されてみえるということだろう。
- CF patch無しのsvnにおける別の状況として、例えば、LinuxマシンでNFCの"が.txt"をcheck inして、それをcheck outした場合はどうなるのだろう。
- おそらく、OSXのファイルを作るAPIにおいて、外からNFCとして投入されたファイルの名前はNFDに自動変換されるのだろう。
- そして、entriesの中身はNFCなのだろう。
- ここでアンマッチが発生して使えないworking directoryが一丁あがりとなるのだろう。
さて、ここからどこまで調べるか。。。。もういいとするか。。。
しかし、精魂尽きた。。。土曜日のShibuya.lispに行けるのだろうか。。。
2009年2月23日月曜日
C言語におけるUTF-8の取り扱い確認
/*
ちょっと調査。
まずemacsでひらがなの「が」をファイルに書くとLinux
とOSXでは違いがあるのかどうか。そのファイルをodで比
べてみる。
まずOSX。
----
$ od -t x1 -t c HIRAGANA-LETTER-GA.txt
0000000 61 62 63 64 0a e3 81 8c 0a
a b c d \n が ** ** \n
0000011
----
続いてLinux。
----
$ od -t x1 -t c HIRAGANA-LETTER-GA.txt
0000000 61 62 63 64 0a e3 81 8c 0a
a b c d \n 343 201 214 \n
0000011
----
違いは無い。
さて、e3 81 8c をUTF-8にて解釈してみる。
ビットにすると、
----
CL-USER(13): (dolist (n (list #xe3 #x81 #x8c))
(format t "~b " n))
11100011 10000001 10001100
NIL
----
である。このビットパターンは、UTF-8としては
1110yyyy 10yxxxxx 10xxxxxx
であり、最大16bitのコードポイントを表現している。
具体的にコードポイント対応部分を抽出すると、
0011 000001 001100
さらにこれを右詰めoctetになおすと、
00110000 01001100
----
CL-USER(14): (dolist (n (list #b00110000 #b01001100))
(format t "~x " n))
30 4c
NIL
----
というわけで、これはNFCの「が」(U+304C)だ。
とどのつまりファイルの中身にはOSは関知していないか
ら、emacsやodが文字をUTF-8のNFCで取り扱っており、そ
こに一貫性がある、ということだろう。
続いてgccのプリプロセッサまでの処理における文字の取
り扱いを確認したい。
前章で実験したところ、gccはC99に完全準拠していなかっ
た。ソースファイルの識別子に多バイト文字が使われて
いた場合は、それをプリプロセス前に国際文字名
(\uxxxx)に変換しなければいけないのだが、それをやっ
てくれていないようだ。それを確認する。
*/
----
#include <stdio.h>
int main(void)
{
int が;
for (が=1;が<10;が++)
printf("%d ", が);
return (0);
}
----
/*
こんなソースでためす。これのod。
----
$ od -t x1 -t c i18n-character-name-2.c
0000000 23 69 6e 63 6c 75 64 65 20 3c 73 74 64 69 6f 2e
# i n c l u d e < s t d i o .
0000020 68 3e 0a 69 6e 74 20 6d 61 69 6e 28 76 6f 69 64
h > \n i n t m a i n ( v o i d
0000040 29 0a 7b 0a 20 20 20 20 20 69 6e 74 20 e3 81 8c
) \n { \n i n t が ** **
0000060 3b 0a 20 20 20 20 20 66 6f 72 20 28 e3 81 8c 3d
; \n f o r ( が ** ** =
0000100 31 3b e3 81 8c 3c 31 30 3b e3 81 8c 2b 2b 29 20
1 ; が ** ** < 1 0 ; が ** ** + + )
0000120 0a 20 20 20 20 20 20 20 20 20 20 70 72 69 6e 74
\n p r i n t
0000140 66 28 22 25 64 20 22 2c 20 e3 81 8c 29 3b 0a 0a
f ( " % d " , が ** ** ) ; \n \n
0000160 20 20 20 20 20 72 65 74 75 72 6e 20 28 30 29 3b
r e t u r n ( 0 ) ;
0000200 0a 7d 0a
\n } \n
0000203
$
----
これをgcc -Eで処理。
includeしているから長くなるが、とにかく変換はしていないようだ。
*/
----
# 1 "i18n-character-name-2.c"
# 1 "<built-in>"
# 1 "<command line>"
# 1 "i18n-character-name-2.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 64 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/_types.h" 1 3 4
# 27 "/usr/include/_types.h" 3 4
# 1 "/usr/include/sys/_types.h" 1 3 4
# 32 "/usr/include/sys/_types.h" 3 4
# 1 "/usr/include/sys/cdefs.h" 1 3 4
# 33 "/usr/include/sys/_types.h" 2 3 4
# 1 "/usr/include/machine/_types.h" 1 3 4
# 34 "/usr/include/machine/_types.h" 3 4
# 1 "/usr/include/i386/_types.h" 1 3 4
# 37 "/usr/include/i386/_types.h" 3 4
typedef signed char __int8_t;
... 中略 ...
static __inline int __sputc(int _c, FILE *_p) {
if (--_p->_w >= 0 || (_p->_w >= _p->_lbfsize && (char)_c != '\n'))
return (*_p->_p++ = _c);
else
return (__swbuf(_c, _p));
}
# 2 "i18n-character-name-2.c" 2
int main(void)
{
int が;
for (が=1;が<10;が++)
printf("%d ", が);
return (0);
}
---
/*
では、文字列定数の中だとちゃんとやってくれるのか?
というところで同じソースを変更して確認する
*/
----
#include <stdio.h>
int main(void)
{
int i;
for (i=1;i<10;i++)
printf("%d が", i);
return (0);
}
----
*/
/*
あり、だめだ。次のように、変換していない。
*/
----
前略 ...
static __inline int __sputc(int _c, FILE *_p) {
if (--_p->_w >= 0 || (_p->_w >= _p->_lbfsize && (char)_c != '\n'))
return (*_p->_p++ = _c);
else
return (__swbuf(_c, _p));
}
# 2 "i18n-character-name-3.c" 2
int main(void)
{
int i;
for (i=1;i<10;i++)
printf("%d が", i);
return (0);
}
----
/*
というわけで、gccの字句関係処理はC99の要求まんまに
実装されているのではなさそうだ。ソース文字集合が
UTF-8そのものである、という作りに見受けられる。
さて、次にファイル名の取り扱いを確認したい。ファイ
ルの中身とちがって、ここはOSの関与がある部分だ。
"が.txt"というファイルを作成する。これの中身を表示
するプログラムで振舞いを確認してみる。APIはCの標準
ライブラリを使う。これは、予測としては不一致なくう
まくいくはず。gccの標準ライブラリがfopenの引数たる
ファイル名をOSXならOSX向けに取り扱ってくれるという
予測だ。
----が.txt---
abcd
が
-------------
*/
----ga-opne.c----
#include <stdio.h>
int main(void)
{
FILE *fp;
fp = fopen("が.txt", "r");
if (fp == NULL)
printf("failed.\n");
else {
printf("Succeed.\n");
fclose(fp);
}
return (0);
}
--------
/*
----
$ gcc -std=c99 ga-open.c
$ ./a.out
Succeed.
$
----
うまくいった。
さて、ファイル名の取得はどうだろう。ディレクトリに
含まれるファイルの名前を取得する方法は標準Cにはない。
よって、ここから先は処理系次第となる。ただし処理系/環境
がPOSIXだとかSUSとかに対応しているなら、それらAPIを
使うということである程度のポータビリティは期待でき
る。
さて、この領域は、Advanced Programming in the UNIX
Environment (apue.2e)だろう、ということでapue.2eか
らサンプルをとってくる。
----ls1.c----
*/
#include "apue.h"
#include <dirent.h>
int
main(int argc, char *argv[])
{
DIR *dp;
struct dirent *dirp;
if (argc != 2)
err_quit("usage: ls directory_name");
if ((dp = opendir(argv[1])) == NULL)
err_sys("can't open %s", argv[1]);
while ((dirp = readdir(dp)) != NULL)
printf("%s\n", dirp->d_name);
closedir(dp);
exit(0);
}
/*
--------
apue.2eのサイトにあがっているソースをLeopard上でコ
ンパイルするには多少調整が必要。apue.2eが発刊された
ときは、10.3.xだったのだ。それから10.4、10.5とかわ
るなかでOSXはPOSIX対応を進めたため、ヘッダ関係で混
乱があるようだ。
いくつか調整作業をしてmakeと次のコマンドでls1.cをコ
ンパイルした。
gcc -ansi -I/Users/aka/scratch/c-ref/apue.2e/include -Wall -g -DMACOS -L../lib ls1.c ../lib/libapue.a -o ls1
で、そのls1で、が.txtを含むga-testディレクトリの中
身をOSから取得する。
(gdb) n
..
(gdb) p dirp->d_name
$10 = "..\000\000?\r\000\024\000\b\nが.txt\000*", '\0' <repeats 231 times>
(gdb) n
(gdb) p dirp->d_name
$11 = "が.txt\000*", '\0' <repeats 243 times>
(gdb) p/x dirp->d_name
$12 = {0xe3, 0x81, 0x8b, 0xe3, 0x82, 0x99, 0x2e, 0x74, 0x78, 0x74, 0x0, 0x2a, 0x0 <repeats 244 times>}
(gdb)
で、ここでメモリに入ってる
0xe3, 0x81, 0x8b, 0xe3, 0x82, 0x99
が、"が"なのだが、これを先程と同じ方法でUTF-8として
読み解くと、
U+304b U+3099
[HIRAGANA LETTER KA] [COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK]
であることがわかる。ここでNFDがあらわれた!
そのあとは、printfで%sしてるだけなので、この
UTF-8(NFD)が正しく表示されるかは、その表示を担当す
るものによりけりになる。上記のls1->gdb->emacs->screen->Terminal
では実は正常に表示されず、が.txtの"が"と"."の間に妙な文字が表示
されてしまう。(コピペした際に上のgdb出力のこの異常は消えちゃった)
Terminal直ならば問題なくて、
------------
$ ./ls1 ga-test
.
..
が.txt
$
------------
となる。
さて、ここまで来たところで、apue.2eのreaddirの出自
を確認する必要がある。
apue.2eによると、POSIX.1とのこと。
とするとmanにあるのか? man readdir、あった。
-----------
DIRECTORY(3) BSD Library Functions Manual DIRECTORY(3)
NAME
closedir, dirfd, opendir, readdir, readdir_r, rewinddir, seekdir, telldir --
directory operations
LIBRARY
Standard C Library (libc, -lc)
SYNOPSIS
#include <dirent.h>
...
-----------
するとdirentもmanにあるのか? あった。
-----------
DIR(5) BSD File Formats Manual DIR(5)
NAME
dir, dirent -- directory file format
SYNOPSIS
#include <sys/types.h>
#include <sys/dir.h>
DESCRIPTION
Directories provide a convenient hierarchical method of grouping files while
obscuring the underlying details of the storage medium. A directory file is
differentiated from a plain file by a flag in its inode(5) entry. It consists
of records (directory entries) each of which contains information about a file
...
-----------
で、
-----------
*/
struct dirent {
ino_t d_ino; /* file number of entry */
u_int64_t d_seekoff; /* length of this record */
u_int16_t d_reclen; /* length of this record */
u_int16_t d_namlen; /* length of string in d_name */
u_int8_t d_type; /* file type, see below */
char d_name[MAXPATHLEN]; /* name must be no longer than this */
};
/*
-----------
なそうであり、d_nameの中身がどうあるべきかは無い。
まあ、ファイルシステムは環境によって違うからなぁ。
というわけで、このPOSIX.1のreaddir経由でOXとアプリ
がファイル名をやりとりする場合は、エンコードについ
てはアプリ側がOSが何を選択しているかをどこかで知っ
た上で使わないといけないということだ。
*/
これで、Subversionにおけるファイル名の取り扱いを探る準備ができたのかなぁ。
こつこつ。
2009年2月21日土曜日
【Subversion】Subversionのディレクトリ構造
ソースを読むまえに、実際にSubversionを動かして、各種ファイルの挙動や役割を確認した。
こつこつ。
----------------------------
リポジトリの内部構造のまとめ
----------------------------
大枠のディレクトリ構造
----------------------
$ ls -laR
total 16
drwxr-xr-x 9 aka staff 306 2 17 14:17 .
drwxr-xr-x 5 aka staff 170 2 17 15:21 ..
-rw-r--r-- 1 aka staff 229 2 17 14:17 README.txt ; これはおきまりの説明ファイル。無内容。
drwxr-xr-x 5 aka staff 170 2 17 14:17 conf ; 接続(ra)関係の設定情報達が入っている。
drwxr-xr-x 2 aka staff 68 2 17 14:17 dav ; mod_dav_svn用ディレクトリ
drwxr-sr-x 10 aka staff 340 2 17 22:29 db ; リポジトリ本体ここにバージョン管理している情報がある。BDBかFSFSかで異なるのはこのディレクトリの中身のみ。
-r--r--r-- 1 aka staff 2 2 17 14:17 format ; ひとつの整数値。リポジトリレイアウトのバージョン番号。現在は5。これはリポジトリの方式のバージョン番号であり通常のリポジトリ運用では不変である。
drwxr-xr-x 11 aka staff 374 2 17 14:17 hooks ; フックスクリプトの設置場所(テンプレートおよびインストールしたもの)
drwxr-xr-x 4 aka staff 136 2 17 14:17 locks ; ロック管理のためのディレクトリただし1.2.xより上のバージョンでは使用していない。
./conf:
total 24
drwxr-xr-x 5 aka staff 170 2 17 14:17 .
drwxr-xr-x 9 aka staff 306 2 17 14:17 ..
-rw-r--r-- 1 aka staff 684 2 17 14:17 authz
-rw-r--r-- 1 aka staff 309 2 17 14:17 passwd
-rw-r--r-- 1 aka staff 1457 2 17 14:17 svnserve.conf
./dav:
total 0
drwxr-xr-x 2 aka staff 68 2 17 14:17 .
drwxr-xr-x 9 aka staff 306 2 17 14:17 ..
./db:
total 32
drwxr-sr-x 10 aka staff 340 2 17 22:29 .
drwxr-xr-x 9 aka staff 306 2 17 14:17 ..
-rw-r--r-- 1 aka staff 6 2 17 22:29 current ; 3つの整数が並んでいる。"6 5 1"など。頭は最新のリビジョンのようだ。
-r--r--r-- 1 aka staff 2 2 17 14:17 format ; 整数値がひとつ。"2"など。これは不変だ。FSFSのリポジトリ構造のバージョンか?
-rw-r--r-- 1 aka staff 5 2 17 14:17 fs-type ; ファイルシステムタイプ。"fsfs"など。
drwxr-xr-x 9 aka staff 306 2 17 22:29 revprops ; リビジョンに対するプロパティ達。(リビジョン管理されている)
drwxr-xr-x 11 aka staff 374 2 17 22:29 revs ; リビジョンの内容そのもの達。(リビジョン管理されている)
drwxr-xr-x 2 aka staff 68 2 17 22:29 transactions ; トランザクション処理において使われるのか??
-rw-r--r-- 1 aka staff 37 2 17 14:17 uuid ; UUIDがひとつ。不変。どうやらリポジトリの/に対するUUIDのようだ。
-rw-r--r-- 1 aka staff 0 2 17 14:17 write-lock ; 書き込みロックのための何かなのだろう。
./db/revprops:
total 56
drwxr-xr-x 9 aka staff 306 2 17 22:29 .
drwxr-sr-x 10 aka staff 340 2 17 22:29 ..
-rw-r--r-- 1 aka staff 50 2 17 14:17 0
-rw-r--r-- 1 aka staff 110 2 17 15:15 1
-rw-r--r-- 1 aka staff 105 2 17 19:03 2
-rw-r--r-- 1 aka staff 108 2 17 19:30 3
-rw-r--r-- 1 aka staff 109 2 17 19:47 4
-rw-r--r-- 1 aka staff 123 2 17 22:21 5
-rw-r--r-- 1 aka staff 111 2 17 22:29 6
./db/revs:
total 3288
drwxr-xr-x 11 aka staff 374 2 17 22:29 .
drwxr-sr-x 10 aka staff 340 2 17 22:29 ..
-rw-r--r-- 1 aka staff 115 2 17 14:17 0
-rw-r--r-- 1 aka staff 385 2 17 15:15 1
-rw-r--r-- 1 aka staff 418 2 17 19:03 2
-rw-r--r-- 1 aka staff 427 2 17 19:30 3
-rw-r--r-- 1 aka staff 317 2 17 19:47 4
-rw-r--r-- 1 aka staff 1648983 2 17 22:21 5
-rw-r--r-- 1 aka staff 733 2 17 22:29 6
./db/transactions:
total 0
drwxr-xr-x 2 aka staff 68 2 17 22:29 .
drwxr-sr-x 10 aka staff 340 2 17 22:29 ..
./hooks:
total 72
drwxr-xr-x 11 aka staff 374 2 17 14:17 .
drwxr-xr-x 9 aka staff 306 2 17 14:17 ..
-rw-r--r-- 1 aka staff 2015 2 17 14:17 post-commit.tmpl
-rw-r--r-- 1 aka staff 1638 2 17 14:17 post-lock.tmpl
-rw-r--r-- 1 aka staff 2255 2 17 14:17 post-revprop-change.tmpl
-rw-r--r-- 1 aka staff 1567 2 17 14:17 post-unlock.tmpl
-rw-r--r-- 1 aka staff 2934 2 17 14:17 pre-commit.tmpl
-rw-r--r-- 1 aka staff 2038 2 17 14:17 pre-lock.tmpl
-rw-r--r-- 1 aka staff 2764 2 17 14:17 pre-revprop-change.tmpl
-rw-r--r-- 1 aka staff 1979 2 17 14:17 pre-unlock.tmpl
-rw-r--r-- 1 aka staff 2137 2 17 14:17 start-commit.tmpl
./locks:
total 16
drwxr-xr-x 4 aka staff 136 2 17 14:17 .
drwxr-xr-x 9 aka staff 306 2 17 14:17 ..
-rw-r--r-- 1 aka staff 139 2 17 14:17 db-logs.lock
-rw-r--r-- 1 aka staff 139 2 17 14:17 db.lock
$
revsの構造
----------
まず、ファイル名が番号になっているがそれはrevision
番号であり、そのファイルに記載されいていることがそ
のrevisionを構成するのに必要な基本情報である。
基本情報、というのは、まずそのリビジョンを構成する
のに必要なすべてのディレクトリとファイルの配置に関
する情報は含まれている。これは一見冗長なように思え
るが、svn coするときは、常に配置に関する全ての情報
を渡してworking directoryを構成しなければならない
ので、そんなに無駄ではない。
つづいて、配置されるファイルの中身に関してもこのファ
イルの中に記載されている。その記述方法は次のとおり。
まず、新規ファイルなら中身はすべてここに格納される。
既存ファイルの修正ならば、修正が少なければ、既存ファ
イルに対する差分情報だけ記載される。ただし、差分元
がどのリビジョンなのかのidが一緒に記載される。
修正が多い場合は、新規ファイルがごとく全て記載され
る。
大枠の構造は次のとおり。
---- ---- ---- ----
DELTA
... (test-pdf の中身)
ENDREP
DELTA
... (test-file2 の中身)
ENDREP
id: 4.0.r5/1648331
type: file
count: 0
text: 5 0 723003 765532 d76ffb583e9cf560c70768ec5124aa69
cpath: /test-pdf
copyroot: 0 /
id: 3.0.r5/1648459
type: file
count: 0
text: 5 723016 925302 1128184 a59d4991a080313dcd6c5bd3d286e291
cpath: /test-file2
copyroot: 0 /
;; ここまでがファイルの中身情報
;; ここからがこのディレクトリの情報。
;; ディレクトリの中に含まれているものがプロパティ的に
;; 表現されている。また、それぞれの実体がidというかリ
;; ビジョンというかで表現されている。
PLAIN
K 8
test-dir
V 12
dir 2.0.r4/0
K 9
test-file
V 14
file 1.0.r3/70
K 10
test-file2
V 19
file 3.0.r5/1648459
K 8
test-pdf
V 19
file 4.0.r5/1648331
END
ENDREP
id: 0.0.r5/1648756
type: dir
pred: 0.0.r4/146
count: 5
text: 5 1648595 148 148 3ca827292b495f4de066b5732524ca7e
cpath: /
copyroot: 0 /
---- ---- ---- ----
ファイルの中身情報は具体的には次のような形式で記載される。
---- ---- ---- ----
DELTA 1 0 34
SVN&...(binary)...&THIS IS THE SAME YES SAME TEST FILE.
ENDREP
---- ---- ---- ----
revpropsの構造
--------------
revprops/配下のファイルのファイル名についてはrevsと
同様である。
中身は次のようにそのリビジョンのメタ情報がプロパティ
の形式で記載されている。
--------
$ cat test-repos.2/db/revprops/1
K 10
svn:author
V 3
aka
K 8
svn:date
V 27
2009-02-17T06:15:31.009624Z
K 7
svn:log
V 16
A first check in
END
$
--------
----------------------------------------
ワーキングディレクトリの内部構造のまとめ
----------------------------------------
大枠のディレクトリ構造
----------------------
$ ls -laR
total 0
drwxr-xr-x 3 aka staff 102 2 17 15:02 .
drwxr-xr-x 12 aka staff 408 2 17 22:28 ..
drwxr-xr-x 4 aka staff 136 2 17 15:14 test-repos
./test-repos:
total 8
drwxr-xr-x 4 aka staff 136 2 17 15:14 .
drwxr-xr-x 3 aka staff 102 2 17 15:02 ..
drwxr-xr-x 8 aka staff 272 2 17 18:57 .svn ; リポジトリの/ディレクトリに関する情報が入っている。
-rw-r--r-- 1 aka staff 22 2 17 15:10 test-file
./test-repos/.svn:
total 16
drwxr-xr-x 8 aka staff 272 2 17 18:57 .
drwxr-xr-x 4 aka staff 136 2 17 15:14 ..
-r--r--r-- 1 aka staff 381 2 17 18:57 entries ; .svnが設置されているディレクトリの内容に関する情報
-r--r--r-- 1 aka staff 2 2 17 15:02 format ; 整数値ひとつ。"8"など。通常の運用においては不変。
drwxr-xr-x 2 aka staff 68 2 17 15:02 prop-base ; このディレクトリのプロパティについてcheck out時の情報。
drwxr-xr-x 2 aka staff 68 2 17 15:02 props ; このディレクトリのプロパティについての情報。
drwxr-xr-x 3 aka staff 102 2 17 15:15 text-base ; このディレクトリの内容(ファイルとかディレクトリとか)に関するcheck out時の情報。
drwxr-xr-x 5 aka staff 170 2 17 18:57 tmp ; svnがローカルでいろいろ変更を加えるときの作業用一時ディレクトリ。
./test-repos/.svn/prop-base:
total 0
drwxr-xr-x 2 aka staff 68 2 17 15:02 .
drwxr-xr-x 8 aka staff 272 2 17 18:57 ..
./test-repos/.svn/props:
total 0
drwxr-xr-x 2 aka staff 68 2 17 15:02 .
drwxr-xr-x 8 aka staff 272 2 17 18:57 ..
./test-repos/.svn/text-base:
total 8
drwxr-xr-x 3 aka staff 102 2 17 15:15 .
drwxr-xr-x 8 aka staff 272 2 17 18:57 ..
-r--r--r-- 1 aka staff 22 2 17 15:10 test-file.svn-base ; coしたときのファイルそのもの。
./test-repos/.svn/tmp:
total 0
drwxr-xr-x 5 aka staff 170 2 17 18:57 .
drwxr-xr-x 8 aka staff 272 2 17 18:57 ..
drwxr-xr-x 2 aka staff 68 2 17 15:02 prop-base
drwxr-xr-x 2 aka staff 68 2 17 15:02 props
drwxr-xr-x 2 aka staff 68 2 17 15:15 text-base
./test-repos/.svn/tmp/prop-base:
total 0
drwxr-xr-x 2 aka staff 68 2 17 15:02 .
drwxr-xr-x 5 aka staff 170 2 17 18:57 ..
./test-repos/.svn/tmp/props:
total 0
drwxr-xr-x 2 aka staff 68 2 17 15:02 .
drwxr-xr-x 5 aka staff 170 2 17 18:57 ..
./test-repos/.svn/tmp/text-base:
total 0
drwxr-xr-x 2 aka staff 68 2 17 15:15 .
drwxr-xr-x 5 aka staff 170 2 17 18:57 ..
$
entriesの構造
-------------
---- ---- ---- ----
8 ; おそらく.svnのformatの8。
; 空行
dir ; 固定。.svnがいるdirectoryに関する記述開始。
1 ; Working Directoriesにもってきたときの、このdirectoryのリビジョン
file:///Users/aka/scratch/subversion/test/repos/test-repos; リポジトリ上のURI
file:///Users/aka/scratch/subversion/test/repos/test-repos; リポジトリ上のURI
; 空行 ↑なぜ同じものが二行あるのかわからない。
; 空行
; 空行
2009-02-17T06:15:31.009624Z ; 作成日付け?
1 ; 作成時のリビジョン
aka ; 作成ユーザ
; 空行
; 空行
svn:special svn:externals svn:needs-lock ; なんだろ?
; 空行
; 空行
; 空行
; 空行
; 空行
; 空行
; 空行
; 空行
; 空行
; 空行
; 空行
2c7b609e-ce01-4a14-978c-b57bba64d288 ; このdirectoryのUUID
^L ; エントリのdelimiter
test-dir ; エントリの名前
dir ; エントリの種別
; 空行
; 空行
; 空行
add ; WD上でsvn addされた場合はここに目印が入る。
; エントリのdelimiter
test-file; エントリの名前
file ; エントリの種別
3 ; このWDにもってきたときのリビジョン
; 空行
; 空行
; 空行
2009-02-17T10:29:34.000000Z ; なんの日付?
6230f0c4f2f42c6471c6c747e7b29b57 ; ハッシュのようだ
2009-02-17T10:30:14.853118Z ; リビジョン3のコミット時刻
3 ; なんのリビジョン?
aka ; コミットユーザ
^L ; エントリのdelimiter
---- ---- ---- ----
ファイル名文字コード問題
------------------------
ファイル名は次の箇所にあらわれる。
リポジトリ
db/revs/n ; ファイルの中の文字列として
ワーキングディレクトリ
test-file ; ファイルシステム上に。
.svn/entries ; ファイルの中の文字列として。
.svn/text-base/test-file.svn-base ; ファイルシステム上に。
おそらくOSXで不具合がでているのは、このentriesの中
の文字列としての文字コードとtest-fileや
test-file.svn-baseとかをsvnが取り扱うときの文字コー
ドで差異が発生しているのが原因なのだろう。
こつこつ。
2009年2月16日月曜日
【Subversion】Subversion Development
* : [subversion] Subversion Development
http://subversion.tigris.org/development.html
を読む。ほぼ内容無し。
The Big Pictureの図でアタリを確認する。
- Working Copy Management Library
- Repository Interface
あたりがポイントになるのかな。しかしこれは本当にbig pictureで、実
態どうだか、というところだ。
【Subversion】調査の基礎
* : [subversion] 調査の基礎
対象ソース
subversion_1.5.1dfsg1.orig.tar.gz
- apt-getで取ってきたもの。
情報源
http://subversion.tigris.org/
- ここにいろいろある。
ソースコードリーディングのツールや手法がいろいろありそうだが、あん
まり知らない。。。とりあえず、etagsとoccurぐらいが手駒。あたりまえ
だけど、自分の手駒でまずは攻めるしかない。。。
まずは情報源の資料を読む。
【Subversion】Subversionの理解
* : [subversion] subversionの調査
ポータブルな環境構築においてリポジトリは重要である。リポジトリは
とりあえずsubversion + svkでいこうと思う。
subversionを取り扱うときには文字コードの問題がある。
今まであいまいにしてきたが、ここで整理する。
ファイルの中身は問題ではない。問題はファイル名だ。
subversionのFSFSでは、db/にバージョン管理対象ファイルが設置される。
ここではファイル名は管理ファイルの中にのみ書かれている。なのでリポ
ジトリの管理としてはOSのファイル名管理APIは関係ない。
続いて、svnクライアントとsubversionリポジトリとの間では何がどのよ
うにやり取りされるのか、ということ。
移送されるのはワーキングコピーであり、それはファイル実体と管理領域
たる.svnである。
.svnの中にはバージョン管理対象となっているファイル実体は無い。管理
情報は管理ファイルの中にすべて記載される。
うーん。もっとちゃんと調べないと、駄目だ。
これらのことはなんとなくはわかるのだが、仕様書が存在しない。そこで
ソースを呼んでみるがそれなりにボリュームがあるし、C言語なので、
Common Lispのようなスピードでは読めない。5時間くらいソースの中を放
浪してなんとなく捉めてはきたが、ちゃんとわかるには、系統だってしっ
かりソースを読まないと駄目だ。
うーん。どうしたものか。
リポジトリの内部動作についてちゃんと理解していないで、自分の開発の信頼性をそれに預けることができるのだろうか? できるかできないかよくわからない。
そこで、おそるおそるSubversionのソースコードリーディングをしてみることにする。無理そうだったら逃げる。
こつこつ。
登録:
投稿 (Atom)