C++ 學習 ---cstdio 的原始碼學習分析 03- 檔案重新命名函式 rename

語言: CN / TW / HK

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 }

複製程式碼