catalog
0. 引言 1. Linux平台上涉及的File IO操作 2. Windows平台上涉及的File IO操作
0. 引言
本文試圖討論在windows、linux操作系統上基於C庫進行文件IO操作時,可能遇到的錯誤,及其解決方法,主機安全攻防產品除了需要將安全攻防上的領域知識固化到程序實現上之外,還極度依賴關聯系統本身、編程語言庫的特性,原則上,並不是所有的安全需求都能100%地落實到程序設計中,這需要我們對操作系統、編程語言本身具有較深的理解
Relevant Link:
http://www.cnblogs.com/LittleHann/p/3905608.html http://www.cnblogs.com/LittleHann/p/4305892.html //搜索:0x3: 權限檢查
1. Linux平台上涉及的File IO操作
0x1: 打開目錄: opendir()
全磁盤遍歷、文件IO操作的第一步是打開文件目錄本身,在Linux中,文件和目錄都統一用inode進行抽象
#include <sys/types.h> #include <dirent.h> DIR * opendir(const char * name); //返回值:成功則返回DIR* 型態的目錄流, 打開失敗則返回NULL
錯誤代碼
1. EACCESS: 權限不足 2. EMFILE: 已達到進程可同時打開的文件數上限 3. ENFILE: 已達到系統可同時打開的文件數上限 4. ENOTDIR: 參數name非真正的目錄 5. ENOENT: 參數name指定的目錄不存在,或是參數name為一空字符串 6. ENOMEM: 核心內存不足
其中EACCESS(權限不足)是最經常遇到的問題
1. Each directory in the path name preceding the directory to be opened 1) 調用opendir,如果傳入路徑參數的各個分量中,有一個分量沒有X權限,則返回Permission denied(EACCES)錯誤 2) 沒有X權限,ll命令雖然可以列出該目錄下的文件及目錄,但是同樣因為可執行(x)權限無法查看屬性 3) cd命令需要可執行(x)權限,因此無法通過cd命令切換到該目錄下,子目錄也是不可切換的,即便對子目錄有完整權限 4) 同樣對於cat命令,由於缺少可執行(x)權限,該目錄下的文件也是不可讀的 5) 對於stat系統調用和cat命令是一樣的,如果缺少x權限,則stat將執行失敗 6) 沒有x權限,目錄下的文件也不能cp 7) 父目錄沒有x權限,即使該目錄下的文件可讀可執行,也無法執行該目錄下的文件 //需要明白的是,Linux的目錄尋址定位是逐個路徑分量逐段進行的,如果父目錄因為沒有x權限受阻了,則即使目錄下的子文件權限足夠,也無法正常操作 2. The directory to be opened 如果傳入的目錄路徑沒有R權限, 則調用opendir返回Permission denied(EACCES)錯誤,沒有R權限導致無法讀取目錄inode的元信息
我們梳理一下概念
1. 對於文件權限: 可讀權限(r)是可執行(x)的基礎,因為執行的前提是讀取二進制數據 2. 對於目錄權限: 可執行(x)權限是關鍵,沒有可執行權限意味着所有命令都不能在該目錄及子目錄下執行,該目錄及子目錄下的文件也不能被執行。這意味着我們常用的命令cd、ls、mkdir、cp、mv、rm等等在該目錄下全部失效
Relevant Link:
http://c.biancheng.net/cpp/html/319.html http://pubs.opengroup.org/onlinepubs/009695399/functions/opendir.html http://lxr.free-electrons.com/source/include/uapi/asm-generic/errno-base.h#L16 http://axisray.me/2015/01/04/linux-dir-and-file-permission/ http://os.51cto.com/art/201003/186949.htm
0x2: 讀取目錄: readdir()
#include <sys/types.h> #include <dirent.h> struct dirent * readdir(DIR * dir); /* 返回值 1. 成功則返回下個目錄進入點 2. 有錯誤發生或讀取到目錄文件尾則返回NULL */
No authorization is required. Authorization is verified during opendir()
錯誤代碼
1. EOVERFLOW One of the values in the structure to be returned cannot be represented correctly. The readdir() function may fail if: 2. EBADF The dirp argument does not refer to an open directory stream. 3. ENOENT The current position of the directory stream is invalid.
Relevant Link:
http://c.biancheng.net/cpp/html/320.html http://pubs.opengroup.org/onlinepubs/7908799/xsh/readdir.html https://www-01.ibm.com/support/knowledgecenter/ssw_ibm_i_61/apis/readdir.htm
0x3: 獲取文件屬性: stat()
在進行文件IO操作的時候,常常遇到目標文件設置了ACL權限而導致操作失敗的情況,這就要求我們在進行文件IO操作之前對文件的屬性進行檢測,並設置相應的權限以支持讀寫
#include <sys/stat.h> #include <unistd.h> int stat(const char * file_name, struct stat *buf);
返回值: 執行成功則返回0,失敗返回-1,錯誤代碼存於errno
錯誤代碼
1. ENOENT: 參數file_name指定的文件不存在 2. ENOTDIR: 路徑中的目錄存在但卻非真正的目錄 3. ELOOP: 欲打開的文件有過多符號連接問題,上限為16符號連接 4. EFAULT: 參數buf為無效指針,指向無法存在的內存空間 5. EACCESS: 存取文件時被拒絕 6. ENOMEM: 核心內存不足 7. ENAMETOOLONG: 參數file_name的路徑名稱太長
如果需要修改文件屬性,可以通過如下方法
1. chown設置文件屬性 /* #include <sys/types.h> #include <unistd.h> int chown(const char *path,uid_t owner, gid_t group); int fchown(int fd, uid_t owner, gid_t group)’ int lchown(const char *path, uid_t owner,gid_t group); 文件的所有者只能改變文件的組id為其所屬組中的一個,超級用戶才能修改文件的所有者id,並且超級用戶可以任意修改文件的用戶組id */ 2. truncate改變文件大小 /* #include <sys/types.h> #include <unistd.h> int truncate(const char *path, off_t length); int ftruncate(int fd, off_t length); 將指定文件大小改為參數length指定的大小,如果原來的文件比參數length大,則超過的部分會被刪除;如果原來的文件大小比參數length小,則文件將被擴展,擴展部分用0填充 */ 3. utime改變文件的st_mtime域和st_ctime域,即存取時間和修改時間 /* #include <sys/types.h> #include <utime.h> int utime(const char *filename,struct utimbuf *buf); #include <sys/time.h> int utime(char *filename,struct timeval *tvp); 如果buf是一個空指針,則存取時間和修改時間都為當前時間 */
Relevant Link:
http://blog.csdn.net/dlutbrucezhang/article/details/8627387 http://c.biancheng.net/cpp/html/326.html http://www.cnblogs.com/LittleHann/p/3905608.html //搜索:3. int stat(const char * file_name, struct stat *buf);
0x4: 打開文件: open()
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char * pathname, int flags); int open(const char * pathname, int flags, mode_t mode); //參數pathname指向欲打開的文件路徑字符串
錯誤代碼
1. EEXIST: 參數pathname所指的文件已存在,卻使用了O_CREAT和O_EXCL旗標 2. EACCESS: 參數pathname所指的文件不符合所要求測試的權限 3. EROFS: 欲測試寫入權限的文件存在於只讀文件系統內 4. EFAULT: 參數pathname指針超出可存取內存空間 5. EINVAL: 參數mode不正確 6. ENAMETOOLONG: 參數pathname太長 7. ENOTDIR: 參數pathname不是目錄 8. ENOMEM: 核心內存不足 9. ELOOP: 參數pathname有過多符號連接問題 10. EIO: I/O存取錯誤
受到POSIX ACL規則的影響,open函數經常返回的是EACCESS權限錯誤
Relevant Link:
http://c.biancheng.net/cpp/html/238.html http://www.cnblogs.com/LittleHann/p/3905608.html //搜索:1. int open(const char *pathname, int flags);
0x5: 向文件寫入內容: write()
#include <unistd.h> ssize_t write (int fd, const void * buf, size_t count); //返回值: 如果順利write()會返回實際寫入的字節數. 當有錯誤發生時則返回-1, 錯誤代碼存入errno中
錯誤代碼
1. EINTR: 此調用被信號所中斷. 2. EAGAIN: 當使用不可阻斷I/O 時(O_NONBLOCK),若無數據可讀取則返回此值 3. EADF: 參數fd非有效的文件描述詞,或該文件已關閉
Relevant Link:
http://c.biancheng.net/cpp/html/241.html
0x6: Linux文件占用狀態下: 打開文件
文件的操作的獨占性是通過文件系統flag指定,而操作系統提供底層的強制保證的,但是Linux並不像windows那樣提供強制的讀寫互斥保護,即
1. 兩個進程可以同時以寫模式打開一個文件,同時向其中寫入數據,這可能導致不一致情況的發生 2. 兩個進程可以分別以讀模式、寫模式打開一個文件,同時進行讀/寫的操作,這可能導致錯誤的數據讀取,即臟讀、幻讀
因為對於Linux VFS文件系統來說,打開一個文件,open系統調用僅僅是向操作系統發起一個inode操作請求,每個進程都持有自己獨有的fd句柄,並通過引用共享內核內存中的inode向底層的存儲數據的超級塊發起數據操作請求(inode的引用計數加1),VFS本身並沒有處理互斥的問題
0x7: Linux文件占用狀態下: 刪除文件
和打開文件一樣,Linux VFS並沒有對文件的刪除采用獨占保護。Linux是通過link的數量來控制文件刪除的,只有當一個文件不存在任何link的時候,這個文件才會被刪除。一般來說,每個文件都有2個link計數器
1. i_count: i_count的意義是當前文件使用者(或被調用)的數量 2. i_nlink: i_nlink的意義是介質連接的數量(硬鏈接的數量) //可以理解為i_count是內存引用計數器,i_nlink是磁盤的引用計數器,當一個文件被某一個進程引用時,對應i_count數就會增加;當創建文件的硬鏈接的時候,對應i_nlink數就會增加
對於刪除命令rm而言,實際就是減少磁盤引用計數i_nlink,如果一個文件正在被某個進程調用,而用戶卻執行rm操作把文件刪除了,因為rm操作只是將文件的i_nlink減少了,如果沒其它的鏈接i_nlink就為0了;但由於該文件依然被進程引用,因此,此時文件對應的i_count並不為0,所以即使執行rm操作,但系統並沒有真正刪除這個文件,當只有i_nlink及i_count都為0的時候,這個文件才會真正被刪除。也就是說,還需要解除該進程的對該文件的調用才行
從原則上來說,Linux並不提供文件獨占的保護,也就是從根本上說,即使一個文件正在被進程使用,那么其他進程也可以直接無條件地刪除這個文件,雖然真實的刪除要等到原始進程釋放對該文件的持有時才進行
0x7: Linux中的文件鎖
從應用程序的角度來說,需要由程序員自己保證文件的獨占使用,即在打開之前,先檢測當前文件是否被其他進程占用,但是這樣增加了開發的成本,而且也不具有兼容性,一個更優雅的解決方案是利用Linux操作系統自身的文件鎖機制,從而更好地解決多個進程讀取同一個文件的互斥問題
早期的UNIX系統只支持對整個文件進行加鎖,因此無法運行數據庫之類的程序,因為此類程序需要實現記錄級的加鎖。在System V Release 3中,通過fcntl提供了記錄級的加鎖,此后發展成為 POSIX標准的一部分
Linux 支持的文件鎖技術主要包括
1. 勸告鎖(advisory lock) 2. 強制鎖(mandatory lock) /* 在Linux中,不論進程是在使用勸告鎖還是強制鎖,它都可以同時使用共享鎖(S鎖 讀鎖)和排他鎖(X鎖 寫鎖) 1. 多個共享鎖之間不會相互干擾,多個進程在同一時刻可以對同一個文件加共享鎖 2. 如果一個進程對該文件加了排他鎖,那么其他進程則無權再對該文件加共享鎖或者排他鎖,直到該排他鎖被釋放 */ 3. 共享模式強制鎖(share-mode mandatory lock) 4. 租借鎖(lease)
對於同一個文件來說,它可以同時擁有很多讀者,但是在某一特定時刻,它只能擁有一個寫者,它們之間的兼容關系如表所示
是否滿足請求 | ||
---|---|---|
當前加上的鎖 | 共享鎖 | 排他鎖 |
無 | 是 | 是 |
共享鎖 | 是 | 否 |
排他鎖 | 否 | 否 |
1. 勸告鎖
勸告鎖是一種協同工作的鎖。對於這一種鎖來說,內核只提供加鎖以及檢測文件是否已經加鎖的手段,但是內核並不參與鎖的控制和協調。也就是說,如果有進程不遵守"游戲規則",不檢查目標文件是否已經由別的進程加了鎖就往其中寫入數據,那么內核是不會加以阻攔的。因此,勸告鎖並不能阻止進程對文件的訪問,而只能依靠各個進程在訪問文件之前檢查該文件是否已經被其他進程加鎖來實現並發控制
進程需要事先對鎖的狀態做一個約定,並根據鎖的當前狀態和相互關系來確定其他進程是否能對文件執行指定的操作。從這點上來說,勸告鎖的工作方式與使用信號量保護臨界區的方式非常類似。
勸告鎖可以對文件的任意一個部分進行加鎖,也可以對整個文件進行加鎖,甚至可以對文件將來增大的部分也進行加鎖。由於進程可以選擇對文件的某個部分進行加鎖,所以一個進程可以獲得關於某個文件不同部分的多個鎖
2. 強制鎖
與勸告鎖不同,強制鎖是一種內核強制采用的文件鎖,它是從System V Release 3開始引入的。每當有系統調用open()、read()以及write()發生的時候,內核都要檢查並確保這些系統調用不會違反在所訪問文件上加的強制鎖約束。也就是說,如果有進程不遵守游戲規則,硬要往加了鎖的文件中寫入內容,內核就會加以阻攔
1. 如果一個文件已經被加上了讀鎖或者共享鎖,那么其他進程再對這個文件進行寫操作就會被內核阻止 2. 如果一個文件已經被加上了寫鎖或者排他鎖,那么其他進程再對這個文件進行讀取或者寫操作就會被內核阻止
如果其他進程試圖訪問一個已經加有強制鎖的文件,進程行為取決於所執行的操作模式和文件鎖的類型,歸納如表所示
當前鎖類型 | 阻塞讀 | 阻塞寫 | 非阻塞讀 | 非阻塞寫 |
---|---|---|---|---|
讀鎖 | 正常讀取數據 | 阻塞 | 正常讀取數據 | EAGAIN |
寫鎖 | 阻塞 | 阻塞 | EAGAIN | EAGAIN |
需要注意的是,如果要訪問的文件的鎖類型與要執行的操作存在沖突,那么采用阻塞讀/寫操作的進程會被阻塞,而采用非阻塞讀/寫操作的進程則不會阻塞,而是立即返回EAGAIN
然而,在有些應用中並不適合使用強制鎖,所以索引節點結構中的i_flags字段中定義了一個標志位MS_MANDLOCK用於有選擇地允許或者不允許對一個文件使用強制鎖。在super_block結構中,也可以將s_flags這個標志為設置為1或者0,用以表示整個設備上的文件是否允許使用強制鎖
要想對一個文件采用強制鎖,必須按照以下步驟執行
1. 使用 -o mand 選項來掛載文件系統。這樣在執行 mount() 系統調用時,會傳入 MS_MANDLOCK 標記,從而將 super_block 結構中的 s_flags 設置為 1,用來表示在這個文件系統上可以采用強制鎖 * # mount -o mand /dev/sdb7 /mnt # mount | grep mnt /dev/sdb7 on /mnt type ext3 (rw,mand) */ 2. 修改要加強制鎖的文件的權限: 設置SGID位,並清除組可執行位。這種組合通常來說是毫無意義的,系統用來表示該文件被加了強制鎖。例如: # touch /mnt/testfile # ls -l /mnt/testfile -rw-r--r-- 1 root root 0 Jun 22 14:43 /mnt/testfile # chmod g+s /mnt/testfile # chmod g-x /mnt/testfile # ls -l /mnt/testfile -rw-r-Sr-- 1 root root 0 Jun 22 14:43 /mnt/testfile 3. 使用 fcntl() 系統調用對該文件進行加鎖或解鎖操作
3. 共享模式鎖
共享模式強制鎖可以用於某些私有網絡文件系統,如果某個文件被加上了共享模式強制鎖,那么其他進程打開該文件的時候不能與該文件的共享模式強制鎖所設置的訪問模式相沖突。但是由於可移植性不好,因此並不建議使用這種鎖
4. 租借鎖
采用強制鎖之后,如果一個進程對某個文件擁有寫鎖,只要它不釋放這個鎖,就會導致訪問該文件的其他進程全部被阻塞或不斷失敗重試;即使該進程只擁有讀鎖,也會造成后續更新該文件的進程的阻塞。為了解決這個問題,Linux 中采用了一種新型的租借鎖。
當進程嘗試打開一個被租借鎖保護的文件時,該進程會被阻塞,同時,在一定時間內擁有該文件租借鎖的進程會收到一個信號。收到信號之后,擁有該文件租借鎖的進程會首先更新文件,從而保證了文件內容的一致性,接着,該進程釋放這個租借鎖。如果擁有租借鎖的進程在一定的時間間隔內沒有完成工作,內核就會自動刪除這個租借鎖或者將該鎖進行降級,從而允許被阻塞的進程繼續工作
//系統默認的這段間隔時間是45秒鍾 int lease_break_time = 45;
這個參數可以通過修改"/proc/sys/fs/lease-break-time"進行調節,前提是"/proc/sys/fs/leases-enable" = 1
0x8: 鎖的使用方法示例
# cat -n mandlock.c #include <errno.h> #include <stdio.h> #include <fcntl.h> #include <sys/wait.h> #include <sys/stat.h> int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len) { struct flock lock; lock.l_type = type; /* F_RDLCK, F_WRLCK, F_UNLCK */ lock.l_start = offset; /* byte offset, relative to l_whence */ lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */ lock.l_len = len; /* #bytes (0 means to EOF) */ return( fcntl(fd, cmd, &lock) ); } #define read_lock(fd, offset, whence, len) lock_reg(fd, F_SETLK, F_RDLCK, offset, whence, len) #define write_lock(fd, offset, whence, len) lock_reg(fd, F_SETLK, F_WRLCK, offset, whence, len) #define err_sys(x) { perror(x); exit(1); } int main(int argc, char *argv[]) { int fd, val; pid_t pid; char buf[5]; struct stat statbuf; if (argc != 2) { fprintf(stderr, "usage: %s filename\n", argv[0]); exit(1); } if ((fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC )) < 0) err_sys("open error"); if (write(fd, "hello world", 11) != 11) err_sys("write error"); /* turn on set-group-ID and turn off group-execute */ if (fstat(fd, &statbuf) < 0) err_sys("fstat error"); if (fchmod(fd, (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0) err_sys("fchmod error"); sleep(2); if ((pid = fork()) < 0) { err_sys("fork error"); } else if (pid > 0) { /* parent */ /* write lock entire file */ if (write_lock(fd, 0, SEEK_SET, 0) < 0) err_sys("write_lock error"); sleep(20); /* wait for child to set lock and read data */ if (waitpid(pid, NULL, 0) < 0) err_sys("waitpid error"); } else { /* child */ sleep(10); /* wait for parent to set lock */ if ( (val = fcntl(fd, F_GETFL, 0)) < 0) err_sys("fcntl F_GETFL error"); val |= O_NONBLOCK; /* turn on O_NONBLOCK flag */ if (fcntl(fd, F_SETFL, val) < 0) err_sys("fcntl F_SETFL error"); /* first let's see what error we get if region is locked */ if (read_lock(fd, 0, SEEK_SET, 0) != -1) /* no wait */ err_sys("child: read_lock succeeded"); printf("read_lock of already-locked region returns %d: %s\n", errno, strerror(errno)); /* now try to read the mandatory locked file */ if (lseek(fd, 0, SEEK_SET) == -1) err_sys("lseek error"); if (read(fd, buf, 5) < 0) printf("read failed (mandatory locking works)\n"); else printf("read OK (no mandatory locking), buf = %5.5s\n", buf); } exit(0); } /* # mount | grep mnt /dev/sdb7 on /mnt type ext3 (rw,mand) /dev/sdb6 on /tmp/mnt type ext3 (rw) # ./mandlock /mnt/testfile read_lock of already-locked region returns 11: Resource temporarily unavailable read failed (mandatory locking works) # ./mandlock /tmp/mnt/testfile read_lock of already-locked region returns 11: Resource temporarily unavailable read OK (no mandatory locking), buf = hello */
Relevant Link:
http://blog.sina.com.cn/s/blog_4b3c1f950102uz74.html http://www.ibm.com/developerworks/cn/linux/l-cn-filelock/
2. Windows平台上涉及的File IO操作
因為C庫是跨操作系統平台的,對於Linux、Windows系統來說,C庫封裝了對文件的IO操作,但是在底層,C庫分別實現了Win32 API的封裝(windows)、系統調用的封裝(linux),在Linux平台上,C庫和系統調用幾乎是平行的,在Windows上,C庫和Win32 API差別較大,並且最終還是要通過Win32 API得以實現,因此,我們接下來全部以原生的Win32 API進行討論
0x1: 遍歷目錄: FindFirstFile、FindNextFile
#include <windows.h> #include <tchar.h> #include <stdio.h> #include <strsafe.h> #pragma comment(lib, "User32.lib") void DisplayErrorBox(LPTSTR lpszFunction); int _tmain(int argc, TCHAR *argv[]) { WIN32_FIND_DATA ffd; LARGE_INTEGER filesize; TCHAR szDir[MAX_PATH]; size_t length_of_arg; HANDLE hFind = INVALID_HANDLE_VALUE; DWORD dwError=0; // If the directory is not specified as a command-line argument, // print usage. if(argc != 2) { _tprintf(TEXT("\nUsage: %s <directory name>\n"), argv[0]); return (-1); } // Check that the input path plus 3 is not longer than MAX_PATH. // Three characters are for the "\*" plus NULL appended below. StringCchLength(argv[1], MAX_PATH, &length_of_arg); if (length_of_arg > (MAX_PATH - 3)) { _tprintf(TEXT("\nDirectory path is too long.\n")); return (-1); } _tprintf(TEXT("\nTarget directory is %s\n\n"), argv[1]); // Prepare string for use with FindFile functions. First, copy the // string to a buffer, then append '\*' to the directory name. StringCchCopy(szDir, MAX_PATH, argv[1]); StringCchCat(szDir, MAX_PATH, TEXT("\\*")); // Find the first file in the directory. hFind = FindFirstFile(szDir, &ffd); if (INVALID_HANDLE_VALUE == hFind) { DisplayErrorBox(TEXT("FindFirstFile")); return dwError; } // List all the files in the directory with some info about them. do { if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { _tprintf(TEXT(" %s <DIR>\n"), ffd.cFileName); } else { filesize.LowPart = ffd.nFileSizeLow; filesize.HighPart = ffd.nFileSizeHigh; _tprintf(TEXT(" %s %ld bytes\n"), ffd.cFileName, filesize.QuadPart); } } while (FindNextFile(hFind, &ffd) != 0); dwError = GetLastError(); if (dwError != ERROR_NO_MORE_FILES) { DisplayErrorBox(TEXT("FindFirstFile")); } FindClose(hFind); return dwError; } void DisplayErrorBox(LPTSTR lpszFunction) { // Retrieve the system error message for the last-error code LPVOID lpMsgBuf; LPVOID lpDisplayBuf; DWORD dw = GetLastError(); FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL ); // Display the error message and clean up lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, (lstrlen((LPCTSTR)lpMsgBuf)+lstrlen((LPCTSTR)lpszFunction)+40)*sizeof(TCHAR)); StringCchPrintf((LPTSTR)lpDisplayBuf, LocalSize(lpDisplayBuf) / sizeof(TCHAR), TEXT("%s failed with error %d: %s"), lpszFunction, dw, lpMsgBuf); MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK); LocalFree(lpMsgBuf); LocalFree(lpDisplayBuf); }
If the function fails or fails to locate files from the search string in the lpFileName parameter, the return value is INVALID_HANDLE_VALUE
If the function fails because no matching files can be found, the GetLastError function returns ERROR_FILE_NOT_FOUND.
Relevant Link:
https://msdn.microsoft.com/en-us/library/aa365200(VS.85).aspx http://www.cnblogs.com/lanxuezaipiao/p/3420025.html https://msdn.microsoft.com/en-us/library/aa364418(v=vs.85).aspx
0x2: 獲取、更改文件屬性
可以在對文件進行IO操作之前對文件的屬性進行檢測,並進行相對應的修改,例如關閉只讀屬性,使文件可寫
SetFileAttributes function
GetFileAttributes function
0x2: 打開文件: _open、_wopen C API
int _open( const char *filename, int oflag [, int pmode] ); int _wopen( const wchar_t *filename, int oflag [, int pmode] ); //Opens a file. These functions are deprecated because more-secure versions are available; see _sopen_s, _wsopen_s
對於windows系統來說,系統內核對文件獨占、互斥提供的底層支持,並通過文件IO API將能力提供給程序員,我們來看open API涉及的參數
1. filename: File name. 2. oflag: The kind of operations allowed. oflag is an integer expression formed from one or more of the following manifest constants or constant combinations, which are defined in <fcntl.h>. 1) _O_APPEND: Moves the file pointer to the end of the file before every write operation. 2) _O_BINARY: Opens the file in binary (untranslated) mode. (See fopen for a description of binary mode.) 3) _O_CREAT: Creates a file and opens it for writing. Has no effect if the file specified by filename exists. The pmode argument is required when _O_CREAT is specified. 4) _O_CREAT | _O_SHORT_LIVED: Creates a file as temporary and if possible does not flush to disk. The pmode argument is required when _O_CREAT is specified. 5) _O_CREAT | _O_TEMPORARY: Creates a file as temporary; the file is deleted when the last file descriptor is closed. The pmode argument is required when _O_CREAT is specified. 6) _O_CREAT | _O_EXCL: Returns an error value if the file specified by filename exists. Applies only when used with _O_CREAT. 7) _O_NOINHERIT: Prevents creation of a shared file descriptor. 8) _O_RANDOM: Specifies that caching is optimized for, but not restricted to, random access from disk. 9) _O_RDONLY: Opens a file for reading only. Cannot be specified with _O_RDWR or _O_WRONLY. 10) _O_RDWR: Opens file for both reading and writing. Cannot be specified with _O_RDONLY or _O_WRONLY. 11) _O_SEQUENTIAL: Specifies that caching is optimized for, but not restricted to, sequential access from disk. 12) _O_TEXT: Opens a file in text (translated) mode. (For more information, see Text and Binary Mode File I/O and fopen.) 13) _O_TRUNC: Opens a file and truncates it to zero length; the file must have write permission. Cannot be specified with _O_RDONLY. _O_TRUNC used with _O_CREAT opens an existing file or creates a file. 14) _O_WRONLY: Opens the file for writing only. Cannot be specified with _O_RDONLY or _O_RDWR. 15) _O_U16TEXT: Opens the file in Unicode UTF-16 mode. 16) _O_U8TEXT: Opens the file in Unicode UTF-8 mode. 17) _O_WTEXT: Opens the file in Unicode mode. 3. pmode: Permission mode. The pmode argument is required only when _O_CREAT is specified. If the file already exists, pmode is ignored, Otherwise, pmode specifies the file permission setting 1) _S_IREAD: Only reading permitted. 2) _S_IWRITE: Writing permitted. (In effect, permits reading and writing.) 3) _S_IREAD | _S_IWRITE: Reading and writing permitted.
代碼示例
// crt_open.c // compile with: /W3 /* This program uses _open to open a file * named CRT_OPEN.C for input and a file named CRT_OPEN.OUT * for output. The files are then closed. */ #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <io.h> #include <stdio.h> int main( void ) { int fh1, fh2; fh1 = _open( "CRT_OPEN.C", _O_RDONLY ); // C4996 // Note: _open is deprecated; consider using _sopen_s instead if( fh1 == -1 ) perror( "Open failed on input file" ); else { printf( "Open succeeded on input file\n" ); _close( fh1 ); } fh2 = _open( "CRT_OPEN.OUT", _O_WRONLY | _O_CREAT, _S_IREAD | _S_IWRITE ); // C4996 if( fh2 == -1 ) perror( "Open failed on output file" ); else { printf( "Open succeeded on output file\n" ); _close( fh2 ); } }
即當某個文件被一個進程以寫模式打開的時候,其他進程無法以讀/寫模式打開,當某個進程以讀模式打開的時候,其他進程最多只能以讀模式打開,否則操作系統會強制返回句柄錯誤
Relevant Link:
https://msdn.microsoft.com/en-us/library/z0kc8e3z.aspx https://msdn.microsoft.com/en-us/library/w64k0ytk.aspx
0x3: 打開文件: CreateFile Win32 API
在windows下,創建新文件、打開文件都使用同一個Win32 API: createfile
HANDLE CreateFile(
LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
Parametes參數列表
1. lpFileName: 要打開的文件的名字 2. dwDesiredAccess 1) GENERIC_READ: 表示允許對設備進行讀訪問 2) GENERIC_WRITE: 表示允許對設備進行寫訪問(可組合使用) 3) GENERIC_EXECUTE: 只允許執行 4) 如果為零: 表示只允許獲取與一個設備有關的信息 3. dwShareMode 1) 零: 表示不共享 2) FILE_SHARE_READ: 表示其他進程允許對這個文件發起"讀請求"共享訪問 3) FILE_SHARE_WRITE: 表示其他進程允許對這個文件發起"寫請求"共享訪問 4. lpSecurityAttributes: SECURITY_ATTRIBUTES 指向一個SECURITY_ATTRIBUTES結構的指針,定義了文件的安全特性(如果操作系統支持的話) 5. dwCreationDisposition 1) CREATE_NEW: 創建文件,如文件存在則會出錯 2) CREATE_ALWAYS: 創建文件,會改寫前一個文件 3) OPEN_EXISTING: 文件必須已經存在。由設備提出要求 4) OPEN_ALWAYS: 如文件不存在則創建它 5) TRUNCATE_EXISTING: 講現有文件縮短為零長度 6. dwFlagsAndAttributes 1) FILE_ATTRIBUTE_ARCHIVE: 標記歸檔屬性 2) FILE_ATTRIBUTE_COMPRESSED: 將文件標記為已壓縮,或者標記為文件在目錄中的默認壓縮方式 3) FILE_ATTRIBUTE_NORMAL: 默認屬性 4) FILE_ATTRIBUTE_HIDDEN: 隱藏文件或目錄 5) FILE_ATTRIBUTE_READONLY: 文件為只讀 6) FILE_ATTRIBUTE_SYSTEM: 文件為系統文件 7) FILE_FLAG_WRITE_THROUGH: 操作系統不得推遲對文件的寫操作 8) FILE_FLAG_OVERLAPPED: 允許對文件進行重疊操作 9) FILE_FLAG_NO_BUFFERING: 禁止對文件進行緩沖處理。文件只能寫入磁盤卷的扇區塊 10) FILE_FLAG_RANDOM_ACCESS: 針對隨機訪問對文件緩沖進行優化 11) FILE_FLAG_SEQUENTIAL_SCAN: 針對連續訪問對文件緩沖進行優化 12) FILE_FLAG_DELETE_ON_CLOSE: 關閉了上一次打開的句柄后,將文件刪除。特別適合臨時文件 //也可在 Windows NT 下組合使用下述常數標記: 13) SECURITY_ANONYMOUS 14) SECURITY_IDENTIFICATION 15) SECURITY_IMPERSONATION 16) SECURITY_DELEGATION 17) SECURITY_CONTEXT_TRACKING 18) SECURITY_EFFECTIVE_ONLY 7. hTemplateFile: 如果不為零,則指定一個文件句柄。新文件將從這個文件中復制擴展屬性
在createfile的參數中,和文件獨占相關較大的是dwShareMode這個參數,如果進程在打開文件的時候,通過dwShareMode參數指定了FILE_SHARE_WRITE,相當於加上了x互斥鎖,則其他進程無法再打開這個文件,如果通過dwShareMode參數指定了FILE_SHARE_READ,相當於加上了s共享鎖,其他進程最多只能以讀模式打開
Relevant Link:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx https://msdn.microsoft.com/zh-cn/library/aa914735.aspx http://www.cppblog.com/yishanhante/articles/19545.html
0x4: 刪除文件: DeleteFile()
BOOL WINAPI DeleteFile(
_In_ LPCTSTR lpFileName
);
Return value
1. If the function succeeds, the return value is nonzero. 2. If an application attempts to delete a file that does not exist, the DeleteFile function fails with ERROR_FILE_NOT_FOUND. 3. If the file is a read-only file, the function fails with ERROR_ACCESS_DENIED.
The following list identifies some tips for deleting, removing, or closing files:
1. To delete a read-only file, first you must remove the read-only attribute. 2. To delete or rename a file, you must have either delete permission on the file, or delete child permission in the parent directory. 3. To recursively delete the files in a directory, use the SHFileOperation function. 4. To remove an empty directory, use the RemoveDirectory function. 5. To close an open file, use the CloseHandle function. 6. If you set up a directory with all access except delete and delete child, and the access control lists (ACL) of new files are inherited, then you can create a file without being able to delete it. However, then you can create a file, and then get all the access you request on the handle that is returned to you at the time you create the file. 7. If you request delete permission at the time you create a file, you can delete or rename the file with that handle, but not with any other handle. For more information, see File Security and Access Rights. 8. The DeleteFile function fails if an application attempts to delete a file that has other handles open for normal I/O or as a memory-mapped file (FILE_SHARE_DELETE must have been specified when other handles were opened). 9. The DeleteFile function marks a file for deletion on close. Therefore, the file deletion does not occur until the last handle to the file is closed. Subsequent calls to CreateFile to open the file fail with ERROR_ACCESS_DENIED.
windows操作系統有嚴格的handler句柄的概念,並且操作系統對進行對文件句柄的獨占保護提供的底層支持
Relevant Link:
https://msdn.microsoft.com/zh-cn/library/windows/desktop/aa363915(v=vs.85).aspx
Copyright (c) 2015 Little5ann All rights reserved