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