ファイルディスクリプタを複製すると何が起きる?
『 Linuxプログラミングインタフェース』5章はファイルIO詳細ということで、fcntl
周りの話などがメインである。
演習問題としてファイルディスクリプタを複製するdup
コマンドおよびdup2
コマンドをfcntl
で実装する課題がある。
別の演習問題として、どの情報が複製されているかを確認する問題もあるので、それも合わせて対応した。
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
の通り、ファイルディスクリプタの操作なので、ファイル周りを今時のプログラミング言語から触る場合はファイルディスクリプタを直接触るのではなく言語で用意されたファイル操作の仕組みを使った方がよさそう。