一切皆文件Everything is FILE
“一切皆是文件” 是Linux/Unix系统的基本哲学之一。即Linux的所有都可以通过文件的方式访问、管理。即使不是文件,也通过文件的形式来管理。例如将硬件、进程等都抽象成一个文件,使用统一的接口管理,这就是 _IO_FILE
_IO_FILE
_IO_FILE
是glibc中用于表述一个文件流的结构体。在Linux一切皆是文件的设计哲学之中,程序的所有输入输出都是通过文件流来实现。在一般的程序中总会处理输入、输出和出现错误,这就引出了我们三个非常重要的文件流:
_IO_2_1_stderr
:fd=2
标准错误流_IO_2_1_stdout
:fd=1
标准输出流_IO_2_1_stdin
:fd=0
标准输入流
这三个文件流的本质都是同一个结构体_IO_FILE
如下图所示。
所有的文件流之间由结构体中的成员_chain
互相连接,最终形成一个_IO_list_all
的链表。每当我们在程序中打开一个新的文件时就会生成一个新的文件流,glibc会将新生成的文件按照头插法连入到该链表中。
_IO_list_all是_IO_FILE_plus类型的结构体,它指向了一串_IO_FILE_plus的结构体,这些结构体的有序序列构成了管理程序中所有文件流的单链表。
glibc2.23的_IO_FILE
结构体定义如下
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
};
在标准IO中,每个程序启动时标准输入输出流是自动打开的,因此在初始状态下,_IO_list_all 只想了一个由这些文件流构成的链表。但需要注意这三个文件流位于libc.so的数据段,而fopen后续创建的文件流是分配到堆上的。
这里介绍一个重要成员_flags
,这个成员标记了所在文件流的读写属性。我们在_IO_old_init
会提到
事实上,_IO_FILE被封装在_IO_FILE_plus
结构体中,其中包含了一个重要的眺表指针vtable
struct _IO_FILE_plus
{
_IO_FILE file;
IO_jump_t *vtable
}
注意在此处有一个宏定义
#ifdef _IO_USE_OLD_IO_FILE
实际上该宏并没有被定义,所以紧邻着这个宏定义判断的代码全部失效
#ifdef _IO_USE_OLD_IO_FILE
};
struct _IO_FILE_complete
{
struct _IO_FILE _file;
#endif
也就是说,整个_IO_FILE_complete
都被合并到了_IO_FILE
结构体中 那么实际上我们的_IO_FILE
应该是这样的
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
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
_IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
/* Wide character stream stuff. */
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
# else
void *__pad1;
void *__pad2;
void *__pad3;
void *__pad4;
# endif
size_t __pad5;
int _mode;
/* Make sure we don't get into trouble again. */
char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
#endif
};
介绍一下结构体的重要成员
_IO_read_ptr : 输入缓冲区的当前地址
_IO_read_end : 输入缓冲区的结束地址
_IO_read_base : 输入缓冲区的起始地址
_IO_write_base : 输出缓冲区的起始地址
_IO_write_end : 输出缓冲区的结束地址
_IO_write_ptr : 输出缓冲区的当前地址
_IO_buf_base : 输入输出缓冲区的起始地址
_IO_buf_end : 输入输出缓冲区的结束地址
_IO_save_base : 备份缓冲区的起始地址
_IO_backup_base : 备份缓冲区第一个有效字符的指针
_IO_save_end : 备份缓冲区的结束地址
_chain : 下一个文件流的地址
_fileno : 文件描述符
_flags2 : 标志符
_old_offset : 没有初始化之前的偏移
_cur_column : 表示文件流中的行数
_vtable_offset : 虚表指针偏移
_shortbuf : 短buf地址
_lock : 锁结构体地址
_offset : 文件描述符的偏移
_codecvt : 宽字符函数表
_wide_data : 宽字节流指针
fopen
_IO_new_fopen
fopen
是一个可以被调用的外部函数,它主要用于在程序中创建文件流,现在我们来分析它的源码。
FILE *fopen(char *filename, *type)
首先fopen
经过_dl_runtime_resolve_xsavec
解析过后是_IO_new_fopen
_IO_FILE *
_IO_new_fopen (const char *filename, const char *mode)
{
return __fopen_internal (filename, mode, 1);
}
该函数的两个参数都存储在.rodata上,该函数调用了__fopen_internal
_IO_new_fopen -> __fopen_internal
_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
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
_IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
#else
_IO_no_init (&new_f->fp.file, 1, 0, NULL, NULL);
#endif
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
_IO_file_init (&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;
}
进入到__fopen_internal
立即申请了一个locked_FILE
结构体,我们在这里做一下拆分
----- lock_FILE
|
----- struct _IO_FILE_plus fp
| |
| ----- _IO_FILE file
| | |
| | --- ...
| ----- const struct _IO_jump_t *vtable
----- _IO_lock_t lock;
|
----- struct _IO_wide_data wd
|
----- ...
可以看出结构体locked_FILE
由三部分组成
- 文件流
_IO_FILE_plus
:这是该结构体的结构核心,locked_FILE
由该结构体拓展而来。如果把_IO_FILE
看作是一个父类,那么locked_FILE
就是它的子类 _IO_lock_t
:代表该文件流的锁,意味着在同一个时间点只能有一个线程访问该临界区_IO_wide_data
:为了读取宽字节而定义的结构体,结构与_IO_FILE_plus
相似
*new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE));
随后申请一个大小为locked_FILE
相同的堆去作为缓冲区,让new_f
指针指向这个user_data如果失败NULL
说明文件读取失败
否则继续执行接下来的操作
#ifdef _IO_MTSAFE_IO
new_f->fp.file._lock = &new_f->lock;
#endif
获取已经展开的结构体lock_FILE
的成员lock
的地址并赋值给_IO_FILE_plus
的成员_lock
这样我们就可直接访问file
来访问_IO_lock_t
的内存
__fopen_internal
实际上创建了一个存放在堆中的新的文件流
__fopen_internal -> _IO_no_init
对于_IO_no_init
存在两个入口
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
_IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
#else
_IO_no_init (&new_f->fp.file, 1, 0, NULL, NULL);
#endif
第一个入口使用宽字节,第二个入口不使用宽字节。我们重点看第一个入口
为了兼容相较于ascii更广泛的编码格式,比如UTF-8,glibc提供了宽字节流也就是
_IO_wide_data
结构体。结构体成员中wchar_t
成员为四字节,要比char类型大得多,包含一个struct _IO_codecvt _codecvt
字符编码转换函数表对非ascii进行兼容
函数原型
void
_IO_no_init (_IO_FILE *fp, int flags, int orientation,
struct _IO_wide_data *wd, const struct _IO_jump_t *jmp)
我们重点看传入的_IO_jump_t
的结构体指针jmp
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
};
和_IO_FILE_plus
中的vtable
相同的跳表指针。而vtable
正是C++使用的虚函数的虚表指针,而_IO_jump_t
则是与其对应的虚函数表,即虚表。
我们来看一下JUMP FIELD
的宏定义
#define JUMP_FIELD(TYPE, NAME) TYPE NAME
会对每一个成员进行定义,之后在_IO_wfile_jumps
对其赋值。
const struct _IO_jump_t _IO_wfile_jumps =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_new_file_finish),
JUMP_INIT(overflow, (_IO_overflow_t) _IO_wfile_overflow),
JUMP_INIT(underflow, (_IO_underflow_t) _IO_wfile_underflow),
JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wdefault_pbackfail),
JUMP_INIT(xsputn, _IO_wfile_xsputn),
JUMP_INIT(xsgetn, _IO_file_xsgetn),
JUMP_INIT(seekoff, _IO_wfile_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_new_file_setbuf),
JUMP_INIT(sync, (_IO_sync_t) _IO_wfile_sync),
JUMP_INIT(doallocate, _IO_wfile_doallocate),
JUMP_INIT(read, _IO_file_read),
JUMP_INIT(write, _IO_new_file_write),
JUMP_INIT(seek, _IO_file_seek),
JUMP_INIT(close, _IO_file_close),
JUMP_INIT(stat, _IO_file_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
JUMP_INIT
#define JUMP_INIT(NAME, VALUE) VALUE
注意到它会返回VALUE,也就是对应函数在内存中的起始位置。
我们继续追踪_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 defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
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;
#endif
fp->_freeres_list = NULL;
}
如果启用了宽字节的情况下,会初始化一系列的_wide_data
中的成员和_wide_vtable
宽字节虚表的指针。在此之前会进入_IO_lod_init
。
_IO_no_init -> _IO_old_init
_IO_no_init
的名称可以直译为“当前的文件流没有初始化”,所以这个函数会执行初始化流程。那么_IO_old_init
可以译为“先前的初始化”。
void
_IO_old_init (_IO_FILE *fp, int flags)
{
fp->_flags = _IO_MAGIC|flags;
fp->_flags2 = 0;
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; /* Not necessary. */
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
}
可以看到这个函数主要是对_IO_FILE
进行初始化,也就是我们文件流的核心。而先前的函数是对宽字节进行初始化。如果没有宽字节,那么先前的函数相当于对这个函数的一次封装。
_IO_old_init
会将_IO_FILE fp
中的所有成员全部置空。最重要的是对_flags
成员进行赋值,它由与魔术头_IO_MAGIC=0xFBAD
与flags
进行与运算得到。而flags
是在_IO_no_init
传入的,考查是否启用了宽字节。
此外_IO_old_init
调用了_IO_lock_init
,在此不多赘述。
阶段性总结
总结一下当前的调用链
fopen -> _IO_new_fopen -> __fopen_internal -> _IO_no_init -> _IO_old_init
自此完成部分初始化流程:宽字节+_IO_FILE
__fopen_internal <- _IO_no_init
返回到__fopen_internal
进行跟踪。
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
这里初始化眺表指针vtable
_IO_file_init (&new_f->fp);
然后调用了_IO_file_init
传入了文件流指针
__fopen_internal -> _IO_file_init(_IO_new_file_init)
注意
# define _IO_new_file_init _IO_file_init
进入_IO_new_file_init
void
_IO_new_file_init (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;
}
对_offset
和_flags
重新赋值,跟进_IO_pos_BAD
#define _IO_pos_BAD ((_IO_off64_t)(-1))
#define _IO_off64_t __off64_t
_IO_pos_BAD
是一个值为_IO_off64_t -1
的宏定义(long int)。它表示操作文件遇到了错误、未知或异常。设置后_offset == -1
跟进CLOSED_FILEBUF_FLAGS
#define CLOSED_FILEBUF_FLAGS \
(_IO_IS_FILEBUF+_IO_NO_READS+_IO_NO_WRITES+_IO_TIED_PUT_GET)
#define _IO_IS_FILEBUF 0x2000
#define _IO_NO_READS 4 /* Reading not allowed */
#define _IO_NO_WRITES 8 /* Writing not allowd */
#define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */
_IO_NO_READS
:不允许对当前文件流进行读取_IO_NO_WRITES
:不允许对当前文件流进行写入_IO_TIED_PUT_GET
:表示与puts和gets函数进行了逻辑绑定_IO_IS_FILEBUF
:文件流标识符
最后的该宏表示当前新建的文件流缓冲区已经关闭,不允许在当前执行流进行读取和写入。设置后的_flags == 0xFBAD240C
然后进入_IO_link_in
_IO_file_init -> _IO_link_in
这个函数主要将新生成的文件流链入_IO_list_all
链表,我们来看实现
void
_IO_link_in (struct _IO_FILE_plus *fp)
{
if ((fp->file._flags & _IO_LINKED) == 0)
{
fp->file._flags |= _IO_LINKED;
#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
run_fp = (_IO_FILE *) fp;
_IO_flockfile ((_IO_FILE *) fp);
#endif
fp->file._chain = (_IO_FILE *) _IO_list_all;
_IO_list_all = fp;
++_IO_list_all_stamp;
#ifdef _IO_MTSAFE_IO
_IO_funlockfile ((_IO_FILE *) fp);
run_fp = NULL;
_IO_lock_unlock (list_all_lock);
_IO_cleanup_region_end (0);
#endif
}
}
第一步判断_flags
是否设置了_IO_LINKED
标志位,判断该文件流是否已经链入链表。
#define _IO_LINKED 0x80
进入判断,对_flags
做处理,_flags == 0xfbad248c
为了防止条件竞争,对文件流加锁
#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
run_fp = (_IO_FILE *) fp;
_IO_flockfile ((_IO_FILE *) fp);
#endif
注意此时_IO_list_all
已经初始化,图示如下
_IO_list_all是_IO_FILE_plus类型的结构体,它指向了一串_IO_FILE_plus的结构体,这些结构体的有序序列构成了管理程序中所有文件流的单链表。
阶段性总结
总结一下当前的调用链
fopen -> _IO_new_fopen -> __fopen_internal -> _IO_file_init <=> _IO_new_file_init -> _IO_link_in
该调用链初始化了虚表,将该文件流链入了_IO_list_all
链表
__fopen_internal <- _IO_file_init
回退到__fopen_internal
#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);
这里先宏判断,如果眺表没定义就定义为NULL,然后调用fopen的核心函数_IO_file_fopen
__fopen_internal -> _IO_file_fopen(_IO_new_file_fopen)
同样,_IO_file_fopen
被包装为了_IO_new_file_fopen
这个函数比较长我们分段分析,先来看看原型
_IO_FILE *
_IO_new_file_fopen (_IO_FILE *fp, const char *filename, const char *mode,
int is32not64)
注意这里传入的fp指针不再是_IO_FILE_plus
的指针,这里发生了强制类型转换。返回值同样是一个_IO_FILE
结构体指针。
开头先定义了一些变量
int oflags = 0, omode;
int read_write;
int oprot = 0666;
int i;
_IO_FILE *result;
#ifdef _LIBC
const char *cs;
const char *last_recognized;
#endif
oflag
:表示文件修改的方式,新建or追加omode
:表示文件打开的模式,读写执行oprot
:表示文件的权限i
:indexresult
:返回的文件流指针
然后在此处调用了新的函数
if (_IO_file_is_open (fp))
return 0;
_IO_file_fopen -> _IO_file_is_open
这是一个宏函数,它检查结构体中的文件标识符是否为-1,即检查_IO_list_all中是否有新增加的文件流,如果没有会直接返回。
#define _IO_file_is_open(__fp) ((__fp)->_fileno != -1)
_IO_file_fopen <- _IO_file_is_open
回退。继续跟进_IO_file_fopen
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;
}
这里对omode
进行定义。我们用一个表来整理这些变量的关系
mode | omode | oflags |
---|---|---|
r | O_RDONLY 只读 | NULL 无 |
w | O_WRONLY 只写 | O_CREAT | O_TRUNC 新建或覆盖 |
a | O_WRONLY 只写 | O_CREAT | O_APPEND 新建或追加 |
接下来继续遍历mode进行处理。 |
for (i = 1; i < 7; ++i)
{
switch (*++mode)
{
case '\0':
break;
case '+':
omode = O_RDWR;
read_write &= _IO_IS_APPENDING;
#ifdef _LIBC
last_recognized = mode;
#endif
continue;
case 'x':
oflags |= O_EXCL;
#ifdef _LIBC
last_recognized = mode;
#endif
continue;
case 'b':
#ifdef _LIBC
last_recognized = mode;
#endif
continue;
case 'm':
fp->_flags2 |= _IO_FLAGS2_MMAP;
continue;
case 'c':
fp->_flags2 |= _IO_FLAGS2_NOTCANCEL;
continue;
case 'e':
#ifdef O_CLOEXEC
oflags |= O_CLOEXEC;
#endif
fp->_flags2 |= _IO_FLAGS2_CLOEXEC;
continue;
default:
/* Ignore. */
continue;
整理为表格
mode | omode | oflags | read_write | _flags2 |
---|---|---|---|---|
’\0’ | NoChange | NoChange | Nochange | Nochange |
’+‘ | O_RDWR | NoChange | &=_IO_IS_APPENDING | |
’x’ | NoChange | |= O_EXCL | NoChange | NoChange |
’b’ | NoChange | NoChange | NoChange | NoChange |
’m’ | NoChange | NoChange | NoChange | |= _IO_FLAGS2_MMAP |
’c’ | NoChange | NoChange | NoChange | |= _IO_FLAGS2_NOTICANCEL |
’e’ | NoChange | |= O_CLOEXEC | NoChange | |= _IO_FLAGS2_CLOEXEC |
随后调用_IO_file_open
result = _IO_file_open (fp, filename, omode|oflags, oprot, read_write,
is32not64);
_IO_file_fopen -> _IO_file_open
_IO_FILE *
_IO_file_open (_IO_FILE *fp, const char *filename, int posix_mode, int prot,
int read_write, int is32not64)
{
int fdesc;
#ifdef _LIBC
if (__glibc_unlikely (fp->_flags2 & _IO_FLAGS2_NOTCANCEL))
fdesc = open_not_cancel (filename,
posix_mode | (is32not64 ? 0 : O_LARGEFILE), prot);
else
fdesc = open (filename, posix_mode | (is32not64 ? 0 : O_LARGEFILE), prot);
#else
fdesc = open (filename, posix_mode, prot);
#endif
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))
{
_IO_off64_t new_pos = _IO_SYSSEEK (fp, 0, _IO_seek_end);
if (new_pos == _IO_pos_BAD && errno != ESPIPE)
{
close_not_cancel (fdesc);
return NULL;
}
}
_IO_link_in ((struct _IO_FILE_plus *) fp);
return fp;
}
在函数的开头先判断该文件流的_flags2是否设置了_IO_FLAGS2_NOTICANCEL属性,有则执行open_not_cancel
没有执行open
。这里进入else分支,实际上调用了syscall open
。返回后fdesc == 3
_IO_file_open -> _IO_mask_flags
然后调用这个函数对_flags进行重新设置
_IO_mask_flags (fp, read_write,_IO_NO_READS+_IO_NO_WRITES+_IO_IS_APPENDING);
这是一个宏函数
#define _IO_mask_flags(fp, f, mask) \
((fp)->_flags = ((fp)->_flags & ~(mask)) | ((f) & (mask)))
会根据之前switch case得到的read_write
对_flags
重新设置。
_IO_file_open <- _IO_mask_flags
回退。
if ((read_write & (_IO_IS_APPENDING | _IO_NO_READS))
== (_IO_IS_APPENDING | _IO_NO_READS))
{
_IO_off64_t new_pos = _IO_SYSSEEK (fp, 0, _IO_seek_end);
if (new_pos == _IO_pos_BAD && errno != ESPIPE)
{
close_not_cancel (fdesc);
return NULL;
}
}
_IO_link_in ((struct _IO_FILE_plus *) fp);
return fp;
如果读写方式为追加,那么将文件末尾作为文件流的指针。最后还会调用一次_IO_link_in
_IO_file_open -> _IO_link_in
这里调用linkin是确保该结构体已经链入_IO_list_all
,至此_IO_file_open
执行完毕
if ((fp->file._flags & _IO_LINKED) == 0)
阶段性总结
由于_IO_file_fopen
剩下的代码是对宽字节进行处理,这里不多研究,直接回退到__fopen_internal
在此之前总结一下当前的调用链
fopen -> _IO_new_fopen -> _fopen_internal -> _IO_file_fopen -> _IO_file_is_open
初始化局部变量,检查文件描述符是否设置
fopen -> _IO_new_fopen -> _fopen_internal -> _IO_file_fopen -> _IO_file_open -> _IO_mask_flags -> _IO_file_open -> _IO_link_in
对文件进行进一步初始化,特别初始化了权限和打开模式,正式调用syscall打开了一个文件流。
__fopen_internal <- _IO_file_fopen
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);//uaf!
return NULL;
如果无法进入if语句,即返回值为NULL,那么表示文件打开失败,此时会释放new_f
。反之进入__fopen_maybe_mmap
主要是处理mmap的情况,堆空间不足。
总结
到这里,glibc2.23的fopen源码就分析完毕了。我们由一张思维导图来总结一下函数的执行流。