何かを書き留める何か

数学や読んだ本について書く何かです。最近は社会人として生き残りの術を学ぶ日々です。

『 Linuxプログラミングインタフェース』を気合いで読む会 第6回:6章プロセス:環境変数の表示

環境変数の環境とは

Linuxプログラミングインタフェース』6章では環境変数も扱われている。 プロセスごとに環境変数が設定されていて、Linux上では/proc/PID/environから、C言語上ではグローバル変数**environから取得する。

Python

  • os.environに辞書形式で取得できる。
  • 公式ドキュメント曰く、起動時のプロセスの環境変数が反映される。
  • Pythonプログラム内で環境変数を変更する際はos.environを直接編集するとPython側がよしなに対応してくれる。os.putenv()で変更してもos.environの値は変更されない。
import os


def main():
    os.putenv("FOO", "BAR")
    os.environ["HOGE"] = "PIYO"
    for key, value in os.environ.items():
        print(f"{key}={value}")


if __name__ == "__main__":
    main()

実際に振る舞いを確かめてみる。

(venv) $> python3.7 display_env.py | grep FOO  # 出ない!
(venv) $> env FOO=BAR python3.7 display_env.py | grep FOO
FOO=BAR
(venv) $> python3.7 display_env.py | grep HOGE
HOGE=PIYO

Rust

  • std::env::vars()から取得できる。
  • 公式ドキュメント曰く、プログラム起動時(コンパイル時ではない)の環境変数を取得する。
  • 環境変数の編集をするにはstd::env::set_var()std::env::remove_var()関数、キーを指定して取得する場合はstd::env::var()関数を使う。
use std::env;

fn main() {
    let key = "FOO";
    env::set_var(key, "BAR");
    for (key, value) in env::vars() {
        println!("{}={}", key, value);
    }
}

実際に振る舞いを確かめてみる。

(venv) $> target/debug/display_env  | grep FOO
FOO=BAR
(venv) $> env HOGE=PIYO target/debug/display_env | grep HOGE
HOGE=PIYO

感想

  • The Twelve Factor Appを関わっている案件で導入して以来、環境変数との密接な付き合いが続いているが、環境変数の環境がどの範囲を示すのかを理解していなかった。環境とはプロセスごとにある。
  • プログラム自体は大したことがないが、「プロセスごとに環境変数を持っている」という事実は今後の開発にものすごく役に立ちそうな気がしている。それだけ、今まで、ふわっと開発してきたことがわかる。

『 Linuxプログラミングインタフェース』を気合いで読む会 第5回:6章プロセス:コマンドラインパラメータ

nechoとは

Linuxプログラミングインタフェース』6章はプロセスの話。 とりあえずすぐに書けそうだった(というより散々書いてきた)コマンドラインパラメータ取得のサンプルコードをPythonおよびRustで書いてみる。

C言語の場合はargcargvであるが、それらの変数はmain()の引数としてしか参照できないという問題がある。 他の関数でコマンドライン引数を使いたい場合はargvを明示的に渡す必要がある。 Linuxの場合は、/proc/self/cmdlineからコマンドラインパラメータが取得できる。 GNU Cライブラリにはプログラム名を取得できるグローバル変数が定義されている。

さて、PythonおよびRustではどのようにするのか。

Python

  • sys.argvコマンドライン引数のリストとなるので、それをそのまま活用する。
  • -cオプションをつけて起動した場合、スクリプト名を指定せず起動した場合でsys.argv[0]の値が異なることに注意。
import sys


def main():
    for idx, arg in enumerate(sys.argv):
        print(f"argv[{idx}] = {arg}")


if __name__ == "__main__":
    main()

Rust

use std::env;

fn main() {
    for (idx, arg) in env::args().enumerate() {
        println!("argv[{}] = {}", idx, arg);
    }
}

感想

  • tlpiのサンプルコードのファイル名がnecho.cであるのだが、nechoとは何者だろうか。サポートページから察するに何かをechoしているのはわかるのだが、nは何を意味するのか。

『 Linuxプログラミングインタフェース』を気合いで読む会 第4回:5章ファイルIO詳細

ファイルディスクリプタを複製すると何が起きる?

Linuxプログラミングインタフェース』5章はファイルIO詳細ということで、fcntl周りの話などがメインである。 演習問題としてファイルディスクリプタを複製するdupコマンドおよびdup2コマンドをfcntlで実装する課題がある。 別の演習問題として、どの情報が複製されているかを確認する問題もあるので、それも合わせて対応した。

Python

  • fcntlモジュールを活用すれば良い。
  • fcntlシステムコール自体にファイルディスクリプタを複製するコマンドF_DUPFDが存在するので、それを使う。
  • Pythonそのものには、os.dup()が存在するので、実案件でdupを実装する場合はos.dup()を使うべきである。
import fcntl
import io
import os


def dup(old_io: io.IOBase):
    try:
        new_fd = fcntl.fcntl(old_io, fcntl.F_DUPFD)
    except OSError:
        print("cloud not duplicate file descripter.")
        return os.EX_IOERR
    return new_fd


def main():
    out_f = open("test_dup.txt", "w")
    new_fd = dup(out_f)
    access_mode = fcntl.fcntl(new_fd, fcntl.F_GETFL) & os.O_ACCMODE
    new_fd_readable = access_mode == os.O_RDONLY or access_mode == os.O_RDWR
    new_fd_writeable = access_mode == os.O_WRONLY or access_mode == os.O_RDWR

    print(f"old fd: {out_f.fileno()}")
    print(f"new fd: {new_fd}")

    print(f"old fd readable?: {out_f.readable()}")
    print(f"new fd is readable?: {new_fd_readable}")

    print(f"old fd is writable?: {out_f.writable()}")
    print(f"new fd is writable?: {new_fd_writeable}")

    print(f"old fd flags: {fcntl.fcntl(out_f, fcntl.F_GETFD)}")
    print(f"new fd flags: {fcntl.fcntl(new_fd, fcntl.F_GETFD)}")

    print(f"old open file description: {fcntl.fcntl(out_f, fcntl.F_GETFL)}")
    print(f"new open file description: {fcntl.fcntl(new_fd, fcntl.F_GETFL)}")

    print(f"old current seek position: {os.lseek(out_f.fileno(), 0, os.SEEK_CUR)}")
    print(f"new current seek position: {os.lseek(new_fd, 0, os.SEEK_CUR)}")

    print(f"new current seek move: {os.lseek(new_fd, 10, os.SEEK_CUR)}")
    print(f"old current seek position: {os.lseek(out_f.fileno(), 0, os.SEEK_CUR)}")
    print(f"new current seek position: {os.lseek(new_fd, 0, os.SEEK_CUR)}")

    out_f.close()
    os.close(new_fd)


if __name__ == "__main__":
    main()

実行結果は以下の通り。

old fd: 3
new fd: 4
old fd readable?: False
new fd is readable?: False
old fd is writable?: True
new fd is writable?: True
old fd flags: 1
new fd flags: 0
old open file description: 1
new open file description: 1
old current seek position: 0
new current seek position: 0
new current seek move: 10
old current seek position: 10
new current seek position: 10
  • ファイルディスクリプタの値は別のを指している。
  • 読み書き周りのフラグは正しくコピーされている。
  • ファイルディスクリプタ自身のフラグ(close-on-exec)はきちんと違う値になっている
  • オープンファイルディスクリプタテーブルの情報のフラグは正しくコピーされている。
  • 複製したファイルディスクリプタだけファイルシークをすると、原本・複製共にシークされて同じ位置に移動する。

次はdup2を実装する。

import fcntl
import errno
import io
import os


def dup2(old_io: io.IOBase, new_fd: int):
    if old_io.fileno() == new_fd:
        return new_fd

    try:
        fcntl.fcntl(old_io, fcntl.F_GETFL)
    except OSError:
        print("cloud not close file descripter.")
        return errno.EBADF

    try:
        os.close(new_fd)
    except OSError:
        print("cloud not close file descripter.")
        return os.EX_IOERR

    try:
        new_fd = fcntl.fcntl(old_io, fcntl.F_DUPFD, new_fd)
    except OSError:
        print("cloud not duplicate file descripter.")
        return os.EX_IOERR
    return new_fd


def main():
    out_f = open("test_dup2.txt", "w")
    new_f = open("dummy.txt", "w")
    new_fd = dup2(out_f, new_f.fileno())
    access_mode = fcntl.fcntl(new_fd, fcntl.F_GETFL) & os.O_ACCMODE
    new_fd_readable = access_mode == os.O_RDONLY or access_mode == os.O_RDWR
    new_fd_writeable = access_mode == os.O_WRONLY or access_mode == os.O_RDWR

    print(f"old fd: {out_f.fileno()}")
    print(f"new fd: {new_fd}")

    print(f"old fd readable?: {out_f.readable()}")
    print(f"new fd is readable?: {new_fd_readable}")

    print(f"old fd is writable?: {out_f.writable()}")
    print(f"new fd is writable?: {new_fd_writeable}")

    print(f"old fd flags: {fcntl.fcntl(out_f, fcntl.F_GETFD)}")
    print(f"new fd flags: {fcntl.fcntl(new_fd, fcntl.F_GETFD)}")

    print(f"old open file description: {fcntl.fcntl(out_f, fcntl.F_GETFL)}")
    print(f"new open file description: {fcntl.fcntl(new_fd, fcntl.F_GETFL)}")

    print(f"old current seek position: {os.lseek(out_f.fileno(), 0, os.SEEK_CUR)}")
    print(f"new current seek position: {os.lseek(new_fd, 0, os.SEEK_CUR)}")

    print(f"new current seek move: {os.lseek(new_fd, 10, os.SEEK_CUR)}")
    print(f"old current seek position: {os.lseek(out_f.fileno(), 0, os.SEEK_CUR)}")
    print(f"new current seek position: {os.lseek(new_fd, 0, os.SEEK_CUR)}")

    out_f.close()
    os.close(new_fd)


if __name__ == "__main__":
    main()

Rust

  • システムコール周りはnixクレートを使う。libcクレートよりもRustの恩恵を受けやすいように実装されているのがうれしい。
  • ファイルディスクリプタからファイルオブジェクトを作るのはunsafeということなので一旦断念した。
  • unwrap()祭りでマナーが悪いのでは。
extern crate libc;
extern crate nix;

use libc::O_ACCMODE;
use libc::O_RDONLY;
use libc::O_RDWR;
use libc::O_WRONLY;
use nix::fcntl::fcntl;
use nix::fcntl::FcntlArg::F_DUPFD;
use nix::fcntl::FcntlArg::F_GETFD;
use nix::fcntl::FcntlArg::F_GETFL;
use std::fs::File;
use std::os::unix::io::AsRawFd;
use std::os::unix::io::RawFd;

fn dup(old_fd: &RawFd) -> i32 {
    let new_fd = fcntl(*old_fd, F_DUPFD(*old_fd)).unwrap();
    new_fd
}

fn main() {
    let old_file = File::create("dummy.txt").unwrap();
    let old_fd = old_file.as_raw_fd();
    let new_fd = dup(&old_fd);

    let old_access_mode = fcntl(old_fd, F_GETFL).unwrap() & O_ACCMODE;
    let new_access_mode = fcntl(old_fd, F_GETFL).unwrap() & O_ACCMODE;

    let old_fd_readable = (old_access_mode == O_RDONLY) | (old_access_mode == O_RDWR);
    let new_fd_readable = (new_access_mode == O_RDONLY) | (new_access_mode == O_RDWR);
    let old_fd_writeable = (old_access_mode == O_WRONLY) | (old_access_mode == O_RDWR);
    let new_fd_writeable = (new_access_mode == O_WRONLY) | (new_access_mode == O_RDWR);

    println!("old fd: {}", &old_fd);
    println!("new fd: {}", &new_fd);

    println!("old fd is readable?: {}", old_fd_readable);
    println!("new fd is readable?: {}", new_fd_readable);
    println!("old fd is writable?: {}", old_fd_writeable);
    println!("new fd is writable?: {}", new_fd_writeable);

    println!("old fd flags: {}", fcntl(old_fd, F_GETFD).unwrap());
    println!("new fd flags: {}", fcntl(new_fd, F_GETFD).unwrap());

    println!(
        "old open file description: {}",
        fcntl(old_fd, F_GETFL).unwrap()
    );
    println!(
        "new open file description: {}",
        fcntl(new_fd, F_GETFL).unwrap()
    );
}

感想

  • fcntlってどのように発音するのだろうか、そもそも何の略語なのだろうか、ということでmacOSのmanで調べたらfcntl -- file controlとのことなので、「えふこんとろーる」か「ふぁいるこんとろーる」と発音すればよさそう。なお、Linuxだとfcntl - manipulate file descriptorなのでよくわからなかった。
  • Linuxmanipulate file descriptorの通り、ファイルディスクリプタの操作なので、ファイル周りを今時のプログラミング言語から触る場合はファイルディスクリプタを直接触るのではなく言語で用意されたファイル操作の仕組みを使った方がよさそう。