7. 任意のファイルの読み書き
シェルのリダイレクト機能を使用すれば標準入出力だけを使ってファイル を扱うことができますが,エディタのようにアプリケーションから直接 ファイルを読み書きしたい場合もあります.
この章では簡単な例としてファイルをコピーするプログラムを作成します. ファイルをコピーするには,ファイルを読みながら別ファイルに書き込めば 実現できます.1文字読む毎に書き出せば最も原始的なコピーコマンドとな ります.
結局, cat <コピー元ファイル >コピー先ファイル と同じです.
必要な操作はコピー元のファイルのオープン,コピー先のファイル のオープン, コピー元のファイルを読み込ながら,コピー先のファイルに 書き出す,オープンしたファイルのクローズです. さらにコピー元の ファイルを読み込んで「何か処理した後」,コピー先のファイルに書き出せば フィルタ系のコマンドが作成できます.
簡単ですね.
実装する前に少し準備をしておきます.
最初は C のソースやカーネルで使われている変数の型をアセンブラで使い やすいように次のファイルを用意します.C の構造体の宣言をアセンブラ用 に変換する場合に便利です.
;Copyright (C) 2000 Jun Mizutani <mizutani.jun@nifty.ne.jp> ; file : vartype.inc ; created : 2000/05/21 %ifndef __VARTYPE_INC %define __VARTYPE_INC %define INT resd %define UINT resd %define LONG resd %define ULONG resd %define SHORT resw %define USHORT resw %define CHAR resb %define UCHAR resb %define BYTE resb %define _INT dd %define _UINT dd %define _LONG dd %define _ULONG dd %define _SHORT dw %define _USHORT dw %define _CHAR db %define _UCHAR db %define _BYTE db %endif
もうひとつだけ用意しておきます.ファイルの操作には色々な定数を指定する 必要があります.それらの定数宣言のためのファイルをカーネルのヘッダファイル include/asm-i386/fcntl.h から作成しておきましょう.早速 vartype.inc を使って (インクルードして) います.
;Copyright (C) 2000 Jun Mizutani <mizutani.jun@nifty.ne.jp> ; ; file : fcntl.inc ; created : 2000/05/21 ; derived from : linux-2.2.14/include/asm-i386/fcntl.h %ifndef __FCNTL_INC %define __FCNTL_INC %include "vartype.inc" ; open/fcntl - O_SYNC is only implemented on blocks devices and on files ; located on an ext2 file system %assign O_ACCMODE 0003q %assign O_RDONLY 00q %assign O_WRONLY 01q %assign O_RDWR 02q %assign O_CREAT 0100q ; not fcntl %assign O_EXCL 0200q ; not fcntl %assign O_NOCTTY 0400q ; not fcntl %assign O_TRUNC 01000q ; not fcntl %assign O_APPEND 02000q %assign O_NONBLOCK 04000q %assign O_NDELAY O_NONBLOCK %assign O_SYNC 010000q %assign FASYNC 020000q ; fcntl, for BSD compatibility %assign O_DIRECT 040000q ; direct disk access hint - currently ignored %assign O_LARGEFILE 0100000q %assign O_DIRECTORY 0200000q ; must be a directory %assign O_NOFOLLOW 0400000q ; don't follow links %assign F_DUPFD 0 ; dup %assign F_GETFD 1 ; get f_flags %assign F_SETFD 2 ; set f_flags %assign F_GETFL 3 ; more flags (cloexec) %assign F_SETFL 4 %assign F_GETLK 5 %assign F_SETLK 6 %assign F_SETLKW 7 %assign F_SETOWN 8 ; for sockets. %assign F_GETOWN 9 ; for sockets. %assign F_SETSIG 10 ; for sockets. %assign F_GETSIG 11 ; for sockets. ; for F_[GET|SET]FL %assign FD_CLOEXEC 1 ; actually anything with low bit set goes ; for posix fcntl() and lockf() %assign F_RDLCK 0 %assign F_WRLCK 1 %assign F_UNLCK 2 ; for old implementation of bsd flock () %assign F_EXLCK 4 ; or 3 %assign F_SHLCK 8 ; or 4 ; operations for bsd flock(), also used by the kernel implementation %assign LOCK_SH 1 ; shared lock %assign LOCK_EX 2 ; exclusive lock %assign LOCK_NB 4 ; or'd with one of the above to prevent blocking %assign LOCK_UN 8 ; remove lock struc flock .l_type USHORT 1; .l_whence USHORT 1; .l_start LONG 1; .l_len LONG 1; .l_pid LONG 1; endstruc %endif
ファイルをオープンする場合のモード指定のために O_RDONLY, O_WRONLY, O_RDWR, O_CREAT ,O_EXCL, O_TRUNC, O_APPEND などを使用します. 8進定数の場合は数値の後ろに「q」を追加している事に注意してください.
さて,本題です.
ファイルの読み書きは,すでに出てきた sys_read と sys_write システム コールを使います.ただし今回はファイルディスクリプタを指定します. ファイルディスクリプタは sys_open システムコールで eax に返された値を 使用します.使い終わったファイルは sys_close でクローズします.
プログラムの流れは次のようになります.
- 引数からファイル名を取得.
- 読みこむファイルのオープン.
- 書き出すファイルのオープン.
- コピー元のファイルから 1 バイトを読み込み.
- コピー先のファイルに 1 バイト書き出し.
- ファイル末でなければ 4. に戻る.
- オープンしたファイルのクローズ.
; -------------------------------------------------------------------------- ; ファイルの読み込みと書きこみ : ファイルコピー ; files.asm ; Jun Mizutani 5/21/2000 ; -------------------------------------------------------------------------- section .bss buf resd 1 ; バッファ rfn resd 1 ; read file name wfn resd 1 ; write file name rfd resd 1 ; fd wfd resd 1 ; fd section .text global _start %include "fcntl.inc" %include "stdio.inc" _start: pop eax ; 引数の数 argc pop ebx ; コマンド名取得 dec eax ; 実際の引数の数 cmp eax, 2 ; 引数は2個のみ jz .nx jmp arg_err ; if no args jmp noarg_err ; ファイル名引数の処理 .nx pop ebx ; pop filename pointer mov [rfn], ebx pop ebx ; pop filename pointer mov [wfn], ebx mov eax, [rfn] call OutAsciiZ mov eax, to_msg call OutAsciiZ mov ebx, [rfn] call fropen js open_err mov [rfd], eax mov eax, [wfn] call OutAsciiZ call NewLine mov ebx, [wfn] call fwopen js open_err mov [wfd], eax mov ecx, buf .loop mov ebx, [rfd] call fread1 test eax, eax js read_err jz done mov ebx, [wfd] call fwrite1 test eax, eax js write_err jmp short .loop done: mov eax, done_msg exit: push eax mov ebx, [rfd] call fclose mov ebx, [wfd] call fclose pop eax call OutAsciiZ call NewLine call Exit open_err: mov eax, e_open_msg jmp exit read_err: mov eax, e_read_msg jmp exit write_err: mov eax, e_write_msg jmp exit arg_err: mov eax, e_arg_msg jmp exit ; ファイルをオープン ; enter ebx: 第1引数 filename ; return eax: fd, if error then eax will be negative. ; int sys_open(const char * filename, int flags, int mode) fropen: mov ecx, O_RDONLY ; 第2引数 flag jmp short fopen fwopen: mov ecx, O_CREAT | O_WRONLY | O_TRUNC fopen: mov eax, SYS_open ; システムコール番号 mov edx, 0644q ; 第3引数 mode int 0x80 test eax,eax ; eax <- fd ret ; ファイルをクローズ ; enter ebx: 第1引数 ファイルディスクリプタ ; int sys_close(unsigned int fd) fclose: mov eax, SYS_close ; システムコール番号 int 0x80 ret ; ファイルから1バイト読みこみ ebx : fd ; enter ebx : 第1引数 ファイルディスクリプタ ; ecx : 第2引数 バッファアドレス ; ssize_t sys_read(unsigned int fd, char * buf, size_t count) fread1: mov eax, SYS_read ; システムコール番号 mov edx, 1 ; 第3引数 読みこみバイト数 int 0x80 ret ; ファイルへ1バイト書きこみ ; enter ebx : 第1引数 ファイルディスクリプタ ; ecx : 第2引数 バッファアドレス ; ssize_t sys_write(unsigned int fd, const char * buf, size_t count) fwrite1: mov eax, SYS_write ; システムコール番号 mov edx, 1 ; 第3引数 書きこみバイト数 int 0x80 ret to_msg db " --> ", 0 e_open_msg db "Can't open such file.", 10, 0 e_read_msg db "Can't read such file.", 10, 0 e_write_msg db "Can't write such file.", 10, 0 e_arg_msg db "Two filenames required.", 10, 0 done_msg db "completed.", 10, 0 ; --------------------------------------------------------------------------
コマンドライン引数は,コピー元のファイル名とコピー先のファイル名の 2つです.引数の個数が2以外ではエラーとなります.* などをファイル名に 指定しないで下さい.シェルによって展開された場合に既存のファイルを 上書きしてしまう場合があります.
コピー先のファイルは O_CREAT | O_WRONLY | O_TRUNC でオープンして いるため,存在しなければ作成,あれば上書きとなります.パーミッションは 644 になります.