cstdio 中的文件操作函数
stdio.h 中定义了文件删除函数 remove,文件重命名函数 rename,创建临时文件函数 tmpfile,生成临时文件名函数 tmpnam。接下来我们一起来分析一下 tmpfile 对应的源码实现。
创建临时文件函数 tmpfile
GLibc 中还定义了 tmpfile64,针对新的 FILE 结构定义,本文中以 tmpfile 函数进行分析。
183 /* Create a temporary file and open it read/write.184 185 This function is a possible cancellation point and therefore not186 marked with __THROW. */187 #ifndef __USE_FILE_OFFSET64188 extern FILE *tmpfile (void)189 __attribute_malloc__ __attr_dealloc_fclose __wur;190 #else191 # ifdef __REDIRECT192 extern FILE *__REDIRECT (tmpfile, (void), tmpfile64)193 __attribute_malloc__ __attr_dealloc_fclose __wur;194 # else195 # define tmpfile tmpfile64 196 # endif197 #endif
复制代码
实现方式
源码如下,基本流程是通过 tmpnam 函数生成一个文件名,然后以"w+b"(二进制读写)打开,返回,下面进行具体流程分析:
//glibc/stdio-common/tmpfile.c 30 /* This returns a new stream opened on a temporary file (generated 31 by tmpnam). The file is opened with mode "w+b" (binary read/write). 32 If we couldn't generate a unique filename or the file couldn't 33 be opened, NULL is returned. */ 34 FILE * 35 tmpfile (void) 36 { 37 int fd; 38 FILE *f; 39 int flags = 0; 40 #ifdef FLAGS 41 flags = FLAGS; 42 #endif 43 44 /* First try a system specific method. */ 45 fd = __gen_tempfd (flags); 46 47 if (fd < 0) 48 { 49 char buf[FILENAME_MAX]; 50 51 if (__path_search (buf, sizeof buf, NULL, "tmpf", 0)) 52 return NULL; 53 54 fd = __gen_tempname (buf, 0, flags, __GT_FILE); 55 if (fd < 0) 56 return NULL; 57 58 /* Note that this relies on the Unix semantics that 59 a file is not really removed until it is closed. */ 60 (void) __unlink (buf); 61 } 62 63 if ((f = __fdopen (fd, "w+b")) == NULL) 64 __close (fd); 65 66 return f; 67 }
复制代码
__gen_tempfd 获取临时 fd
首先通过系统特定方法__gen_tempfd 获取临时 fdFLAGS 定义,大文件 size 打开的标志,这里 tmpfile 和 tmpfile64 实际上是一种处理方式,所以__gen_tempfd 的入参是 0,除非是特定的架构中对 O_LARGEFILE 宏定义不同,这也提现了 Glibc 代码的兼容性,没有直接使用数字 0,给后面的平台留足了兼容空间,同时的,代码也将会变得比较复杂难懂。
//glibc/bits/fcntl.h 52 /* All opens support large file sizes, so there is no flag bit for this. */ 53 #ifdef __USE_LARGEFILE64 54 # define O_LARGEFILE 0 55 #endif //glibc/stdio-common/tmpfile64.c 21 /* If there is no O_LARGEFILE, then the plain tmpfile definition 22 does the job and it gets tmpfile64 as an alias. */ 23 24 #if defined O_LARGEFILE && O_LARGEFILE != 0 25 # define FLAGS O_LARGEFILE 26 # define tmpfile tmpfile64 27 # include <tmpfile.c> 28 #endif
复制代码
__gen_tempfd 的逻辑
这里我们就不细节展开__open 的流程了,后续有专门的文章进行分析。关注打开的文件节点和 flags 信息。
先尝试打开 P_tmpdir(默认是/tmp 路径),如果失败返回 fd 小于 0,且错误码为 ENOENT(/* No such file or directory */),而且之前打开的 P_tmpdir 不是"/tmp",那就尝试打开"/tmp"路径,这也是为了兼容其它平台可以提前定义 P_tmpdir 的情况。
flags 信息为:读写+临时文件+如果文件存在返回失败
文件控制信息为:被当前用户读或写
//glibc/libio/stdio.h118 #if defined __USE_MISC || defined __USE_XOPEN119 /* Default path prefix for `tempnam' and `tmpnam'. */120 # define P_tmpdir "/tmp"121 #endif
//glibc/sysdeps/unix/sysv/linux/gentempfd.c 24 int 25 __gen_tempfd (int flags) 26 { 27 int fd = __open (P_tmpdir, O_RDWR | O_TMPFILE | O_EXCL | flags, 28 S_IRUSR | S_IWUSR); 29 if (fd < 0 && errno == ENOENT && strcmp (P_tmpdir, "/tmp") != 0) 30 fd = __open ("/tmp", O_RDWR | O_TMPFILE | O_EXCL | flags, 31 S_IRUSR | S_IWUSR); 32 33 return fd; 34 }
复制代码
__open 的函数原型第一次参数是打开的文件名,第二个参数是 open flag,当 O_CREAT 和 O_TMPFILE 在 OFLAG 中时,增加第三个参数表示文件的保护,即由谁进行读写。
//glibc/sysdeps/unix/sysv/linux/open.c 28 /* Open FILE with access OFLAG. If O_CREAT or O_TMPFILE is in OFLAG, 29 a third argument is the file protection. */ 30 int 31 __libc_open (const char *file, int oflag, ...) 32 { 33 int mode = 0; 34 35 if (__OPEN_NEEDS_MODE (oflag)) 36 { 37 va_list arg; 38 va_start (arg, oflag); 39 mode = va_arg (arg, int); 40 va_end (arg); 41 } 42 43 return SYSCALL_CANCEL (openat, AT_FDCWD, file, oflag, mode); 44 }
复制代码
__path_search 生成临时文件路径
__path_search (buf, sizeof buf, NULL, "tmpf", 0)根据函数原型的解释,在 dir(默认可以选择/tmp)中查找可用的目录,然后生成文件路径 dir/file,拷贝到 buf 中,中间的逻辑其实就是为了生成字符串sprintf (tmpl, "%.*s/%.*sXXXXXX", (int) dlen, dir, (int) plen, pfx);其中 dir 可以从几个路径获取,有优先级和控制考虑,pfx 优先使用传入的,否则判空之后使用'file'
//glibc/sysdeps/posix/tempname.c105 /* Path search algorithm, for tmpnam, tmpfile, etc. If DIR is106 non-null and exists, uses it; otherwise uses the first of $TMPDIR,107 P_tmpdir, /tmp that exists. Copies into TMPL a template suitable108 for use with mk[s]temp. Will fail (-1) if DIR is non-null and109 doesn't exist, none of the searched dirs exists, or there's not110 enough space in TMPL. */111 int112 __path_search (char *tmpl, size_t tmpl_len, const char *dir, const char *pfx,113 int try_tmpdir)114 {115 const char *d;116 size_t dlen, plen;117 118 if (!pfx || !pfx[0]) 119 {120 pfx = "file";121 plen = 4;122 }123 else124 {125 plen = strlen (pfx);126 if (plen > 5)127 plen = 5;128 }129 130 if (try_tmpdir)131 {132 d = __secure_getenv ("TMPDIR");133 if (d != NULL && direxists (d))134 dir = d;135 else if (dir != NULL && direxists (dir))136 /* nothing */ ;137 else138 dir = NULL;139 }140 if (dir == NULL)141 {142 if (direxists (P_tmpdir))143 dir = P_tmpdir;144 else if (strcmp (P_tmpdir, "/tmp") != 0 && direxists ("/tmp"))145 dir = "/tmp";146 else147 {148 __set_errno (ENOENT);149 return -1;150 }151 }152 153 dlen = strlen (dir);154 while (dlen > 1 && dir[dlen - 1] == '/')155 dlen--; /* remove trailing slashes */156 157 /* check we have room for "${dir}/${pfx}XXXXXX\0" */158 if (tmpl_len < dlen + 1 + plen + 6 + 1)159 {160 __set_errno (EINVAL);161 return -1;162 }163 164 sprintf (tmpl, "%.*s/%.*sXXXXXX", (int) dlen, dir, (int) plen, pfx);165 return 0; 166 }
复制代码
__gen_tempname 生成临时文件 fd
fd = __gen_tempname (buf, 0, flags, __GT_FILE);__GT_FILE 表示新建文件,注意上面我们获取到的临时文件路径为"/tmp/tmpfXXXXXX"(关键就在于后面的 6 个 X)
gen_tempname_len 函数转发时,增加了一个参数 6,这个参数在其函数定义中有解释,表示临时路径中至少有 X_SUFFIX_LEN 个"X"s,这个是方面后面生成临时数字的,因为临时文件可能不止一个,它们按照/tmp/tmpfxxxxxx,/tmp/tmpfxxxxxx...排序,x 是多个数字的组合
后面调用 try_tempname_len 获取,传入对应创建的函数指针 try_file,try_dir,try_nocreate<这里我们就不深入分析是如何生成文件名的了,
看到最后的 try_file 函数,实际上它与我们前面看到的__gen_tempfd 一样,也是调用__open 打开文件的。
141 /* The __kind argument to __gen_tempname may be one of: */142 # define __GT_FILE 0 /* create a file */143 # define __GT_DIR 1 /* create a directory */144 # define __GT_NOCREATE 2 /* just find a name not currently in use */
//glibc/sysdeps/posix/tempname.c332 int333 __gen_tempname (char *tmpl, int suffixlen, int flags, int kind)334 {335 return gen_tempname_len (tmpl, suffixlen, flags, kind, 6); 336 }
203 /* Generate a temporary file name based on TMPL. TMPL must match the204 rules for mk[s]temp (i.e., end in at least X_SUFFIX_LEN "X"s,205 possibly with a suffix).206 The name constructed does not exist at the time of the call to 207 this function. TMPL is overwritten with the result.208 209 KIND may be one of:210 __GT_NOCREATE: simply verify that the name does not exist211 at the time of the call.212 __GT_FILE: create the file using open(O_CREAT|O_EXCL)213 and return a read-write fd. The file is mode 0600.214 __GT_DIR: create a directory, which will be mode 0700.215 216 We use a clever algorithm to get hard-to-predict names. */217 #ifdef _LIBC218 static219 #endif220 int221 gen_tempname_len (char *tmpl, int suffixlen, int flags, int kind,222 size_t x_suffix_len)223 {224 static int (*const tryfunc[]) (char *, void *) =225 {226 [__GT_FILE] = try_file,227 [__GT_DIR] = try_dir,228 [__GT_NOCREATE] = try_nocreate229 };230 return try_tempname_len (tmpl, suffixlen, &flags, tryfunc[kind],231 x_suffix_len);232 }
174 static int175 try_file (char *tmpl, void *flags)176 {177 int *openflags = flags;178 return __open (tmpl,179 (*openflags & ~O_ACCMODE)180 | O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);181 }
复制代码
__unlink 删除上面生成的临时文件
注意这里是比较重要的点,为什么刚生成 fd 之后又要删除这个临时文件呢,注释有说明,依赖于 unix 的语义,一个文件只有在被关闭的时候才会真正被删除,所以这也是临时文件的含义,只有调用者主动关闭 tmpfile 之后,它就会被直接删除,而不是像普通文件那样保存下来。
__fdopen 打开上面的临时文件 fd,返回 file 对象
具体细节我们就不做具体分析了,通过对内部_IO_new_fdopen 函数的操作打开对应 fd 的文件,返回文件流对象。
_IO_FILE_plus 是对 FILE file 和 const struct _IO_jump_t *vtable 的封装,后者是为了 C++streambuf 做的兼容封装,保存了一些函数跳转表
#define __fdopen _IO_fdopen
//glibc/libio/iofdopen.c163 libc_hidden_ver (_IO_new_fdopen, _IO_fdopen)
33 FILE * 34 _IO_new_fdopen (int fd, const char *mode) 35 { 36 int read_write; 37 struct locked_FILE 38 { 39 struct _IO_FILE_plus fp; 40 #ifdef _IO_MTSAFE_IO 41 _IO_lock_t lock; 42 #endif 43 struct _IO_wide_data wd; 44 } *new_f; ...151 /* For append mode, set the file offset to the end of the file if we added152 O_APPEND to the file descriptor flags. Don't update the offset cache153 though, since the file handle is not active. */154 if (do_seek && ((read_write & (_IO_IS_APPENDING | _IO_NO_READS))155 == (_IO_IS_APPENDING | _IO_NO_READS)))156 {157 off64_t new_pos = _IO_SYSSEEK (&new_f->fp.file, 0, _IO_seek_end);158 if (new_pos == _IO_pos_BAD && errno != ESPIPE)159 return NULL;160 }161 return &new_f->fp.file;162 }
//glibc/libio/libioP.h319 /* We always allocate an extra word following an _IO_FILE.320 This contains a pointer to the function jump table used.321 This is for compatibility with C++ streambuf; the word can322 be used to smash to a pointer to a virtual function table. */323 324 struct _IO_FILE_plus325 {326 FILE file;327 const struct _IO_jump_t *vtable;328 };
293 struct _IO_jump_t294 {295 JUMP_FIELD(size_t, __dummy);296 JUMP_FIELD(size_t, __dummy2);297 JUMP_FIELD(_IO_finish_t, __finish);298 JUMP_FIELD(_IO_overflow_t, __overflow);299 JUMP_FIELD(_IO_underflow_t, __underflow);300 JUMP_FIELD(_IO_underflow_t, __uflow);301 JUMP_FIELD(_IO_pbackfail_t, __pbackfail);302 /* showmany */303 JUMP_FIELD(_IO_xsputn_t, __xsputn);304 JUMP_FIELD(_IO_xsgetn_t, __xsgetn);305 JUMP_FIELD(_IO_seekoff_t, __seekoff);306 JUMP_FIELD(_IO_seekpos_t, __seekpos);307 JUMP_FIELD(_IO_setbuf_t, __setbuf);308 JUMP_FIELD(_IO_sync_t, __sync);309 JUMP_FIELD(_IO_doallocate_t, __doallocate);310 JUMP_FIELD(_IO_read_t, __read);311 JUMP_FIELD(_IO_write_t, __write);312 JUMP_FIELD(_IO_seek_t, __seek);313 JUMP_FIELD(_IO_close_t, __close);314 JUMP_FIELD(_IO_stat_t, __stat);315 JUMP_FIELD(_IO_showmanyc_t, __showmanyc);316 JUMP_FIELD(_IO_imbue_t, __imbue);317 };
复制代码
评论