LeopardのUNIXコマンドと環境変数COMMAND_MODE

昨日id:ichii386

id:ichii386Solarisとかだといつもps -efってやるんだけど、Macだとだめなんだよね」
id:moriyoshi「え?使えるよ?何いってんの?」

という話になり*1、この違いの原因を突き止めることに。

バックドアとか仕掛けられてない?」「んなわけねーよ」とかいいながらBluetooth経由で/bin/psを送ったりとかsuidビットを落としてみたりとかいろいろ試してみると、iTermではだめで、Terminalでは大丈夫ということが判明、ますます混乱する。

で、psのソースを見てみると*2

以下主要部分のみ抜粋。

#define	PS_ARGS	(u03 ? "aACcdeEfg:G:hjLlMmO:o:p:rSTt:U:u:vwx" : \
	"aACcdeEgG:hjLlMmO:o:p:rSTt:U:uvwx")

int
main(int argc, char *argv[])
{
    ...
	int u03 = COMPAT_MODE("bin/ps", "unix2003");
    ...
	while ((ch = getopt(argc, argv, PS_ARGS)) != -1)
		switch ((char)ch) {
        ...
        }
 
	argc -= optind;
	argv += optind;
    ...
	/*
	 * If there arguments after processing all the options, attempt
	 * to treat them as a list of process ids.
	 */
	while (*argv) {
		if (!isdigitch(**argv))
			break;
		add_list(&pidlist, *argv);
		argv++;
	}
	if (*argv) {
		fprintf(stderr, "%s: illegal argument: %s\n",
		    getprogname(), *argv);
		usage(u03);
	}
    ...
}

u03という変数の値により、受け付けるオプションの種類を変えているのが一目瞭然だ。で、u03自体はCOMPAT_MODEという謎マクロの値となっている。

COMPAT_MODEはファイルの最初の方にあって、

#ifdef __APPLE__
#include <get_compat.h>
#else /* !__APPLE__ */
#define COMPAT_MODE(func, mode) (1)
#endif /* __APPLE__ */

となっている。今度はget_compat.hを見てみると、

extern bool compat_mode(const char *function, const char *mode);

/* this will eventually cache the result, you need to call it with a
  static string otherwise it will cache the wrong result.  It ought
  to be fast enough to use in things like malloc(2) without extra
  tricks */
#define COMPAT_MODE(func, mode) compat_mode(func, mode)

などと書いてあるので、「man compat_mode」としようとして「man comp」まで打ったところでタブを押してシェルによる補完を期待したら、「man compat」で止まったので、これを見てみると…


NAME

compat -- manipulate compatibility settings

SYNOPSIS


COMMAND_MODE=legacy|unix2003

#define _POSIX_C_SOURCE
#define _DARWIN_C_SOURCE
#define _NONSTD_SOURCE
defined(__LP64__)

#include
defined(_DARWIN_FEATURE_UNIX_CONFORMANCE)


DESCRIPTION


Setting the environment variable COMMAND_MODE to the value legacy causes
utility programs to behave as closely to Mac OS X 10.3's utility programs
as possible. When in this mode all of 10.3's flags are accepted, and in
some cases extra flags are accepted, but no flags that were used in 10.3
will have been removed or changed in meaning. Any behavioral changes in
this mode are documented in the LEGACY sections of the individual utili-
ties.

Setting the environment variable COMMAND_MODE to the value unix03 causes
utility programs to obey the Version 3 of the Single UNIX Specification
(``SUSv3'') standards even if doing so would alter the behavior of flags
used in 10.3.

The value of COMMAND_MODE is case insensitive and if it is unset or set
to something other than legacy or unix03 it behaves as if it were set to
unix03.


compat(5)

COMMAND_MODEという環境変数の値をlegacyとかにしてやると、UNIXユーティリティの挙動が変わるという感じらしいことがわかる。

% COMMAND_MODE=legacy ps -ef
ps: illegal option -- f
usage: ps [-AaCcEefhjlMmrSTvwXx] [-O fmt | -o fmt] [-G gid[,gid...]]
          [-u]
          [-p pid[,pid...]] [-t tty[,tty...]] [-U user[,user...]]
       ps [-L]

確かにエラーになった。この間に「COMMAND_MODE iTerm」などとぐぐってみると、

Leopard「ターミナル」で、/bin/sh の echo -n がおかしい挙動を示します。

sh-3.2$ echo -n foo
-n foo
sh-3.2$ 

分ったこと:

  • /bin/shbash だが、/bin/bash とは違うもの
  • which echo すると /bin/echo と言われるが、built-in のようだ
  • iTerm から起動すると echo -n はちゃんと動く

ターミナルで /bin/sh の echo は、iBCS2 コンパチとして動くのでしょうか?
google しても解決方法は分りませんでした。ご存知の方は教えて下さい。(_ _)
...

kzysk: なぜか iTerm は環境変数 COMMAND_MODE=legacy, ターミナルは unix2003 になっていて、その違いで sh 上の echo の動きがかわるらしいです。

http://developer.apple.com/releasenotes/Darwin/RN-Unix03Conformance/

echo -n - あどけない話

上の日記 (Mewの作者さんの山本さんのですね) のコメント欄にヒントが書いてあるのを発見。

で、そのリンク先を読んでみると、

Types of Compatibility Issues

Existing compiled applications should not encounter problems in moving to Mac OS X v.10.5. If a system call or library function is known to have new behavior, the new version will only be used with programs that have been compiled for use on Mac OS X v.10.5. An existing executable will not "see" these versions and thus will not be affected by the changes.

If an application is recompiled for use on Mac OS X v.10.5, it will get the new versions of libraries and system calls, unless a legacy target is specified (See Execution issues, below) .Some scripts, such as bash and perl scripts, may also encounter problems with commands whose options and/or behavior has changed. It is possible to request legacy behavior by setting the environment variable COMMAND_MODE to the value "legacy".

Types of Compatibility

などと書いてあり、さらに下には


ps


The biggest change is in the interpretation of the -u option, which now displays processes belonging to the specified username(s). Thus, "ps -aux" will fail (unless you want to know about user "x"). As a convenience, however, "ps aux" still works as it did in Mac OS X v.10.4.

In legacy operation, ps has the following differences:

  • -e Display the environment as well. Same as -E.
  • -g Ignored for compatibility. Takes no argument.
  • -l Display information associated with the following keywords: uid, pid, ppid, cpu, pri, nice, vsz, rss, wchan, state, tt, time, and command.
  • -u Display information associated with the following keywords: user, pid, %cpu, %mem, vsz, rss, tt, state, start, time, and command.

The -u option implies the -r option.


などとかなり適当なことが書いてあるわけです。

じゃあ、なぜiTerm.appの場合だけはCOMMAND_MODE=legacyとなるのかということが気になるわけで、

strings /Applications/iTerm.app/Contents/MacOSX/iTerm | grep -i 'compat\|legacy\|unix2003\|command_mode'

としてみると、いずれの文字列も見つからず、

「まあまさかわざわざlegacyに設定する必要もないし、明示的にはやってないんじゃないの」

と思い直して闇雲に調べつつ話していると、はっと普段はTigerでiTermを使っていることを思い出し、ふと環境変数MACOSX_DEPLOYMENT_TARGETのことに気づき、これでまたぐぐるとcompat(5)の説明にたどり着くというとんでもない遠回りをして、再びこれを読み進めてみたところ、

32-BIT COMPILATION
     Defining _NONSTD_SOURCE causes library and kernel calls to behave as
     closely to Mac OS X 10.3's library and kernel calls as possible.  Any
     behavioral changes in this mode are documented in the LEGACY sections of
     the individual function calls.

     Defining _POSIX_C_SOURCE or _DARWIN_C_SOURCE causes library and kernel
     calls to conform to the SUSv3 standards even if doing so would alter the
     behavior of functions used in 10.3.  Defining _POSIX_C_SOURCE also
     removes functions, types, and other interfaces that are not part of SUSv3
     from the normal C namespace, unless _DARWIN_C_SOURCE is also defined
     (i.e., _DARWIN_C_SOURCE is _POSIX_C_SOURCE with non-POSIX extensions).
     In any of these cases, the _DARWIN_FEATURE_UNIX_CONFORMANCE feature macro
     will be defined to the SUS conformance level (it is undefined otherwise).

     Starting in Mac OS X 10.5, if none of the macros _NONSTD_SOURCE,
     _POSIX_C_SOURCE or _DARWIN_C_SOURCE are defined, and the environment
     variable MACOSX_DEPLOYMENT_TARGET is either undefined or set to 10.5 or
     greater (or equivalently, the gcc(1) option -mmacosx-version-min is
     either not specified or set to 10.5 or greater), then UNIX conformance
     will be on by default, and non-POSIX extensions will also be available
     (this is the equivalent of defining _DARWIN_C_SOURCE).  For version val-
     ues less that 10.5, UNIX conformance will be off (the equivalent of
     defining _NONSTD_SOURCE).

64-BIT COMPILATION
     When compiling for 64-bit architectures, the __LP64__ macro will be
     defined to 1, and UNIX conformance is always on (the
     _DARWIN_FEATURE_UNIX_CONFORMANCE macro will also be defined to the SUS
     conformance level).  Defining _NONSTD_SOURCE will cause a compilation
     error.

と書かれていたので、次のようなテストプログラムを書いて実験してみた。

#include <stdio.h>
#include <get_compat.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    printf("compat mode=%s\n",
        compat_mode("bin/ps", "unix2003") ? "unix2003": "legacy");
    
    if (!fork()) {
        execl("/bin/ps", "ps", "-ef", NULL);
    } else {
        wait(0);
    }
}

コンパイルオプションは以下。

gcc -mmacosx-version-min=10.4 -isysroot /Developer/SDKs/MacOSX10.4u.sdk -m32 -arch i386 -o test test.c

しかし、これでもうまく行ってしまう。

仕方なくiTermのソースをhttp://iterm.svn.sourceforge.net/svnroot/iterm/trunkから落としてきてビルドしてみても、とりわけ奇妙なビルドオプションを指定しているわけでもなく、main関数でNSApplicationMain()が呼ばれる前にprintf("%s", getenv("COMMAND_MODE"));してみると、これがlegacyとなる。

いろいろビルドオプションをつけたりはずしたりして上のtest.cのコンパイルを繰り返し、最後に違いがあると思われたのが「-framework」オプションだったので、これでだめなら、と思いながら試してみると…

% /Developer/usr/bin/gcc -mmacosx-version-min=10.4 -arch i386 -isysroot /Developer/SDKs/MacOSX10.4u.sdk -o test test.c -framework Foundation
% ./test
compat mode=legacy
ps: illegal option -- f
usage: ps [-AaCcEefhjlMmrSTvwXx] [-O fmt | -o fmt] [-G gid[,gid...]]
          [-u]
          [-p pid[,pid...]] [-t tty[,tty...]] [-U user[,user...]]
       ps [-L]

となり、鍵は-frameworkらしいということがわかる。ldに-Wl,-tとして-tオプションを渡し、リンク時に読み込んでいるファイルを確認して、-frameworkに与える名前をいろいろ確認するという作業を繰り返した結果、CoreFoundationがリンクされていて、さらに-mmacosx-version-min=10.4以下に設定されている場合に何かが起こっているらしいということが判明。

環境変数DYLD_PRINT_ENVを設定してtestを実行すると、COMMAND_MODE=legacyとなっていることが確認できるので、dyld自体がCOMMAND_MODEをハンドルしているのではないかと想像されるものの、詳細は不明ということで調査は一旦打ち切りにしたい。

*1:2人とも環境は10.5.x, Leopard

*2:ソース自体はadv_cmdsというパッケージに含まれている