Windows、Linux下文件操作(写、删除)错误的产生原因、及解决方法

catalog

. 引言
. Linux平台上涉及的File IO操作
. 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

错误代码

. EACCESS: 权限不足
. EMFILE: 已达到进程可同时打开的文件数上限
. ENFILE: 已达到系统可同时打开的文件数上限
. ENOTDIR: 参数name非真正的目录
. ENOENT: 参数name指定的目录不存在,或是参数name为一空字符串
. ENOMEM: 核心内存不足

其中EACCESS(权限不足)是最经常遇到的问题

. Each directory in the path name preceding the directory to be opened
) 调用opendir,如果传入路径参数的各个分量中,有一个分量没有X权限,则返回Permission denied(EACCES)错误
) 没有X权限,ll命令虽然可以列出该目录下的文件及目录,但是同样因为可执行(x)权限无法查看属性
) cd命令需要可执行(x)权限,因此无法通过cd命令切换到该目录下,子目录也是不可切换的,即便对子目录有完整权限
) 同样对于cat命令,由于缺少可执行(x)权限,该目录下的文件也是不可读的
) 对于stat系统调用和cat命令是一样的,如果缺少x权限,则stat将执行失败
) 没有x权限,目录下的文件也不能cp
) 父目录没有x权限,即使该目录下的文件可读可执行,也无法执行该目录下的文件
//需要明白的是,Linux的目录寻址定位是逐个路径分量逐段进行的,如果父目录因为没有x权限受阻了,则即使目录下的子文件权限足够,也无法正常操作 . The directory to be opened
如果传入的目录路径没有R权限, 则调用opendir返回Permission denied(EACCES)错误,没有R权限导致无法读取目录inode的元信息

我们梳理一下概念

. 对于文件权限: 可读权限(r)是可执行(x)的基础,因为执行的前提是读取二进制数据
. 对于目录权限: 可执行(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()
错误代码

. EOVERFLOW
One of the values in the structure to be returned cannot be represented correctly.
The readdir() function may fail if: . EBADF
The dirp argument does not refer to an open directory stream. . 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
错误代码

. ENOENT: 参数file_name指定的文件不存在
. ENOTDIR: 路径中的目录存在但却非真正的目录
. ELOOP: 欲打开的文件有过多符号连接问题,上限为16符号连接
. EFAULT: 参数buf为无效指针,指向无法存在的内存空间
. EACCESS: 存取文件时被拒绝
. ENOMEM: 核心内存不足
. ENAMETOOLONG: 参数file_name的路径名称太长

如果需要修改文件属性,可以通过如下方法

. 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
*/ . 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填充
*/ . 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指向欲打开的文件路径字符串

错误代码

. EEXIST: 参数pathname所指的文件已存在,却使用了O_CREAT和O_EXCL旗标
. EACCESS: 参数pathname所指的文件不符合所要求测试的权限
. EROFS: 欲测试写入权限的文件存在于只读文件系统内
. EFAULT: 参数pathname指针超出可存取内存空间
. EINVAL: 参数mode不正确
. ENAMETOOLONG: 参数pathname太长
. ENOTDIR: 参数pathname不是目录
. ENOMEM: 核心内存不足
. ELOOP: 参数pathname有过多符号连接问题
. 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中

错误代码

. EINTR: 此调用被信号所中断.
. EAGAIN: 当使用不可阻断I/O 时(O_NONBLOCK),若无数据可读取则返回此值
. EADF: 参数fd非有效的文件描述词,或该文件已关闭

Relevant Link:

http://c.biancheng.net/cpp/html/241.html

0x6: Linux文件占用状态下: 打开文件

文件的操作的独占性是通过文件系统flag指定,而操作系统提供底层的强制保证的,但是Linux并不像windows那样提供强制的读写互斥保护,即

. 两个进程可以同时以写模式打开一个文件,同时向其中写入数据,这可能导致不一致情况的发生
. 两个进程可以分别以读模式、写模式打开一个文件,同时进行读/写的操作,这可能导致错误的数据读取,即脏读、幻读

因为对于Linux VFS文件系统来说,打开一个文件,open系统调用仅仅是向操作系统发起一个inode操作请求,每个进程都持有自己独有的fd句柄,并通过引用共享内核内存中的inode向底层的存储数据的超级块发起数据操作请求(inode的引用计数加1),VFS本身并没有处理互斥的问题

0x7: Linux文件占用状态下: 删除文件

和打开文件一样,Linux VFS并没有对文件的删除采用独占保护。Linux是通过link的数量来控制文件删除的,只有当一个文件不存在任何link的时候,这个文件才会被删除。一般来说,每个文件都有2个link计数器

. i_count: i_count的意义是当前文件使用者(或被调用)的数量
. 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 支持的文件锁技术主要包括

. 劝告锁(advisory lock)
. 强制锁(mandatory lock)
/*
在Linux中,不论进程是在使用劝告锁还是强制锁,它都可以同时使用共享锁(S锁 读锁)和排他锁(X锁 写锁)
1. 多个共享锁之间不会相互干扰,多个进程在同一时刻可以对同一个文件加共享锁
2. 如果一个进程对该文件加了排他锁,那么其他进程则无权再对该文件加共享锁或者排他锁,直到该排他锁被释放
*/ . 共享模式强制锁(share-mode mandatory lock)
. 租借锁(lease)

对于同一个文件来说,它可以同时拥有很多读者,但是在某一特定时刻,它只能拥有一个写者,它们之间的兼容关系如表所示

  是否满足请求
当前加上的锁 共享锁 排他锁
共享锁
排他锁

1. 劝告锁

劝告锁是一种协同工作的锁。对于这一种锁来说,内核只提供加锁以及检测文件是否已经加锁的手段,但是内核并不参与锁的控制和协调。也就是说,如果有进程不遵守"游戏规则",不检查目标文件是否已经由别的进程加了锁就往其中写入数据,那么内核是不会加以阻拦的。因此,劝告锁并不能阻止进程对文件的访问,而只能依靠各个进程在访问文件之前检查该文件是否已经被其他进程加锁来实现并发控制
进程需要事先对锁的状态做一个约定,并根据锁的当前状态和相互关系来确定其他进程是否能对文件执行指定的操作。从这点上来说,劝告锁的工作方式与使用信号量保护临界区的方式非常类似。
劝告锁可以对文件的任意一个部分进行加锁,也可以对整个文件进行加锁,甚至可以对文件将来增大的部分也进行加锁。由于进程可以选择对文件的某个部分进行加锁,所以一个进程可以获得关于某个文件不同部分的多个锁
2. 强制锁

与劝告锁不同,强制锁是一种内核强制采用的文件锁,它是从System V Release 3开始引入的。每当有系统调用open()、read()以及write()发生的时候,内核都要检查并确保这些系统调用不会违反在所访问文件上加的强制锁约束。也就是说,如果有进程不遵守游戏规则,硬要往加了锁的文件中写入内容,内核就会加以阻拦

. 如果一个文件已经被加上了读锁或者共享锁,那么其他进程再对这个文件进行写操作就会被内核阻止
. 如果一个文件已经被加上了写锁或者排他锁,那么其他进程再对这个文件进行读取或者写操作就会被内核阻止

如果其他进程试图访问一个已经加有强制锁的文件,进程行为取决于所执行的操作模式和文件锁的类型,归纳如表所示

当前锁类型 阻塞读 阻塞写 非阻塞读 非阻塞写
读锁 正常读取数据 阻塞 正常读取数据 EAGAIN
写锁 阻塞 阻塞 EAGAIN EAGAIN

需要注意的是,如果要访问的文件的锁类型与要执行的操作存在冲突,那么采用阻塞读/写操作的进程会被阻塞,而采用非阻塞读/写操作的进程则不会阻塞,而是立即返回EAGAIN
然而,在有些应用中并不适合使用强制锁,所以索引节点结构中的i_flags字段中定义了一个标志位MS_MANDLOCK用于有选择地允许或者不允许对一个文件使用强制锁。在super_block结构中,也可以将s_flags这个标志为设置为1或者0,用以表示整个设备上的文件是否允许使用强制锁
要想对一个文件采用强制锁,必须按照以下步骤执行

. 使用 -o mand 选项来挂载文件系统。这样在执行 mount() 系统调用时,会传入 MS_MANDLOCK 标记,从而将 super_block 结构中的 s_flags 设置为 ,用来表示在这个文件系统上可以采用强制锁
*
# mount -o mand /dev/sdb7 /mnt
# mount | grep mnt
/dev/sdb7 on /mnt type ext3 (rw,mand)
*/ . 修改要加强制锁的文件的权限: 设置SGID位,并清除组可执行位。这种组合通常来说是毫无意义的,系统用来表示该文件被加了强制锁。例如:
# touch /mnt/testfile
# ls -l /mnt/testfile
-rw-r--r-- root root Jun : /mnt/testfile
# chmod g+s /mnt/testfile
# chmod g-x /mnt/testfile
# ls -l /mnt/testfile
-rw-r-Sr-- root root Jun : /mnt/testfile . 使用 fcntl() 系统调用对该文件进行加锁或解锁操作

3. 共享模式锁

共享模式强制锁可以用于某些私有网络文件系统,如果某个文件被加上了共享模式强制锁,那么其他进程打开该文件的时候不能与该文件的共享模式强制锁所设置的访问模式相冲突。但是由于可移植性不好,因此并不建议使用这种锁
4. 租借锁

采用强制锁之后,如果一个进程对某个文件拥有写锁,只要它不释放这个锁,就会导致访问该文件的其他进程全部被阻塞或不断失败重试;即使该进程只拥有读锁,也会造成后续更新该文件的进程的阻塞。为了解决这个问题,Linux 中采用了一种新型的租借锁。
当进程尝试打开一个被租借锁保护的文件时,该进程会被阻塞,同时,在一定时间内拥有该文件租借锁的进程会收到一个信号。收到信号之后,拥有该文件租借锁的进程会首先更新文件,从而保证了文件内容的一致性,接着,该进程释放这个租借锁。如果拥有租借锁的进程在一定的时间间隔内没有完成工作,内核就会自动删除这个租借锁或者将该锁进行降级,从而允许被阻塞的进程继续工作

//系统默认的这段间隔时间是45秒钟
int lease_break_time = ;

这个参数可以通过修改"/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[];
struct stat statbuf;
if (argc != )
{
fprintf(stderr, "usage: %s filename\n", argv[]);
exit();
}
if ((fd = open(argv[], O_RDWR | O_CREAT | O_TRUNC )) < )
err_sys("open error");
if (write(fd, "hello world", ) != )
err_sys("write error"); /* turn on set-group-ID and turn off group-execute */
if (fstat(fd, &statbuf) < )
err_sys("fstat error");
if (fchmod(fd, (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < )
err_sys("fchmod error"); sleep(); if ((pid = fork()) < )
{
err_sys("fork error");
}
else if (pid > )
{ /* parent */
/* write lock entire file */
if (write_lock(fd, , SEEK_SET, ) < )
err_sys("write_lock error");
sleep(); /* wait for child to set lock and read data */ if (waitpid(pid, NULL, ) < )
err_sys("waitpid error");
}
else
{ /* child */
sleep(); /* wait for parent to set lock */ if ( (val = fcntl(fd, F_GETFL, )) < )
err_sys("fcntl F_GETFL error"); val |= O_NONBLOCK; /* turn on O_NONBLOCK flag */
if (fcntl(fd, F_SETFL, val) < )
err_sys("fcntl F_SETFL error");
/* first let's see what error we get if region is locked */
if (read_lock(fd, , SEEK_SET, ) != -) /* 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, , SEEK_SET) == -)
err_sys("lseek error");
if (read(fd, buf, ) < )
printf("read failed (mandatory locking works)\n");
else
printf("read OK (no mandatory locking), buf = %5.5s\n", buf);
}
exit();
}
/*
# 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=; // If the directory is not specified as a command-line argument,
// print usage. if(argc != )
{
_tprintf(TEXT("\nUsage: %s <directory name>\n"), argv[]);
return (-);
} // Check that the input path plus 3 is not longer than MAX_PATH.
// Three characters are for the "\*" plus NULL appended below. StringCchLength(argv[], MAX_PATH, &length_of_arg); if (length_of_arg > (MAX_PATH - ))
{
_tprintf(TEXT("\nDirectory path is too long.\n"));
return (-);
} _tprintf(TEXT("\nTarget directory is %s\n\n"), argv[]); // 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[]);
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) != ); 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,
, NULL ); // Display the error message and clean up lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
(lstrlen((LPCTSTR)lpMsgBuf)+lstrlen((LPCTSTR)lpszFunction)+)*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涉及的参数

. filename: File name.
. 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>.
) _O_APPEND: Moves the file pointer to the end of the file before every write operation.
) _O_BINARY: Opens the file in binary (untranslated) mode. (See fopen for a description of binary mode.)
) _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.
) _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.
) _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.
) _O_CREAT | _O_EXCL: Returns an error value if the file specified by filename exists. Applies only when used with _O_CREAT.
) _O_NOINHERIT: Prevents creation of a shared file descriptor.
) _O_RANDOM: Specifies that caching is optimized for, but not restricted to, random access from disk.
) _O_RDONLY: Opens a file for reading only. Cannot be specified with _O_RDWR or _O_WRONLY.
) _O_RDWR: Opens file for both reading and writing. Cannot be specified with _O_RDONLY or _O_WRONLY.
) _O_SEQUENTIAL: Specifies that caching is optimized for, but not restricted to, sequential access from disk.
) _O_TEXT: Opens a file in text (translated) mode. (For more information, see Text and Binary Mode File I/O and fopen.)
) _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.
) _O_WRONLY: Opens the file for writing only. Cannot be specified with _O_RDONLY or _O_RDWR.
) _O_U16TEXT: Opens the file in Unicode UTF- mode.
) _O_U8TEXT: Opens the file in Unicode UTF- mode.
) _O_WTEXT: Opens the file in Unicode mode. . 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
) _S_IREAD: Only reading permitted.
) _S_IWRITE: Writing permitted. (In effect, permits reading and writing.)
) _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 == - )
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 == - )
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参数列表

. lpFileName: 要打开的文件的名字
. dwDesiredAccess
) GENERIC_READ: 表示允许对设备进行读访问
) GENERIC_WRITE: 表示允许对设备进行写访问(可组合使用)
) GENERIC_EXECUTE: 只允许执行
) 如果为零: 表示只允许获取与一个设备有关的信息
. dwShareMode
) 零: 表示不共享
) FILE_SHARE_READ: 表示其他进程允许对这个文件发起"读请求"共享访问
) FILE_SHARE_WRITE: 表示其他进程允许对这个文件发起"写请求"共享访问
. lpSecurityAttributes: SECURITY_ATTRIBUTES
指向一个SECURITY_ATTRIBUTES结构的指针,定义了文件的安全特性(如果操作系统支持的话)
. dwCreationDisposition
) CREATE_NEW: 创建文件,如文件存在则会出错
) CREATE_ALWAYS: 创建文件,会改写前一个文件
) OPEN_EXISTING: 文件必须已经存在。由设备提出要求
) OPEN_ALWAYS: 如文件不存在则创建它
) TRUNCATE_EXISTING: 讲现有文件缩短为零长度
. dwFlagsAndAttributes
) FILE_ATTRIBUTE_ARCHIVE: 标记归档属性
) FILE_ATTRIBUTE_COMPRESSED: 将文件标记为已压缩,或者标记为文件在目录中的默认压缩方式
) FILE_ATTRIBUTE_NORMAL: 默认属性
) FILE_ATTRIBUTE_HIDDEN: 隐藏文件或目录
) FILE_ATTRIBUTE_READONLY: 文件为只读
) FILE_ATTRIBUTE_SYSTEM: 文件为系统文件
) FILE_FLAG_WRITE_THROUGH: 操作系统不得推迟对文件的写操作
) FILE_FLAG_OVERLAPPED: 允许对文件进行重叠操作
) FILE_FLAG_NO_BUFFERING: 禁止对文件进行缓冲处理。文件只能写入磁盘卷的扇区块
) FILE_FLAG_RANDOM_ACCESS: 针对随机访问对文件缓冲进行优化
) FILE_FLAG_SEQUENTIAL_SCAN: 针对连续访问对文件缓冲进行优化
) FILE_FLAG_DELETE_ON_CLOSE: 关闭了上一次打开的句柄后,将文件删除。特别适合临时文件
//也可在 Windows NT 下组合使用下述常数标记:
) SECURITY_ANONYMOUS
) SECURITY_IDENTIFICATION
) SECURITY_IMPERSONATION
) SECURITY_DELEGATION
) SECURITY_CONTEXT_TRACKING
) SECURITY_EFFECTIVE_ONLY
. 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

. If the function succeeds, the return value is nonzero.
. If an application attempts to delete a file that does not exist, the DeleteFile function fails with ERROR_FILE_NOT_FOUND.
. 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:

. To delete a read-only file, first you must remove the read-only attribute.
. To delete or rename a file, you must have either delete permission on the file, or delete child permission in the parent directory.
. To recursively delete the files in a directory, use the SHFileOperation function.
. To remove an empty directory, use the RemoveDirectory function.
. To close an open file, use the CloseHandle function.
. 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.
. 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.
. 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).
. 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

上一篇:select2 下拉无法显示


下一篇:日常总结——JSP篇(补)