何かを書き留める何か

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

『 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の通り、ファイルディスクリプタの操作なので、ファイル周りを今時のプログラミング言語から触る場合はファイルディスクリプタを直接触るのではなく言語で用意されたファイル操作の仕組みを使った方がよさそう。