写点什么

C++ 学习 ---cstdio 的源码学习分析 03- 文件重命名函数 rename

作者:桑榆
  • 2022 年 9 月 18 日
    广东
  • 本文字数:3735 字

    阅读完需:约 12 分钟

cstdio 中的文件操作函数

stdio.h 中定义了文件删除函数 remove,文件重命名函数 rename,打开临时文件函数 tmpfile,生成临时文件名函数 tmpnam。接下来我们一起来分析一下 rename 对应的源码实现。

文件重命名函数 rename

使用新的文件名替换旧的文件名

int rename ( const char * oldname, const char * newname );
复制代码

在 glibc/libio/stdio.h 中与 rename 相关的函数增加了两个 renameat(替换时增加了 FD 的关联),renameat2(替换时增加 FD 的关联,flags 信息,表示三种不同的模式:RENAME_NOREPLACE,RENAME_EXCHANGE,RENAME_WHITEOUT)

153 /* Rename file OLD to NEW.  */154 extern int rename (const char *__old, const char *__new) __THROW;155 156 #ifdef __USE_ATFILE157 /* Rename file OLD relative to OLDFD to NEW relative to NEWFD.  */158 extern int renameat (int __oldfd, const char *__old, int __newfd,159              const char *__new) __THROW;160 #endif161 162 #ifdef __USE_GNU163 /* Flags for renameat2.  */164 # define RENAME_NOREPLACE (1 << 0)165 # define RENAME_EXCHANGE (1 << 1)166 # define RENAME_WHITEOUT (1 << 2)167 168 /* Rename file OLD relative to OLDFD to NEW relative to NEWFD, with169    additional flags.  */170 extern int renameat2 (int __oldfd, const char *__old, int __newfd,   171               const char *__new, unsigned int __flags) __THROW;172 #endif
复制代码

实现方式---unix 方式

代码参考:/glibc/sysdeps/unix/sysv/linux/rename.c

实际上就是通过调用 INLINE_SYSCALL_CALL 实现的

 24 /* Rename the file OLD to NEW.  */ 25 int  26 rename (const char *old, const char *new) 27 { 28 #if defined (__NR_rename) 29   return INLINE_SYSCALL_CALL (rename, old, new); 30 #elif defined (__NR_renameat) 31   return INLINE_SYSCALL_CALL (renameat, AT_FDCWD, old, AT_FDCWD, new); 32 #else 33   return INLINE_SYSCALL_CALL (renameat2, AT_FDCWD, old, AT_FDCWD, new, 0); 34 #endif 35 }
复制代码

INLINE_SYSCALL_CALL 的实现,继续调用__INLINE_SYSCALL_DISP,注意这里实际上是将__INLINE_SYSCALL 和后面的数据做了连接操作("##"在宏中做字符串连接)

//glibc/sysdeps/unix/sysdep.h103 /* Issue a syscall defined by syscall number plus any other argument104    required.  Any error will be handled using arch defined macros and errno105    will be set accordingly.106    It is similar to INLINE_SYSCALL macro, but without the need to pass the107    expected argument number as second parameter.  */108 #define INLINE_SYSCALL_CALL(...) \109   __INLINE_SYSCALL_DISP (__INLINE_SYSCALL, __VA_ARGS__)
100 #define __INLINE_SYSCALL_DISP(b,...) \101 __SYSCALL_CONCAT (b,__INLINE_SYSCALL_NARGS(__VA_ARGS__))(__VA_ARGS__)
27 #define __SYSCALL_CONCAT_X(a,b) a##b 28 #define __SYSCALL_CONCAT(a,b) __SYSCALL_CONCAT_X (a, b) 31 #define __INTERNAL_SYSCALL0(name) \ 32 INTERNAL_SYSCALL (name, 0) 33 #define __INTERNAL_SYSCALL1(name, a1) \ 34 INTERNAL_SYSCALL (name, 1, a1) 35 #define __INTERNAL_SYSCALL2(name, a1, a2) \ 36 INTERNAL_SYSCALL (name, 2, a1, a2) 37 #define __INTERNAL_SYSCALL3(name, a1, a2, a3) \ 38 INTERNAL_SYSCALL (name, 3, a1, a2, a3) 39 #define __INTERNAL_SYSCALL4(name, a1, a2, a3, a4) \ 40 INTERNAL_SYSCALL (name, 4, a1, a2, a3, a4) 41 #define __INTERNAL_SYSCALL5(name, a1, a2, a3, a4, a5) \ 42 INTERNAL_SYSCALL (name, 5, a1, a2, a3, a4, a5) 43 #define __INTERNAL_SYSCALL6(name, a1, a2, a3, a4, a5, a6) \ 44 INTERNAL_SYSCALL (name, 6, a1, a2, a3, a4, a5, a6) 45 #define __INTERNAL_SYSCALL7(name, a1, a2, a3, a4, a5, a6, a7) \ 46 INTERNAL_SYSCALL (name, 7, a1, a2, a3, a4, a5, a6, a7) 47 48 #define __INTERNAL_SYSCALL_NARGS_X(a,b,c,d,e,f,g,h,n,...) n 49 #define __INTERNAL_SYSCALL_NARGS(...) \ 50 __INTERNAL_SYSCALL_NARGS_X (__VA_ARGS__,7,6,5,4,3,2,1,0,)复制代码
复制代码

这里有必要分析一下__SYSCALL_CONCAT (b,__INLINE_SYSCALL_NARGS(__VA_ARGS__))(__VA_ARGS__)__INLINE_SYSCALL_NARGS(__VA_ARGS__)的作用,

可以看到,它在传入参数的后面添加了 7-0 共 8 个参数传递给__INTERNAL_SYSCALL_NARGS_X,该宏按顺序筛选出第 9 个数 n,即如果__VA_ARGS__只有一个参数,n=0,两个参数 n=1,

所以INLINE_SYSCALL_CALL (rename, old, new)三个参数,n=2,被宏展开为__INLINE_SYSCALL2(rename,old,new),再按照宏展开为INTERNAL_SYSCALL (rename, 2, old, new)

对应架构的实现如下:glibc/sysdeps/unix/sysv/linux/x86_64/sysdep.h

其中 number 为连接出的_NR_rename,这也说明了最开始判断#if defined (__NR_rename)的原因,

arg1,arg2 分别为输入的 old,new,调用汇编进行执行。

29 /* For Linux we can use the system call table in the header file 30 /usr/include/asm/unistd.h 31 of the kernel. But these symbols do not follow the SYS_* syntax 32 so we have to redefine the `SYS_ify' macro here. */ 33 #undef SYS_ify 34 #define SYS_ify(syscall_name) __NR_##syscall_name 
233 #undef INTERNAL_SYSCALL 234 #define INTERNAL_SYSCALL(name, nr, args...) \ 235 internal_syscall##nr (SYS_ify (name), args)

267 #undef internal_syscall2268 #define internal_syscall2(number, arg1, arg2) \269 ({ \270 unsigned long int resultvar; \271 TYPEFY (arg2, __arg2) = ARGIFY (arg2); \272 TYPEFY (arg1, __arg1) = ARGIFY (arg1); \273 register TYPEFY (arg2, _a2) asm ("rsi") = __arg2; \274 register TYPEFY (arg1, _a1) asm ("rdi") = __arg1; \275 asm volatile ( \276 "syscall\n\t" \277 : "=a" (resultvar) \278 : "0" (number), "r" (_a1), "r" (_a2) \279 : "memory", REGISTERS_CLOBBERED_BY_SYSCALL); \280 (long int) resultvar; \281 })
复制代码

renameat 和 renameat2 的调用与之类似,这里就不做过多说明了,需要注意的是 renameat 调用中使用的 flags 是 0(默认值)。

实现方式---mach 内核架构实现

glibc/sysdeps/mach/hurd/rename.c,实际上是调用__dir_rename 实现的,不做更深入解读了

 21 /* Rename the file OLD to NEW.  */ 22 int 23 rename (const char *old, const char *new) 24 { 25   error_t err; 26   file_t olddir, newdir; 27   const char *oldname, *newname; 28  29   olddir = __directory_name_split (old, (char **) &oldname); 30   if (olddir == MACH_PORT_NULL) 31     return -1; 32   newdir = __directory_name_split (new, (char **) &newname); 33   if (newdir == MACH_PORT_NULL) 34     { 35        __mach_port_deallocate (__mach_task_self (), olddir); 36       return -1; 37     } 38  39   err = __dir_rename (olddir, oldname, newdir, newname, 0); 40   __mach_port_deallocate (__mach_task_self (), olddir); 41   __mach_port_deallocate (__mach_task_self (), newdir); 42   if (err) 43     return __hurd_fail (err); 44   return 0; 45 }
复制代码

实现方式---posix 实现

glibc/sysdeps/posix/rename.c

逻辑也比较好理解,使用__link 完成对应的 rename 操作,然后使用__unlink 断开 oldname,大部分代码是针对出现异常时,即返回值小于 0 的异常处理,如前一处条件中考虑到了 EEXIST(文件存在)的情况,那就需要删除新文件,重新尝试__link,删除 old 文件时也是,出现异常,同时也要将新文件删除,保证最后失败不会带来额外的影响。具体__link 和__unlink 的调用基本也都是基于 syscall 实现的,就不展开细说了。

 22 /* Rename the file OLD to NEW.  */ 23 int 24 rename (const char *old, const char *new) 25 { 26   int save = errno; 27   if (__link (old, new) < 0) 28     { 29       if (errno == EEXIST) 30     { 31       __set_errno (save); 32       /* Race condition, required for 1003.1 conformance.  */ 33       if (__unlink (new) < 0 34           || __link (old, new) < 0) 35         return -1; 36     } 37       else 38     return -1; 39     } 40   if (__unlink (old) < 0) 41     { 42       save = errno; 43       if (__unlink (new) == 0) 44     __set_errno (save); 45       return -1; 46     } 47   return 0; 48 }
复制代码


发布于: 2022 年 09 月 18 日阅读数: 45
用户头像

桑榆

关注

北海虽赊,扶摇可接;东隅已逝,桑榆非晚! 2020.02.29 加入

Android手机厂商-相机软件系统工程师 爬山/徒步/Coding

评论

发布
暂无评论
C++学习---cstdio的源码学习分析03-文件重命名函数rename_c++_桑榆_InfoQ写作社区