
analysis of the fopen function
Introduction
C言語でfileの取り扱いをするときその類型のfunction動作原理について疑問になって簡単にそれぞれのfunctionについて分析する。今回はその一つ目となるfopen functionについて考える。
Body
まず、最初にfopen functionは__fopen_internal functionと繋がってそれを呼び出す。
#include <stdlib.h>
int main(int argc, char* argv[]) {
FILE *fp = fopen("./tmp", "r");
}
pwndbg> disassemble fopen
Dump of assembler code for function _IO_new_fopen:
0x00007ffff7c85e60 <+0>: mov edx, 0x1
0x00007ffff7c85e64 <+4>: jmp 0x7ffff7a7acc0 <__fopen_internal>
End of assembler dump.
__fopen_internal functionは以下のような構造で動作する。
_IO_FILE *__fopen_internal (const char *filename, const char *mode, int is32)
{
struct locked_FILE
{
struct _IO_FILE_plus fp;
#ifdef _IO_MTSAFE_IO
_IO_lock_t lock;
#endif
struct _IO_wide_data wd;
} *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE));
if (new_f == NULL)
return NULL;
#ifdef _IO_MTSAFE_IO
new_f->fp.file._lock = &new_f->lock;
#endif
_IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
_IO_new_file_init_internal (&new_f->fp);
#if !_IO_UNIFIED_JUMPTABLES
new_f->fp.vtable = NULL;
#endif
if (_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32) != NULL)
return __fopen_maybe_mmap (&new_f->fp.file);
_IO_un_link (&new_f->fp);
free (new_f);
return NULL;
}
まず、struct locked_FILEについて考える。(ここではスレットについてには話さない。)struct _IO_FILE_plusをfpそして、struct _IO_wide_dataをwdとして参照する。それぞれのstructは以下のように定義される。
struct _IO_FILE_plus, struct _IO_FILE
struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
struct _IO_wide_data
struct _IO_wide_data
{
wchar_t *_IO_read_ptr; /* Current read pointer */
wchar_t *_IO_read_end; /* End of get area. */
wchar_t *_IO_read_base; /* Start of putback+get area. */
wchar_t *_IO_write_base; /* Start of put area. */
wchar_t *_IO_write_ptr; /* Current put pointer. */
wchar_t *_IO_write_end; /* End of put area. */
wchar_t *_IO_buf_base; /* Start of reserve area. */
wchar_t *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */
wchar_t *_IO_backup_base; /* Pointer to first valid character of
backup area */
wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */
__mbstate_t _IO_state;
__mbstate_t _IO_last_state;
struct _IO_codecvt _codecvt;
wchar_t _shortbuf[1];
const struct _IO_jump_t *_wide_vtable;
};
new_fにはstruct locked_FILEのsizeを用いて、malloc functionにより割り当てられたpointerが入る。ここでfopenが失敗した場合NULLがreturnされる理由が確認できる。malloc functionはreturn valueとして割り当てられたpointerを返すが、それができなかったらNULLを返すことになる。これがfopenが失敗したらNULLが返される理由となる。次に、以下のようなコードを考える。
#endif
_IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
_IO_new_file_init_internal (&new_f->fp);
_IO_no_intや_IO_JUMPS, _IO_new_file-init_internalなどのfunctionを呼び出してfile strcutを初期化する。これについてそれぞれのfunctionを見ながら考えることとする。
_IO_no_init
void _IO_no_init (_IO_FILE *fp, int flags, int orientation,
struct _IO_wide_data *wd, const struct _IO_jump_t *jmp)
{
_IO_old_init (fp, flags);
fp->_mode = orientation;
if (orientation >= 0)
{
fp->_wide_data = wd;
fp->_wide_data->_IO_buf_base = NULL;
fp->_wide_data->_IO_buf_end = NULL;
fp->_wide_data->_IO_read_base = NULL;
fp->_wide_data->_IO_read_ptr = NULL;
fp->_wide_data->_IO_read_end = NULL;
fp->_wide_data->_IO_write_base = NULL;
fp->_wide_data->_IO_write_ptr = NULL;
fp->_wide_data->_IO_write_end = NULL;
fp->_wide_data->_IO_save_base = NULL;
fp->_wide_data->_IO_backup_base = NULL;
fp->_wide_data->_IO_save_end = NULL;
fp->_wide_data->_wide_vtable = jmp;
}
else
/* Cause predictable crash when a wide function is called on a byte
stream. */
fp->_wide_data = (struct _IO_wide_data *) -1L;
fp->_freeres_list = NULL;
}
これは、因子として上記のmallocから割り当てられたpointerとflags, orientation, struct pointer二つを持つ。まず最初にある_IO_old_initについては以下のコードから構成される。
_IO_old_init
void _IO_old_init (FILE *fp, int flags)
{
fp->_flags = _IO_MAGIC|flags; // 0xfbad0000下位2byteをflagとして使う。上位2byteは
//magic byteである。
fp->_flags2 = 0;
if (stdio_needs_locking)
fp->_flags2 |= _IO_FLAGS2_NEED_LOCK;
fp->_IO_buf_base = NULL;
fp->_IO_buf_end = NULL;
fp->_IO_read_base = NULL;
fp->_IO_read_ptr = NULL;
fp->_IO_read_end = NULL;
fp->_IO_write_base = NULL;
fp->_IO_write_ptr = NULL;
fp->_IO_write_end = NULL;
fp->_chain = NULL; /* 必ず必要ではない。 */
fp->_IO_save_base = NULL;
fp->_IO_backup_base = NULL;
fp->_IO_save_end = NULL;
fp->_markers = NULL;
fp->_cur_column = 0;
#if _IO_JUMPS_OFFSET
fp->_vtable_offset = 0;
#endif
#ifdef _IO_MTSAFE_IO
if (fp->_lock != NULL)
_IO_lock_init (*fp->_lock);
#endif
}
このfunctionはただ、struct _IO_FILE_plusにあるstruct _IO_FILEのmember variableをすべて初期化する。次となる以下のコードも同じ役割をする。
fp->_mode = orientation;
if (orientation >= 0)
{
fp->_wide_data = wd;
fp->_wide_data->_IO_buf_base = NULL;
fp->_wide_data->_IO_buf_end = NULL;
fp->_wide_data->_IO_read_base = NULL;
fp->_wide_data->_IO_read_ptr = NULL;
fp->_wide_data->_IO_read_end = NULL;
fp->_wide_data->_IO_write_base = NULL;
fp->_wide_data->_IO_write_ptr = NULL;
fp->_wide_data->_IO_write_end = NULL;
fp->_wide_data->_IO_save_base = NULL;
fp->_wide_data->_IO_backup_base = NULL;
fp->_wide_data->_IO_save_end = NULL;
fp->_wide_data->_wide_vtable = jmp;
}
else
/* Cause predictable crash when a wide function is called on a byte
stream. */
fp->_wide_data = (struct _IO_wide_data *) -1L;
fp->_freeres_list = NULL;
}
つまり、この段階ではまだfileを読み込む動作は行わずそのfileを読み込むために必要なvariableなどの初期化を行う。(もちろん、今回は上記のようにfunctionを呼び出す際にその因子を構成しているためそれぞれの変数はそれに対応する。)ここまでの段階でのまとめは、以下のようになる。
fopen functionが呼び出されるとと、内部的に__fopen_internalが呼び出される。
__fopen_internalではstruct __IO_FILE_plus(__IO_FILEとvtableのmember)とstruct __IO_wide_dataをそれぞれ参照したloked_FILEというstructを作る。
struct locked_FILEのvariable new_f pointerにそのstructの大きさ分memoryを割り当ててそのpointerを入れる。
new_fに入ってるpointerを用いて、struct _IO_FILEやstruct _IO_wide_dataを初期化し、fileを読み込むための準備を行う。
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
次に考えるfunctionは_IO_JUMPSである。
extern const struct _IO_jump_t _IO_file_jumps;
global variable として_IO_jump_tとして入ってることが確認できる。これは、以下のようなstructから構成される。
struct _IO_jump_t
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
get_column;
set_column;
#endif
};
これはvtable overwriteやFSOPなどについて話す機会があるとき詳細に考えたいと思う。簡単に言うと、これはfreadやfwrite, puts, getsなどのfunctionが呼び出されたとき、そのfunctionの位置をoffsetとして算出するときのbase addressである。つまり、そのbase addressを指定することであると理解してもいい。(vtable)ここでは、そのvtableを初期化する。
_IO_new_file_init_internal (&new_f->fp);
次に_IO_new_file_init_internal functionである。
_IO_new_file_init_internal
void _IO_new_file_init_internal (struct _IO_FILE_plus *fp)
{
/* POSIX.1 allows another file handle to be used to change the position
of our file descriptor. Hence we actually don't know the actual
position before we do the first fseek (and until a following fflush). */
fp->file._offset = _IO_pos_BAD;
fp->file._IO_file_flags |= CLOSED_FILEBUF_FLAGS;
_IO_link_in (fp);
fp->file._fileno = -1; //まだfileを開いてないことを示す。
}
この段階もまだfileの読み込みはされていない状況であるため、file.offsetにエラーが発生したと仮定する。(いずれにしても_IO_file_fopen functionでこれらは修正される。)そして、_IO_file_flagsも同じようにエラーが発生したと仮定して設定する。ここで単純に仮定して設定する理由としては、最後に考える_IO_file_fopen functionで理解できる。そして、_IO_link_in functionを呼び出す。そのコードは以下のようになる。
_IO_link_in
void _IO_link_in (struct _IO_FILE_plus *fp)
{
if ((fp->file._flags & _IO_LINKED) == 0) //_IO_LINKED 0x80
{ //Linkされてない時が正となる。
fp->file._flags |= _IO_LINKED;
#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
run_fp = (FILE *) fp;
_IO_flockfile ((FILE *) fp);
#endif
fp->file._chain = (FILE *) _IO_list_all;
if (_IO_vtable_offset ((FILE *) fp) == 0)
{
fp->file._prevchain = (FILE **) &_IO_list_all;
if (_IO_list_all != NULL)
_IO_list_all->file._prevchain = &fp->file._chain;
}
_IO_list_all = fp;
#ifdef _IO_MTSAFE_IO
_IO_funlockfile ((FILE *) fp);
run_fp = NULL;
_IO_lock_unlock (list_all_lock);
_IO_cleanup_region_end (0);
#endif
}
}
_IO_link_in functionが行う作業はchain, _IO_list_allについてのsingle linked listを構成する。_IO_list_allはfile streamの一番最初(head)を示す接続リストである。これをchainに繋ぐことにより、以下のような関係が成り立つ。
_IO_list_all -> [stderr] -> [stdout] -> [stdin] -> ... -> NULL
次に、_IO_list_all = fpにより、以下のような関係も成り立つ。
_IO_list_all -> [fp] -> [chain] -> [stderr] -> [stdout] -> [stdin] -> ... -> NULL
つまり、_IO_list_allからsingle liked listを構成して、structを管理する。ここまでの過程についてまとめると次のようになる。
fopen functionが呼び出されるとと、内部的に__fopen_internalが呼び出される。
__fopen_internalではstruct __IO_FILE_plus(__IO_FILEとvtableのmember)とstruct __IO_wide_dataをそれぞれ参照したloked_FILEというstructを作る。
struct locked_FILEのvariable new_f pointerにそのstructの大きさ分memoryを割り当ててそのpointerを入れる。
new_fに入ってるpointerを用いて、struct _IO_FILEやstruct _IO_wide_dataを初期化し、fileを読み込むための準備を行う。
struct _IO_jump_tからvtableを構成するための初期化を行う。
chainと_io_list_allからsingle linked listを構成する。(file streamの取り扱いのため)
次に考えるコードは以下のコードである。
#if !_IO_UNIFIED_JUMPTABLES
new_f->fp.vtable = NULL;
#endif
if (_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32) != NULL)
return __fopen_maybe_mmap (&new_f->fp.file);
_IO_un_link (&new_f->fp);
free (new_f);
return NULL;
まず、_IO_file_fopen functionから考える。そのコードは以下のようになる。
_IO_file_fopen
#define _IO_NO_READS 0x0004 /* readを許可しない。 */
#define _IO_NO_WRITES 0x0008 /* writeを許可しない。 */
#define _IO_IS_APPENDING 0x1000
#define O_RDONLY 0x0000 /* only read */
#define O_WRONLY 0x0001 /* only write */
#define O_RDWR 0x0002 /* read & write */
#define O_CREAT 0x0200 /* fileがない場合作る。 */
#define O_TRUNC 0x0400 /* fileがある場合にはすべてを消す */
#define O_EXCL 0x0800 /* すでに存在すればエラーする。 */
#define _IO_FLAGS2_MMAP 1
#define _IO_FLAGS2_NOTCANCEL 2
#define _IO_FLAGS2_CLOEXEC 64
FILE *_IO_new_file_fopen (FILE *fp, const char *filename, const char *mode,
int is32not64)
{
int oflags = 0, omode;
int read_write;
int oprot = 0666;
int i;
FILE *result;
const char *cs;
const char *last_recognized;
if (_IO_file_is_open (fp)) //fileno=-1であることを確認する。
return NULL;
switch (*mode)
{
case 'r':
omode = O_RDONLY;
read_write = _IO_NO_WRITES;
break;
case 'w':
omode = O_WRONLY;
oflags = O_CREAT|O_TRUNC;
read_write = _IO_NO_READS;
break;
case 'a':
omode = O_WRONLY;
oflags = O_CREAT|O_APPEND;
read_write = _IO_NO_READS|_IO_IS_APPENDING;
break;
default:
__set_errno (EINVAL);
return NULL;
}
last_recognized = mode;
for (i = 1; i < 7; ++i)
{
switch (*++mode)
{
case '\0':
case ',':
break;
case '+':
omode = O_RDWR;
read_write &= _IO_IS_APPENDING;
last_recognized = mode;
continue;
case 'x':
oflags |= O_EXCL;
last_recognized = mode;
continue;
case 'b':
last_recognized = mode;
continue;
case 'm':
fp->_flags2 |= _IO_FLAGS2_MMAP;
continue;
case 'c':
fp->_flags2 |= _IO_FLAGS2_NOTCANCEL;
continue;
case 'e':
oflags |= O_CLOEXEC;
fp->_flags2 |= _IO_FLAGS2_CLOEXEC;
continue;
default:
/* Ignore. */
continue;
}
break;
}
result = _IO_file_open (fp, filename, omode|oflags, oprot, read_write,
is32not64);
...
このfunctionは分析する前に見た感じとしてはfopen("test", "r")のようにfileをどのような作業をするのかを設定するr,w,xなどによってflagを設定するようである。(…の続きは_IO_file_openの次で考える)
if (_IO_file_is_open (fp)) //fileno=-1であることを確認する。
return NULL;
_IO_new_file_init_internal functionからfile streamに関係するすべてのvariableの初期化を行うとき、まだfileを開いてないflagとして-1を入れたのを前述した。_IO_new_file_fopen functionは実際fileを読み込むものであるため、このflagを読みとって実際fileの開きが行われてないことを確認する。
switch (*mode)
{
case 'r':
omode = O_RDONLY;
read_write = _IO_NO_WRITES;
break;
case 'w':
omode = O_WRONLY;
oflags = O_CREAT|O_TRUNC;
read_write = _IO_NO_READS;
break;
case 'a':
omode = O_WRONLY;
oflags = O_CREAT|O_APPEND;
read_write = _IO_NO_READS|_IO_IS_APPENDING;
break;
default:
__set_errno (EINVAL);
return NULL;
}
ここでは、r, w, xなどの文字からそれぞれに対応するomode, read_writeを更新する。今回の例題としてはopen("./tmp", "r")となるため、case 'r'に相当する。これにより、omodeはO_RDONLYと(Read only)となり、unwritableとするために_IO_NO_WRITESと設定する。このようにfopenのmodeによって与えられる権限を設定する。
last_recognized = mode;
for (i = 1; i < 7; ++i)
{
switch (*++mode)
{
case '\0':
case ',':
break;
case '+':
omode = O_RDWR;
read_write &= _IO_IS_APPENDING;
last_recognized = mode;
continue;
case 'x':
oflags |= O_EXCL;
last_recognized = mode;
continue;
case 'b':
last_recognized = mode;
continue;
case 'm':
fp->_flags2 |= _IO_FLAGS2_MMAP;
continue;
case 'c':
fp->_flags2 |= _IO_FLAGS2_NOTCANCEL;
continue;
case 'e':
oflags |= O_CLOEXEC;
fp->_flags2 |= _IO_FLAGS2_CLOEXEC;
continue;
default:
/* Ignore. */
continue;
}
break;
}
次に、last_recognizedはmodeの因子をそのまま受け取って、その次の文字を示す。例えば、fopen("./tmp", "r")であれば、rの次は文字は存在しないので、'\0'となる。もし、fopen("./tmp", "rwx")やfopen("./tmp", "r+")のような場合にも同じようにそれぞれに相当するmodeをlast_recognizedに入れる。
result = _IO_file_open (fp, filename, omode|oflags, oprot, read_write,
is32not64);
次は、いよいよ_IO_file_open functionとなる。ここからはsyscallによるlow-levelから実際fileを開いて読み込む。
_IO_file_open
FILE *
_IO_file_open (FILE *fp, const char *filename, int posix_mode, int prot,
int read_write, int is32not64)
{
int fdesc;
if (__glibc_unlikely (fp->_flags2 & _IO_FLAGS2_NOTCANCEL))
fdesc = __open_nocancel (filename,
posix_mode | (is32not64 ? 0 : O_LARGEFILE), prot);
else
fdesc = __open (filename, posix_mode | (is32not64 ? 0 : O_LARGEFILE), prot);
if (fdesc < 0)
return NULL;
fp->_fileno = fdesc;
_IO_mask_flags (fp, read_write,_IO_NO_READS+_IO_NO_WRITES+_IO_IS_APPENDING);
/* For append mode, send the file offset to the end of the file. Don't
update the offset cache though, since the file handle is not active. */
if ((read_write & (_IO_IS_APPENDING | _IO_NO_READS))
== (_IO_IS_APPENDING | _IO_NO_READS))
{
off64_t new_pos = _IO_SYSSEEK (fp, 0, _IO_seek_end);
if (new_pos == _IO_pos_BAD && errno != ESPIPE)
{
__close_nocancel (fdesc);
return NULL;
}
}
_IO_link_in ((struct _IO_FILE_plus *) fp);
return fp;
まず、__open_nocancel functionや__open functionからsyscallが起こり、実際のfileの書き込みが行われる。ここで、fileのflagを設定し、file descriptorをreturnする。_IO_mask_flags functionは、flagのread_writeなど前述で設定したread_write variableの値によってそのfileのpermissionを与える。そして、前述からIO_pos_BADに設定した値を_IO_seek_end functionからfile pointerの移動するoffsetを設定する。(多分ここで更新されるはず)それで、returnされたnew_posの値が_IO_pos_BADのそのままであり、ESPIPEが出た場合にはfile読み込みが失敗したと判断する。そして、_IO_link_in functionから再びlinked listを更新して、fpをreturnする。
最後に、以下のコードについて考える。
return __fopen_maybe_mmap (&new_f->fp.file);
_IO_un_link (&new_f->fp);
__fopen_maybe_mmap
FILE *
__fopen_maybe_mmap (FILE *fp)
{
#if _G_HAVE_MMAP
if ((fp->_flags2 & _IO_FLAGS2_MMAP) && (fp->_flags & _IO_NO_WRITES))
{
/* Since this is read-only, we might be able to mmap the contents
directly. We delay the decision until the first read attempt by
giving it a jump table containing functions that choose mmap or
vanilla file operations and reset the jump table accordingly. */
if (fp->_mode <= 0)
_IO_JUMPS_FILE_plus (fp) = &_IO_file_jumps_maybe_mmap;
else
_IO_JUMPS_FILE_plus (fp) = &_IO_wfile_jumps_maybe_mmap;
fp->_wide_data->_wide_vtable = &_IO_wfile_jumps_maybe_mmap;
}
#endif
return fp;
}
ここでは、ただflagと与えられたpermissionによってvtableを設定して、fp pointerをreturnする。
_IO_un_link
void
_IO_un_link (struct _IO_FILE_plus *fp)
{
if (fp->file._flags & _IO_LINKED)
{
FILE **f;
#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
run_fp = (FILE *) fp;
_IO_flockfile ((FILE *) fp);
#endif
if (_IO_list_all == NULL)
;
else if (_IO_vtable_offset ((FILE *) fp) == 0)
{
FILE **pr = fp->file._prevchain;
FILE *nx = fp->file._chain;
*pr = nx;
if (nx != NULL)
nx->_prevchain = pr;
}
else if (fp == _IO_list_all)
_IO_list_all = (struct _IO_FILE_plus *) _IO_list_all->file._chain;
else
for (f = &_IO_list_all->file._chain; *f; f = &(*f)->_chain)
if (*f == (FILE *) fp)
{
*f = fp->file._chain;
break;
}
fp->file._flags &= ~_IO_LINKED;
#ifdef _IO_MTSAFE_IO
_IO_funlockfile ((FILE *) fp);
run_fp = NULL;
_IO_lock_unlock (list_all_lock);
_IO_cleanup_region_end (0);
#endif
}
}
このfunctionは、そもそもfileの読み込みができなかった場合だけ実行される。名前だけ見た感じだと、このfunctionはlinkedされてたpointerを解除する役割をすると考えられる。
if (_IO_list_all == NULL)
;
else if (_IO_vtable_offset ((FILE *) fp) == 0)
{
FILE **pr = fp->file._prevchain;
FILE *nx = fp->file._chain;
*pr = nx;
if (nx != NULL)
nx->_prevchain = pr;
}
まず、_IO_list_all == NULLつまり、何もlinkingされてない場合には、すでにunlinkedされていることと同じように考える。次に、_IO_vtable_offsetが0である場合、以前のfile streamと現在のfile streamを同じようにする。
else if (fp == _IO_list_all)
_IO_list_all = (struct _IO_FILE_plus *) _IO_list_all->file._chain;
else
for (f = &_IO_list_all->file._chain; *f; f = &(*f)->_chain)
if (*f == (FILE *) fp)
{
*f = fp->file._chain;
break;
}
fp == _IO_list_allつまり、listの最も前にある場合には、listをその次のfile streamに変える。そして、繰り返しによってfがlistに繋がっているfile streamの最初から現在のfile streamまで巡回するながらそのfile stream pointerが一致する場合には、その次のfile streamに設定する。
まとめると、
else if (fp == _IO_list_all) : linked listの最も前(もしくは後ろ)の場合
for (f = &_IO_list_all->file._chain; *f; f = &(*f)->_chain) : linked listの間にある場合
として場合分ができる。そして、最終的にはfp->file._flags &= ~_IO_LINKED;によってflagをunlinkedされたものに書き換え終了する。
以上の分析結果から以下のような図が描ける。

conclusion
今回は、fopen functionの構造やそのflowについて考えた。表からの名前やrwxなどの単純な入力を裏では非常に細かく場合わけを行い、最終的にはsyscallまで、なんとなく素晴らしい人多いなと思う機会であった。
次は、fread functionも分析したいと思う。
ref