Guile色々/ユーザー・グループ情報の取得

変更履歴

概 要

目 次

参考資料

ユーザー情報の取得

ユーザー情報は, という手順で取得します.

パスワードエントリーの取得

▹パスワードファイル(/etc/passwd)に登録された各ユーザーのエントリー(以下,パスワードエントリー)は,以下のいずれかの手続きを使って取得します. いずれの手続きも, 引数で指定されたユーザーのパスワードエントリーを取り出して, エントリー内の各情報を成分とするベクタを返します.

(getpwnam name)
ユーザー名 name のパスワードエントリーを返します.name はユーザー名を表す文字列です.
(getpwuid uid)
ユーザーID uid のパスワードエントリーを返す.uid はユーザーIDを表す整数です.
(getpw user)
ユーザー user のパスワードエントリーを返す.usernameuid のいずれかです.

▹実行例
$ guile
GNU Guile 3.0.5
      ...... 起動メッセージ ......
guile> (getpwnam "algo")
$1 = #("algo" "x" 1000 1000 "AlgoKajya,,," "/home/algo" "/bin/bash")
guile> (getpwuid 1000)
$2 = #("algo" "x" 1000 1000 "AlgoKajya,,," "/home/algo" "/bin/bash")
guile> (getpw "algo")
$3 = #("algo" "x" 1000 1000 "AlgoKajya,,," "/home/algo" "/bin/bash")
guile> (getpw 1000)
$4 = #("algo" "x" 1000 1000 "AlgoKajya,,," "/home/algo" "/bin/bash")
guile> 

▹(記録) getpw 手続きはコアシステム(C言語のプログラム)の一部として実装されています.getpwname 手続きと getpwuid 手続きは次のように定義されています.
(define (getpwnam name) (getpw name))
(define (getpwuid uid) (getpw uid))
getpw 手続きの名付け元となっている(と思われる)ライブラリコールの getpw 関数は,バッファーオーバーフローの危険性があるため廃止されているようです([GETPW(3)]のBUGSを参照). 一方,Guileのソースコード(posix.c)を見ると,上記の getpw 手続きは, ライブラリコールの getpwnam 関数と getpwuid 関数を使って実装されているので, 問題なく使用できると思います.

ユーザー情報の抽出

▹上記の手続きによって得たパスワードエントリーはベクタなので,vector-ref を使ってユーザー情報を抽出することができます.一方,パスワードエントリーから各情報を抽出するための手続きが用意されています. 以下の pw は上記の手続きによって取得したパスワードエントリー(のベクタ)を表します.

(passwd:name pw)
パスワードエントリー pw 内のユーザー名(第1成分)を返します.
(passwd:passwd pw)
パスワードエントリー pw 内のパスワード(第2成分)を返します.
(passwd:uid pw)
パスワードエントリー pw 内のユーザーID(第3成分)を返します.
(passwd:gid pw)
パスワードエントリー pw 内のグループID(第4成分)を返します.
(passwd:gecos pw)
パスワードエントリー pw 内のコメント欄(第5成分)を返します.
(passwd:dir pw)
パスワードエントリー pw 内のホームディレクトリ(第6成分)を返します.
(passwd:shell pw)
パスワードエントリー pw 内のログインシェル(第7成分)を返します.

▹以下の実行例では,(getpwnam "algo") の値($1)から各情報を抽出しています.
$ guile
GNU Guile 3.0.5
      ...... 起動メッセージ ......
guile> (getpwnam "algo")
$1 = #("algo" "x" 1000 1000 "AlgoKajya,,," "/home/algo" "/bin/bash")
guile> (passwd:name $1)
$2 = "algo"
guile> (passwd:passwd $1)
$3 = "x"
guile> (passwd:uid $1)
$4 = 1000
guile> (passwd:gid $1)
$5 = 1000
guile> (passwd:gecos $1)
$6 = "AlgoKajya,,,"
guile> (passwd:dir $1)
$7 = "/home/algo"
guile> (passwd:shell $1)
$8 = "/bin/bash"
guile> 

現行プロセスのユーザー

▹現行プロセスのユーザーIDやユーザー名を返す手続きとして, 次のものがあります.

(getuid)
現行プロセスの実ユーザーIDを返します.

(getlogin)
現行プロセスの制御端末にログインしているユーザー名(文字列)を返します.

ここで,現行プロセスとは,getuid や getlogin を実行したREPLやスクリプトを動かしているプロセスのことです.実ユーザーIDとは,現行プロセス(REPLやスクリプト)を起動したユーザーIDのことです.

▹以下の実行例は, 一般ユーザー(algo;uidは1000)がREPLを起動して,getuid と getlogin を実行した結果を示しています.
$ guile
GNU Guile 3.0.5
      ...... 起動メッセージ ......
guile> (getuid)
$1 = 1000
guile> (getlogin)
$2 = "algo"
guile> 
以下の実行例は,sudoコマンドによってREPLを起動して, そのREPLの中で getuid とgetlogin を実行した結果を示しています.getuid の結果は,REPLを起動したユーザーがrootであることを示しています.一方,ログインユーザーは一般ユーザー(algo)です.
$ sudo guile
[sudo] algo のパスワード:********************* ↵
GNU Guile 3.0.5
      ...... 起動メッセージ ......
scheme@(guile-user)> (getuid)
$1 = 0
scheme@(guile-user)> (getlogin)
$2 = "algo"
scheme@(guile-user)> 

具体例

▹Guile の load 手続きは,ホームディレクトリを表すティルダ(~)が使えません. そこで,ティルダが使えるような自前のload手続きを作ってみます. 以下の myload 手続きは,filename の先頭の文字がティルダ(~)だったとき, それを mylaod を実行した実ユーザーのホームディレクトリ(homedir)に変更して load 手続きを適用します.ホームディレクトリ(homedir)を求めるために, 上で述べた手続きを利用しています.
;; myload.scm

(define (myload filename)
  (if (char=? (string-ref filename 0) #\~)
      (let* ((homedir (passwd:dir (getpwuid (getuid))))
             (fname (string-append homedir (substring filename 1))))
        (display ";;; load ") (display fname) (newline)
        (load fname))
      (load filename)))
ホームディレクトリ上の tmp ディレクトリに次のプログラムを保存しておいて,myload を使ってこのプログラムをロードしてみます.
;; /home/algo/tmp/here.scm

(display "** Here is /home/algo/tmp.") 
(newline)
$ guile -l myload.scm
GNU Guile 3.0.5
      ...... コンパイルメッセージ&起動メッセージ ......
guile> (myload "~/tmp/here.scm")
;;; load /home/algo/tmp/here.scm
** Here is /home/algo/tmp.
guile> 

▹(記録) 上の手続きを ~/.guile に登録して,REPLの起動時にロードしても, 残念ながらうまく動きません.その原因は,~/.guile をロードする時点では load 手続きの定義がロードされていないためです.~/.guile に登録するときには, 上記の load を primitive-load に変更するとうまく動きます. ちなみに,primitive-load はコアシステム(C言語プログラム)の一部として実装されていますが,load はマクロとして実装されています. さらに,primitive-load はシステム標準の reader を使ってロードしますが,load は reader を指定できます.

パスワードファイルのストリーム処理

▹以下に示す手続きによって, パスワードファイル(/etc/passwd)からパスワードエントリーを次々と取得できます.なお,タイトルにある「ストリーム処理」は「次々と取得する処理」のことを表しています.

(getpw)
(getpw) を実行するたびに, パスワードファイル(/etc/passwd)からパスワードエントリーを次々と取得して, その情報からなるベクタを返します. パスワードファイル(/etc/passwd)の終端まで到達したら #f を返します.なお, パスワードファイル(/etc/passwd)がクローズしているときには, オープンして,先頭のパスワードエントリーを返します.

(setpw) または (setpw #f)
これはパスワードファイルをクローズして,ストリーム処理を終了させます. なお,パスワードファイルがクローズしているときにこれを実行しても, 何も起こらないようです(マニュアルや仕様書を見ても,残念ながら, この点に関する説明は発見できません).

(setpw true)
これはストリーム処理をリセットします. つまり,(getpw) が先頭のパスワードエントリーから次々と取得できるようにします. true は #f 以外の任意のオブジェクトです.

(getpwent) (settpwent) (endpwent)
それぞれ,次のように定義されています.
(define (getpwent) (getpw))
(define (setpwent) (setpw #t))
(define (endpwent) (setpw))
これらは,POSIXの仕様に合わせて用意しているのだと思います. ちなみに,getpw や setpw はPOSIXの仕様書にはありません.

▹実行例:以下の実行例では,(getpw) を3回,(setpw #t) を1回(リセット),(getpw) を3回,(setpw) を1回(クローズ),(getpw) を3回の順に実行しています. パスワードファイルがクローズしているとき,(getpw) 自身がオープンしてストリーム処理を初期設定するため,ストリーム処理をリセットする (setpw #t) とパスワードファイルをクローズする (setpw) がまったく同じ処理をしているように見えます.
$ guile
GNU Guile 3.0.5
      ...... 起動メッセージ ......
guile> (getpw)
$1 = #("root" "x" 0 0 "root" "/root" "/bin/bash")
guile> (getpw)
$2 = #("daemon" "x" 1 1 "daemon" "/usr/sbin" "/usr/sbin/nologin")
guile> (getpw)
$3 = #("bin" "x" 2 2 "bin" "/bin" "/usr/sbin/nologin")
guile> (setpw #t)
guile> (getpw)
$4 = #("root" "x" 0 0 "root" "/root" "/bin/bash")
guile> (getpw)
$5 = #("daemon" "x" 1 1 "daemon" "/usr/sbin" "/usr/sbin/nologin")
guile> (getpw)
$6 = #("bin" "x" 2 2 "bin" "/bin" "/usr/sbin/nologin")
guile> (setpw)
guile> (getpw)
$7 = #("root" "x" 0 0 "root" "/root" "/bin/bash")
guile> (getpw)
$8 = #("daemon" "x" 1 1 "daemon" "/usr/sbin" "/usr/sbin/nologin")
guile> (getpw)
$9 = #("bin" "x" 2 2 "bin" "/bin" "/usr/sbin/nologin")
guile> 

▹(getpw) はパスワードファイルを自前でオープンします. さらに,ストリーム処理を行っているプロセスが終了すると同時に(たぶん間違いなく)パスワードファイルはクローズされると思います. そのため,setpw 手続きは実行する必要がないように感じます. しかし,ストリーム処理を何回も実行したり,手続きとして実装するときには, ストリーム処理のリセット(初期設定)とクローズ(終了)を真面目に実行すべきだろうと思います.少なくとも,ストリーム処理を開始するときにリセットだけは実行したほうがよいでしょう.そうしないと,それまでに実行していたストリーム処理の途中から再開することになるかも知れません.

▹(記録) ライブラリーコールの getpw 関数は, バッファーオーバーフローの危険性があるため廃止されているようです([GETPW(3)]のBUGSを参照). 一方,Guileのソースコード(posix.c)を見ると,上記の getpw 手続きは, ライブラリコールの getpwent 関数を使って実装されているので, 問題なく使用できると思います.

グループ情報の取得とストリーム処理

▹グループファイル(/etc/group)についてもパスワードファイルと類似の手続きが用意されています.それぞれの手続きの処理内容はパスワードファイルの場合とまったく同じなので,詳細は省略します. なお,以下のグループエントリーは, グループファイル(/etc/group)に登録されているエントリーのことです.

(getgrnam gname)
グループ名 gname のグループエントリー(のベクタ)を返します.gname はグループ名を表す文字列です.
(getgruid gid)
グループID gid のグループエントリー(のベクタ)を返します.gid はグループIDを表す整数です.
(getgr group)
グループ group のグループエントリー(のベクタ)を返します.groupgnamegid のいずれかです.

(group:name gr)
グループエントリー gr 内のグループ名(第1成分)を返します.
(group:group gr)
グループエントリー gr 内のグループパスワード(第2成分)を返します.
(group:uid gr)
グループエントリー gr 内のグループID(第3成分)を返します.
(group:gid gr)
グループエントリー gr 内のユーザーリスト(第4成分)を返します.ここで,ユーザーリストは,このグループを補助グループとするユーザー名のリストです.

(getgid)
現行プロセスの実グループIDを返します.

(getgr)
(getgr) を実行するたびに, グループファイル(/etc/group)からグループエントリーを次々と取得して, その情報からなるベクタを返します. グループファイル(/etc/group)の終端まで到達したら #f を返します.なお, グループファイル(/etc/group)がクローズしているときには, オープンして,先頭のグループエントリーを返します.

(setgr) または (setgr #f)
これはグループファイルをクローズして,ストリーム処理を終了させます.

(setgr true)
これはストリーム処理をリセットします. つまり,(getgr) が先頭のグループエントリーから次々と取得できるようにします. true は #f 以外の任意のオブジェクトです.

(getgrent) (settgrent) (endgrent)
それぞれ,次のように定義されています.
(define (getgrent) (getgr))
(define (setgrent) (setgr #t))
(define (endgrent) (setgr))
(おしまい)