参考引用
#include
#include
#include
int stat(const char *pathname, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
int fstatat(int fd, const char *pathname, struct stat *buf, int flag);
函数返回值
一旦给出 pathname
第 2 个参数 buf 是一个指针,存放文件属性,是一个 Inode 结构体指针。其基本形式如下
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* Inode number */
mode_t st_mode; /* File type and mode */
nlink_t st_nlink; /* Number of hard links */
uid_t st_uid; /* User ID of owner */
gid_t st_gid; /* Group ID of owner */
dev_t st_rdev; /* Device ID (if special file) */
off_t st_size; /* Total size, in bytes */
blksize_t st_blksize; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
struct timespec st_atim; /* Time of last access */
struct timespec st_mtim; /* Time of last modification */
struct timespec st_ctim; /* Time of last status change */
};
获取文件大小:st_size
#include
#include
#include
#include
int main(int argc, char* argv[]) {
struct stat sbuf;
int ret = stat(argv[1], &sbuf);
if (ret == -1) {
perror("stat error");
exit(1);
}
printf("file size: %ld\n", sbuf.st_size);
return 0;
}
文件权限位
#include
#include
#include
#include
int main(int argc, char* argv[]) {
struct stat sbuf;
// stat 会穿透符号链接,导致无法判断符号链接
// int ret = stat(argv[1], &sbuf);
int ret = lstat(argv[1], &sbuf);
if (ret == -1) {
perror("stat error");
exit(1);
}
if (S_ISREG(sbuf.st_mode)) {
printf("It's a regular\n");
} else if (S_ISDIR(sbuf.st_mode)) {
printf("It's a dir\n");
} else if (S_ISFIFO(sbuf.st_mode)) {
printf("It's a pipe\n");
} else if (S_ISLNK(sbuf.st_mode)) {
printf("It's a sym link\n");
}
return 0;
}
ls -l 命令不会穿透符号链接;cat 和 vim 命令则会穿透符号链接
st_mode 值也包含了对文件的访问权限位。所有文件类型 (目录、字符特别文件等) 都有访问权限
每个文件有 9 个访问权限位
进程每次打开、创建或删除一个文件时,内核就进行文件访问权限测试,而这种测试可能涉及文件的所有者 (st_uid 和 st_gid)、进程的有效 ID(有效用户 ID 和有效组 ID) 以及进程的附属组 ID(若支持的话)。两个所有者 ID 是文件的性质,而两个有效 ID 和附属组 ID 则是进程的性质
#include
#include
int access(const char *pathname, int mode);
int faccessat(int fd, const char *pathname, int mode, int flag);
即使一个进程可能已经通过设置用户 ID 以超级用户权限运行,它仍可能想验证其实际用户能否访问一个给定的文件
函数返回值
若成功,返回 0
若出错,返回 -1
access 和 faccessat 函数是按实际用户 ID 和实际组 ID 进行访问权限测试的
如果测试文件是否已经存在,mode 就为 F_OK,否则 mode 是下图所列常量的按位或
faccessat 函数与 access 函数在下面两种情况下是相同的,否则,faccessat 计算相对于打开目录 (由 fd 参数指向) 的 pathname
flag 参数可以用于改变 faccessat 的行为
#include
#include
#include
#include
int main(int argc, char* argv[]) {
if (argc != 2) {
perror("usage: access " );
exit(1);
}
if (access(argv[1], R_OK) < 0) {
perror("access error");
exit(1);
} else {
printf("read access OK\n");
}
if (open(argv[1], O_RDONLY) < 0) {
perror("open error");
exit(1);
} else {
printf("open for reading OK\n");
}
return 0;
}
$ gcc access.c -o access
$ ./access fcntl.c
read access OK
open for reading OK
#include
#include
// mask 取值见 2.4 节图
mode_t umask(mode_t mask);
umask 函数为进程设置文件模式创建屏蔽字,并返回之前的值,这是少数几个没有出错返函数中的一个
函数返回值
在进程创建一个新文件或新目录时,就一定会使用文模式创建屏字
#include
#include
#include
#include
#include
#define RWRWRW (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
int main(int argc, char* argv[]) {
umask(0);
if (creat("foo", RWRWRW) < 0) {
perror("creat error for foo");
}
umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if (creat("bar", RWRWRW) < 0) {
perror("creat error for bar");
}
return 0;
}
$ umask # 先打印当前文件模式创建屏蔽字
0002
$ gcc umask.c -o umask
$ ./umask
$ ls -l foo bar
-rw------- 1 yxd yxd 0 9月 14 08:53 bar
-rw-rw-rw- 1 yxd yxd 0 9月 14 08:53 foo
$ umask
0002
$ umask -S # 观察文件模式创建屏蔽字是否更改
u=rwx,g=rwx,o=rx
$ umask 027 # 更改文件模式创建屏蔽字
$ umask -S
u=rwx,g=rx,o=
#include
#include
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
int fchmodat(int fd, const char *pathname, mode_t mode, int flag);
函数返回值
chmod 函数在指定的文件上进行操作,而 fchmod 函数则对已打开的文件进行操作
为了改变一个文件的权限位
参数 mode 是下图中所示常量的按位或
#include
#include
#include
#include
#include
#define RWRWRW (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
int main(int argc, char* argv[]) {
struct stat statbuf;
// 对于其当前状态设置权限:先调用 stat 获得其当前权限,然后修改它
// 显式地打开设置组 ID 位、关闭了组执行位
if (stat("foo", &statbuf) < 0) {
perror("stat error for foo");
exit(1);
}
if (chmod("foo", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0) {
perror("chmod error for foo");
exit(1);
}
// 不管文件 bar 的当前权限位如何,都将其权限设置为一个绝对值 rw-r--r--
if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) {
perror("chmod error for bar");
}
return 0;
}
$ gcc chmod.c -o chmod
$ ./chmod
$ ls -l foo bar
-rw-r--r-- 1 yxd yxd 0 9月 14 08:53 bar
-rw-rwSrw- 1 yxd yxd 0 9月 14 08:53 foo
下面几个 chown 函数可用于更改文件的用户 ID 和组 ID
#include
#include
// 如果两个参数 owner 或 group 中的任意一个是 -1,则对应的 ID 不变
int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char *pathname, uid_t owner, gid_t group);
int fchownat(int fd, const char *pathname, uid_t owner, gid_t group, int flag);
函数返回值
除了所引用的文件是符号链接以外,这 4 个函数的操作类似
stat 结构成员 st_size 表示以字节为单位的文件的长度
对于普通文件,其文件长度可以是 0,在开始读这种文件时,将得到文件结束指示
对于目录,文件长度通常是一个数 (如 16 或 512) 的整倍数
对于符号链接,文件长度是在文件名中的实际字节数
文件空洞
#include
#include
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
返回值
这两个函数将一个现有文件长度截断为 length
为什么目录项要游离于 inode 之外并将文件名单独存储呢?这样的存储方式有什么样的好处?
- 其目的是为了实现文件共享。Linux 允许多个目录项共享一个 inode,即共享盘块(data)
- 不同文件名,在人类眼中将它理解成两个文件,但是在内核眼里是同一个文件
#include
#include
int link(const char *oldpath, const char *newpath);
int linkat(int oldfd, const char *oldpath, int newfd, const char *newpath, int flag);
#include
#include
#include
#include
#include
#include
int main(int argc, char* argv[]) {
link (argv[1], argv[2]);
unlink(argv[1]);
return 0;
}
#include
#include
int unlink(const char *pathname);
int unlinkat(int fd, const char *pathname, int flag);
只有当链接计数达到 0 时,该文件的内容才可被删除。另一个条件也会阻止删除文件的内容:只要有进程打开了该文件,其内容也不能删除。关闭一个文件时,内核首先检查打开该文件的进程个数,如果这个计数达到 0,内核再去检查其链接计数,如果计数也是 0,那么就删除该文件的内容
隐式回收:当进程结束运行时,所有该进程打开的文件会被关闭,申请的内存空间会被释放。系统的这一特性称之为隐式回收系统资源
#include
#include
#include
#include
#include
#include
int main(int argc, char* argv[]) {
int fd, ret;
char* p = "test of unlink\n";
char* p2 = "after write something.\n";
fd = open("lseek.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror("open temp error");
exit(1);
}
ret = unlink("lseek.txt");
if (ret < 0) {
perror("unlink error");
exit(1);
}
// 此处的 write 实际是把内容写到了缓冲区而非磁盘区
ret = write(fd, p, strlen(p));
if (ret == -1) {
perror("-----write error");
}
printf("hi! I'm printf\n");
ret = write(fd, p2, strlen(p2));
if (ret == -1) {
perror("-----write error");
}
printf("Enter anykey continue\n");
getchar();
p[3] = 'H';
close(fd);
return 0;
}
#include
int remove(const char* pathname);
#include
#include
int rename(const char *oldpath, const char *newpath);
int renameat(int oldfd, const char *oldpath, int newfd, const char *newpath);
如果 oldname 指的是一个文件,那么为该文件或符号链接重命名
如果 oldname 指的是一个目录,那么为该目录重命名
不能对 . 和 … 重命名。更确切地说 . 和 … 都不能出现在 oldname 和 newname 的最后部分
作为一个特例,如果 oldname 和 newname 引用同一文件,则函数不做任何更改而成功返回
符号链接是对一个文件的间接指针,它与上一节所述的硬链接有所不同,硬链接直接指向文件的 i 节点
引入符号链接的原因是为了避开硬链接的一些限制
对符号链接以及它指向何种对象并无任何文件系统限制,任何用户都可以创建指向目录的符号链接,符号链接一般用于将一个文件或整个目录结构移到系统中另一个位置
$ mkdir foo # 创建一个新目录
$ touch foo/a # 创建一个 0 长度的文件
$ ln -s ../foo foo/testdir # 创建一个符号链接
$ ls -l foo
total 0
-rw-rw-r-- 1 yxd yxd 0 9月 14 15:28 a
lrwxrwxrwx 1 yxd yxd 6 9月 14 15:28 testdir -> ../foo
$ ln -s /no/such/file myfile # 创建一个符号链接
$ ls myfile
myfile
$ cat myfile # 试图查看文件
cat: myfile: No such file or directory
$ ls -l myfile
lrwxrwxrwx 1 yxd yxd 13 9月 14 15:37 myfile -> /no/such/file
#include
#include
int symlink(const char *target, const char *linkpath);
int symlinkat(const char *target, int newdirfd, const char *linkpath);
#include
#include
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
ssize_t readlinkat(int fd, const char *pathname, char *buf, size_t bufsiz);
#include
#include
#include
int mkdir(const char *pathname, mode_t mode);
int mkdirat(int fd, const char *pathname, mode_t mode);
#include
int rmdir(const char *pathname);
返回值
用 rmdir 函数可以删除一个空目录
如果调用此函数使目录的链接计数成为 0,并且也没有其他进程打开此目录,则释放由此目录占用的空间
如果在链接计数达到 0 时,有一个或多个进程打开此目录,则在此函数返回前删除最后一个链接及 . 和 … 项
另外,在此目录中不能再创建新文件。但是在最后一个进程关闭它之前并不释放此目录
#include
#include
// 1、打开目录
// 若成功,返回目录结构体指针;若出错,返回 NULL
// DIR* 类似于 FILE*
DIR *opendir(const char *name);
DIR *fdopendir(int fd);
// 2、读目录
// 若成功,返回目录项结构体指针;若在目录尾或出错,返回 NULL,设置 errno 为相应值
struct dirent *readdir(DIR *dirp);
// 3、关闭目录
// 若成功,返回 0;若出错,返回 -1,设置 errno 为相应值
int closedir(DIR *dirp);
ino_t d_ino; // inode 编号
char d_name[256] // 文件名
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char* argv[]) {
DIR* dp;
struct dirent* sdp;
dp = opendir(argv[1]);
if (dp == NULL) {
perror("opendir error");
exit(1);
}
while ((sdp = readdir(dp)) != NULL) {
if ((strcmp(sdp->d_name, ".") == 0)) {
continue;
}
printf("%s\t", sdp->d_name);
}
printf("\n");
closedir(dp);
return 0;
}
$ gcc myls.c -o myls
$ ./myls .. # 与 ls .. 命令等价
每个进程都有一个当前工作目录,此目录是搜索所有相对路径名的起点 (不以斜线开始的路径名为相对路径名)。当前工作目录是进程的一个属性,起始目录则是登录名的一个属性
进程调用 chdir 或 fchdir 函数可以更改当前工作目录
#include
int chdir(const char *path);
int fchdir(int fd);
返回值
因为当前工作目录是进程的一个属性,所以它只影响调用 chdir 的进程本身,而不影响其他进程
函数 getcwd 功能
#include
char* getcwd(char* buf, size_t size);
返回值
必须向此函数传递两个参数,一个是缓冲区地址 buf,另一个是缓冲区的长度 size (以字节为单位)。该缓冲区必须有足够的长度以容纳绝对路径名再加上一个终止 null 字节,否则返回出错
当一个应用程序需要在文件系统中返回到它工作的出发点时,getcwd 函数是有用的
#include
#include
#include
#include
#include
#include
#include
void isFile(char* name);
// 打开目录读取,处理目录
void read_dir(char* dir, void (*func)(char*)) {
char path[256];
DIR* dp;
struct dirent *sdp;
dp = opendir(dir);
if (dp == NULL) {
perror("opendir error");
return;
}
// 读取目录项
while ((sdp = readdir(dp)) != NULL) {
if (strcmp(sdp->d_name, ".") == 0 || strcmp(sdp->d_name, "..") == 0) {
continue;
}
// 目录项本身不可访问, 拼接 目录/目录项
sprintf(path, "%s/%s", dir, sdp->d_name);
// 判断文件类型,目录递归进入,文件显示名字/大小
(*func)(path);
}
closedir(dp);
return;
}
void isFile(char* name) {
int ret = 0;
struct stat sub;
// 获取文件属性, 判断文件类型
ret = stat(name, &sub);
if (ret == -1) {
perror("stat error");
return;
}
// 是目录文件
if (S_ISDIR(sub.st_mode)) {
read_dir(name, isFile);
}
// 是普通文件, 直接打印名字/大小
printf("%10s\t\t%ld\n", name, sub.st_size);
return;
}
int main(int argc, char* argv[]) {
// 命令行参数个数 argc = ---→ ./ls-R
// 命令行参数列表 argv[1]---→ ./ls-R /home/test
if (argc == 1) { // 判断命令行参数
isFile(".");
} else {
isFile(argv[1]);
}
return 0;
}
$ gcc ls-R.c -o ls-R
$ ./ls-R
./fcntl 8384
./mycat.c 262
./ls-R.c 943
./fcntl2 8432
./ls-R 8768
. 4096