env EDITOR=vipw vipwしたらエラーになる件について

面白いこと聞いたので調べてみた.

# env EDITOR=vipw vipw
usage: vipw [-d directory]
vipw: pw_edit(): No such file or directory

この2行のエラーについて.環境はFreeBSD 9.0-RELEASE.


vipwのソースコードは/usr/src/usr.sbin/vipw

# wc vipw.c
     137     566    3804 vipw.c

なんとお手軽……(ゴクリ

vipw の概要は,初期化 → pw_init() → pw_lock() → pw_tmp() → pw_edit() -> pw_mkdb() → pw_fini() な感じ.vipw.c では EDITOR 環境変数を読まない.pw_hoge() は /usr/src/lib/libutil/pw_util.cにあるのでそっちをみる.


見てみると,EDITOR環境変数を読み込んで,無ければviを設定してる.

/usr/src/lib/libutil/pw_util.c
  289 pw_edit(int notsetuid)
  290 {
...
  297     if ((editor = getenv("EDITOR")) == NULL)
  298         editor = _PATH_VI;


その次はパスワードファイルを編集するプロセスを fork する.321 行目でエディタを起動.env EDITOR=vipw してるので,vipwに tempname を引数として与えて起動することになる.man vipw すれば分かる通り,vipw に -d オプション以外の引数はエラーになる.
これが1行目のエラー.
このときに errno が -1 になって,_exit() で子プロセス(env EDITORの方)の戻り値は -1 に.

/usr/src/lib/libutil/pw_util.c
  289 pw_edit(int notsetuid)
  290 {
...
  309     switch ((editpid = fork())) {
  310     case -1:
  311         return (-1);
  312     case 0:
  313         sigaction(SIGINT, &sa_int, NULL);
  314         sigaction(SIGQUIT, &sa_quit, NULL);
  315         sigprocmask(SIG_SETMASK, &oldsigset, NULL);
  316         if (notsetuid) {
  317             (void)setgid(getgid());
  318             (void)setuid(getuid());
  319         }
  320         errno = 0;
  321         execlp(editor, basename(editor), tempname, (char *)NULL);
  322         _exit(errno);
  323     default:
  324         /* parent */
  325         break;
  326     }


vipw に戻って,switch(pw_edit(0)) してるところを見ると,子プロセス(env EDITORの方)の戻り値が -1 だったので, err(1, "pw_edit()") になる.
これが2行目のエラー.

/usr/src/usr.sbin/vipw/vipw.c
  103     for (;;) {
  104         switch (pw_edit(0)) {
  105         case -1:
  106             pw_fini();
  107             err(1, "pw_edit()");
  108         case 0:
  109             pw_fini();
  110             errx(0, "no changes made");
  111         default:
  112             break;
  113         }
  114         if (pw_mkdb(NULL) == 0) {
  115             pw_fini();
  116             errx(0, "password list updated");
  117         }
  118         printf("re-edit the password file? ");
  119         fflush(stdout);
  120         if ((line = fgetln(stdin, &len)) == NULL) {
  121             pw_fini();
  122             err(1, "fgetln()");
  123         }
  124         if (len > 0 && (*line == 'N' || *line == 'n'))
  125             break;
  126     }


tempname は masterpasswd から生成してて,masterpasswd と同じディレクトリに,ファイル名を pw.XXXXXX にして一時ファイルを作成する.

/usr/src/usr.sbin/vipw/vipw.c
  213 int
  214 pw_tmp(int mfd)
  215 {
...
  221     if (*masterpasswd == '\0')
  222         return (-1);
  223     if ((p = strrchr(masterpasswd, '/')))
  224         ++p;
  225     else
  226         p = masterpasswd;
  227     if (snprintf(tempname, sizeof(tempname), "%.*spw.XXXXXX",
  228         (int)(p - masterpasswd), masterpasswd) >= (int)sizeof(tempname)) {
  229         errno = ENAMETOOLONG;
  230         return (-1);
  231     }


masterpasswd は _PATH_MASTERPASSWD から生成してる._PATH_MASTERPASSWD は/usr/include/pwd.h に書いてある.

   93 int
   94 pw_init(const char *dir, const char *master)
   95 {
...
  110     if (master == NULL) {
  111         if (dir == NULL) {
  112             strcpy(masterpasswd, _PATH_MASTERPASSWD);
  113         } else if (snprintf(masterpasswd, sizeof(masterpasswd), "%s/%s",
  114             passwd_dir, _MASTERPASSWD) > (int)sizeof(masterpasswd)) {
  115             errno = ENAMETOOLONG;
  116             return (-1);
  117         }
  118     } else {
  119         if (strlen(master) >= sizeof(masterpasswd)) {
  120             errno = ENAMETOOLONG;
  121             return (-1);
  122         }
  123         strcpy(masterpasswd, master);
  124     }       
/usr/include/pwd.h
   64 #define _PATH_PWD       "/etc"
   65 #define _PATH_PASSWD        "/etc/passwd"
   66 #define _PASSWD         "passwd"
   67 #define _PATH_MASTERPASSWD  "/etc/master.passwd"
   68 #define _MASTERPASSWD       "master.passwd"