APUE: Files and Directories

stat, fstat, fstatat和lstat函数

#include <sys/stat.h>node

int stat(const char *restrict pathname, struct stat *restrict buf);跨域

int fstat(int fd, struct stat *buf);缓存

int lstat(const char *restrict pathname, struct stat *restrict buf);网络

int fstatat(int fd, const char *restrict pathname, struct stat *restrict buf, int flag);数据结构

                        returns: 成功返回0, 失败返回-1socket

stat函数用于处理文件名, fstat用于处理文件描述符, 而lstat用于处理连接文件.函数

stat的数据结构以下:测试

struct stat {
    mode_t  st_mode;    // 文件类型和模式
    ino_t   st_ino;     // i-node数量
    dev_t   st_dev;     // 设备数量
    dev_t   st_rdev;    // 特殊文件的设备数量
    nlink_t st_nlink;   // 连接个数
    uid_t   st_uid;     // 用户id
    gid_t   st_gid;     // 分组id
    off_t   st_size;    // 文件大小
    struct  timespec    st_atim;    // 最后access时间
    struct  timespec    st_mtim;    // 最后修改时间
    struct  timespec    st_ctim;    // 最后文件状态改变时间
    blksize_t   st_blksize; // I/O块大小
    blkcnt_t    st_blocks;  // 所分配的块大小
}

一个实际的例子:ui

#include <stdio.h>
#include <sys/stat.h>

int main( void )
{
    struct stat buf;
    if (0 == stat("./test.js", &buf)) {
        // 33188
        printf("%d\n", buf.st_mode);
        // 47154894
        printf("%llu\n", buf.st_ino);
        // 16777220
        printf("%d\n", buf.st_dev);
        // 0
        printf("%d\n", buf.st_rdev);
        // 1
        printf("%d\n", buf.st_nlink);
        // 501
        printf("%d\n", buf.st_uid);
        // 20
        printf("%d\n", buf.st_gid);
        // 69
        printf("%lld\n", buf.st_size);
        // 4096
        printf("%d\n", buf.st_blksize);
        // 8
        printf("%lld\n", buf.st_blocks);
    }

    return 0;
}

文件类型

1. 普通文件, 对系统内核来讲不区分文本仍是二进制文件.spa

2. 目录文件: 包含其它文件名而且一个指针指向这些文件的一个文件.

3. 块特殊文件: 带缓存的访问文件,每次访问以固定长度为单位进行.

4. 字符特殊文件: 不带缓存的访问文件, 访问长度可变.

备注: 全部设备不是块特殊文件就是字符特殊文件.

5. FIFO: 用于进程间通讯的文件.

6. socket: 进程间的网络通讯.

7. 符号连接: 一个文件(相似指针)指向另外一个文件.

咱们能够经过stat结构的st_mode来判断文件的类型, 使用以下宏定义:

S_ISREG(): 普通文件

S_ISDIR(): 目录文件

S_ISCHR(): 字符特殊文件

S_ISBLK(): 块特殊文件

S_ISFIFO(): FIFO文件

S_ISLNK(): 符号连接文件

S_ISSOCK(): socket

一个实际的例子:

#include <stdio.h>
#include <sys/stat.h>

int main(int argc, char *argv[])
{
    int     i;
    struct stat buf;
    char    *ptr;
    for (i = 1; i < argc; i++){
        printf("%s: ", argv[i]);
        if (lstat(argv[i], &buf) < 0) {
            printf("lstat error\n");
            continue;
        }
        if (S_ISREG(buf.st_mode))
            ptr = "regular";
        else if (S_ISDIR(buf.st_mode))
            ptr = "directory";
        else if (S_ISCHR(buf.st_mode))
            ptr = "character special";
        else if (S_ISBLK(buf.st_mode))
            ptr = "block special";
        else if (S_ISFIFO(buf.st_mode))
            ptr = "fifo";
        else if (S_ISLNK(buf.st_mode))
            ptr = "symbolic link";
        else if (S_ISSOCK(buf.st_mode))
            ptr = "socket";
        else
            ptr = "** unknown mode ** ";
        printf("%s\n", ptr);
    }
    return 0;
}

终端输出:

leicj@leicj:~/test$ ./a.out /etc/passwd /etc /dev/log /dev/tty /var/lib/oprofile/opd_pipe /dev/sr0 /dev/cdrom
/etc/passwd: regular
/etc: directory
/dev/log: symbolic link
/dev/tty: character special
/var/lib/oprofile/opd_pipe: lstat error
/dev/sr0: block special
/dev/cdrom: symbolic link

针对S_ISDIR的定义, 查看源代码可知:

#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)

而S_IFDIR经过变换成二进制便可理解上述代码(经过其它语言转换成二进制显示):

#include <stdio.h>
#include <sys/stat.h>

int main(int argc, char *argv[])
{
    // 110000000000000
    printf("%d\n", S_IFBLK);
    // 10000000000000
    printf("%d\n", S_IFCHR);
    // 100000000000000
    printf("%d\n", S_IFDIR);
    // 1000000000000
    printf("%d\n", S_IFIFO);
    // 1010000000000000
    printf("%d\n", S_IFLNK);
    // 1000000000000000
    printf("%d\n", S_IFREG);
    // 1100000000000000
    printf("%d\n", S_IFSOCK);
    return 0;
}

设置用户ID和设置分组ID

每一个进程都具备以下id:

real user ID

real group ID

who we really are

effective user ID

effective group ID

supplementary group IDs

used for file access permission checks

saved set-user-ID

saved set-group-ID

saved by exec functions

当咱们登陆时候, real user ID/real group ID都已经肯定下来了, 一般不可更改, 但超级用户有权限修改.

而effective user ID/group ID(s)代表接触文件的权限.

set-user-ID/set-group-ID在程序执行时, 复制effective user ID/group ID

文件接触权限

普通文件具备access权限:

st_mode mask 含义

S_IRUSR

S_IWUSR

S_IXUSR

用户只读

用户只写

用户可执行

S_IRGRP

S_IWGRP

S_IXGRP

分组只读

分组只写

分组可执行

S_IROTH

S_IWOTH

S_IXOTH

其它只读

其它只写

其它可执行

access和faccessat函数

基于real user ID/group ID, 来测试文件是否可读, 可写, 可执行:

#include <unistd.h>

int access(const char *pathname, int mode);

int faccessat(int fd, const char *pathname, int mode, int flag);

                            returns: 成功返回0, 失败返回-1

而mode具备如下四个值:

mode 描述
F_OK 文件是否存在
R_OK 文件是否可读
W_OK 文件是否可写
X_OK 文件是否可执行

例子: 经过access函数来查看文件的访问权限。

#include <stdio.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    if (argc != 2)
        printf("usage:a.out <pathname>");
    if (access(argv[ 1 ], R_OK) < 0)
        printf("access error for %s\n", argv[1]);
    else
        printf("read access OK\n");
    if (open(argv[ 1 ], O_RDONLY ) < 0)
        printf("open error for %s\n", argv[1]);
    else
        printf("open for reading OK\n");

    return 0;
}

程序输出:

leicj@leicj:~/test$ ls -l a.out
-rwxrwxr-x 1 leicj leicj 8760 12月 24 10:30 a.out
leicj@leicj:~/test$ ./a.out a.out
read access OK
open for reading OK
leicj@leicj:~/test$ ls -l /etc/shadow
-rw-r----- 1 root shadow 1343 12月 22 18:29 /etc/shadow
leicj@leicj:~/test$ ./a.out /etc/shadow
access error for /etc/shadow
open error for /etc/shadow
leicj@leicj:~/test$ sudo chown root a.out
[sudo] password for leicj: 
leicj@leicj:~/test$ sudo chmod u+s a.out
leicj@leicj:~/test$ ls -l a.out
-rwsrwxr-x 1 root leicj 8760 12月 24 10:30 a.out
leicj@leicj:~/test$ ./a.out /etc/shadow
access error for /etc/shadow
open for reading OK

umask函数

#include <sys/stat.h>

mode_t umask(mode_t cmask);

                    returns: 返回文件以前的模式

umask用于屏蔽模式, 例如umask(444), 则后面建立的文件均不可读:

mask bit 含义
0400 用户可读
0200 用户可写
0100 用户可执行
0040 分组可读
0020 分组可写
0010 分组可执行
0004 其它可读
0002 其它可写
0001 其它可执行

一个实际的例子:

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>

#define RWRWRW (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)

int main(void)
{
    umask( 0 );
    if (creat("foo", RWRWRW) < 0)
        printf("creat error for foo\n");
    umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
    if (creat("bar", RWRWRW ) < 0)
        printf("creat error for bar\n");
    return 0;
}

终端输出:

leicj@leicj:~/test$ ./a.out
leicj@leicj:~/test$ ls -l foo bar
-rw------- 1 leicj leicj 0 12月 24 10:44 bar
-rw-rw-rw- 1 leicj leicj 0 12月 24 10:44 foo

chmod, fchmod和fchmodat函数

这三个函数用于改变文件的access权限:

#include <sys/stat.h>

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);

                                returns: 成功返回0, 失败返回-1

而mode取值以下:

mode 含义

S_ISUID

S_ISGID

S_ISVTX

set-user-ID on execution

set-group-ID on execution

saved-text(sticky bit)

S_IRWXU

S_IRUSR

S_IWUSR

S_IXUSR

用户可续,可写, 可执行

用户可读

用户可写

用户可执行

S_IRWXG

S_IRGRP

S_IWGRP

S_IXGRP

分组可读,可写,可执行

分组可读

分组可写

分组可执行

S_IRWXO

S_IROTH

S_IWOTH

S_IXOTH

其它可读,可写,可执行

其它可读

其它可写

其它可执行

例子: 修改文件的访问权限

#include <stdio.h>
#include <sys/stat.h>

int main(void)
{
    struct stat statbuf;

    if (stat("foo", &statbuf) < 0)
        printf("stat error for foo\n");
    if (chmod("foo", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0)
        printf("chmod error for foo\n");
    if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH ) < 0)
        printf("chmod error for bar\n");
    return 0;
}

程序输出:

leicj@leicj:~/test$ ./a.out
leicj@leicj:~/test$ ls -l foo bar
-rw-r--r-- 1 leicj leicj 0 12月 24 10:44 bar
-rw-rwSrw- 1 leicj leicj 0 12月 24 10:44 foo

chown, fchown,fchownat和lchown函数

chown函数用于改变文件的用户id和分组id, 若是owner或group参数有一个为-1, 则修改失效.

#include <unistd.h>

int chown(conat char *pathname, uid_t owner, gid_t group);

int fchown(int fd, uid_t owner, gid_t group);

int fchownat(int fd, const char *pathname, uid_t owner, gid_t group);

int lchown(const char *pathname, uid_t owner, gid_t group);

                        returns: 成功返回0, 失败返回-1

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
int main(void)
{
    struct stat buf;
    if (stat("foo", &buf) < 0) {
        printf("stat error\n");
        return 1;
    }
    printf("uid is:%d, gid is:%d\n", buf.st_uid, buf.st_gid);
    if (chown("foo", 0, 0) < 0) {
        printf("chown error\n");
        return 1;
    }
    if (stat("foo", &buf) < 0) {
        printf("stat error\n");
        return 1;
    }
    printf("now uid is:%d, gid is:%d\n", buf.st_uid, buf.st_gid);

    return 0;
}

终端输出:

leicj@leicj:~/test$ sudo ./a.out
uid is:1000, gid is:1000
now uid is:0, gid is:0

 

文件大小

stat结构中的st_size表明文件的大小, 文件大小只对普通文件, 目录和符号连接文件有效.

对于符号连接来讲, 文件大小为所连接文件名的长度.

leicj@leicj:~/test$ ls -l ttt test.js
-rwxrwxrwx 1 leicj leicj 566 12月 19 14:21 test.js
lrwxrwxrwx 1 leicj leicj   7 12月 24 12:58 ttt -> test.js

假设咱们使用lseek人为的制造空洞文件:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>

int main(void)
{
    int fd;
    char buf[] = "123";
    if ((fd = open("core", O_RDWR | O_CREAT)) < 0)
        printf("error");
    write(fd, buf, 3);
    lseek(fd, 10, SEEK_SET);
    write(fd, buf, 3);
    close(fd);
    return 0;
}

当咱们使用cat查看core文件时候, 终端输出:

123123

执行命令查看:

leicj@leicj:~/test$ ls -l core
-rwxrwxrwx 1 leicj leicj 13 12月 24 13:05 core

文件截断

某种状况下, 咱们但愿截断文件的数据, 则可使用truncate函数:

#include <unistd.h>

int truncate(const char *pathname, off_t length);

int ftruncate(int fd, off_t length);

                    returns: 成功返回0, 失败返回-1

文件系统

1. 两个目录入口可能指向相同的i-node入口. 每一个i-node都存在一个统计多少个目录指向它的变量. 只有此变量等于0, 才肯定文件被删除.

这就是为何unlink一个文件并不意味着删除文件. 在stat结构中, 存在st_nlink属性.

2. 符号连接仅仅只是一个指针, 指向具体的文件, 因此它的长度是具体文件名的长度.

leicj@leicj:~/test$ ll ttt
lrwxrwxrwx 1 leicj leicj 7 12月 24 12:58 ttt -> test.js*

3. i-node节点包含一个文件的所有信息: 文件类型, 文件access权限, 文件大小, 指向数据的指针等等. 绝大部分的信息都在stat结构中. 仅仅只有两项存在于目录入口: 文件名和i-node数量.

4. 目录中i-node number指向i-node, 因此对于ln命令来讲, 没法跨域文件系统执行.

5. 重命名一个文件并不改变文件系统, 仅仅只是增长一个目录入口指向已存在的i-node, unlink旧的目录入口.

当咱们执行: mkdir testdir时以下:

link, linkat, unlink, unlinkat和remove函数

使用link建立多个目录入口, 指向相同的i-node:

#include <unistd.h>

int link(const char *existingpath, const char *newpath);

int linkat(int efd, const char *existingpath, int nfd, const char *newpath, int flag);

                        returns: 成功返回0, 失败返回-1

int unlink(const char *pathname);

int unlinkat(int fd, const char *pathname, int flag);

                        returns: 成功返回0, 失败返回-1

考虑以下的代码:

#include <stdio.h>
#include <fcntl.h>

int main(void)
{
    if (open("tempfile", O_RDWR) < 0)
        printf("open error\n");
    if (unlink( "tempfile") < 0)
        printf("unlink error\n");
    printf("file unlinked\n");
    sleep(30);
    printf("done\n");
    return 0;
}

正常状况来讲, 应该进程彻底退出后系统才统计空间大小. 但如今的结果(Ubuntu16)跟以前的不同(Ubuntu14):

leicj@leicj:~/test$ ls -l tempfile
-rw-rw-r-- 1 leicj leicj 11 12月 24 14:45 tempfile
leicj@leicj:~/test$ df /home
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/sda6      856811692 8678348 804586720   2% /home
leicj@leicj:~/test$ ./a.out &
[3] 6967
leicj@leicj:~/test$ file unlinked
df /home
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/sda6      856811692 8678352 804586716   2% /home
leicj@leicj:~/test$ done
df /home
[3]   Done                    ./a.out
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/sda6      856811692 8678352 804586716   2% /home

也和书上说的不同(以前在Ubuntu14上测试跟书上同样).

rename和renameat函数

一个文件或目录能够经过rename进行重命名.

#include <stdio.h>

int rename(const char *oldname, const char *newname);

int renameat(int oldfd, const char *oldname, int newfd, const char *newname);

                            returns: 成功返回0, 失败返回-1

基于连接是目录, 文件仍是符号连接, 具备不一样的结果:

1. 若是oldname是文件或符号连接. 若是newname已经存在, 则它不能指向目录. newname存在且非目录状况下, 它将被删除且oldname重命名为newname, 而且newname的权限要跟oldname同样.

2. 在oldname为目录状况下, 若是newname已经存在则它必须为目录并且目录必须为空.

3. 若是oldname或者newname为符号连接, 则执行连接便可.

4. 不能对"."和".."进行重命名.

5. 若是oldname和newname为同一文件, 则没作任何改变.

符号连接

leicj@leicj:~/test$ ln -s /no/such/file myfile
leicj@leicj:~/test$ ls -l myfile
lrwxrwxrwx 1 leicj leicj 13 12月 24 15:40 myfile -> /no/such/file
leicj@leicj:~/test$ cat myfile
cat: myfile: No such file or directory

建立和读取符号连接

可使用symlink建立符号连接:

#include <unistd.h>

int symlink(const char *actualpath, const char *sympath);

int symlinkat(const char *actualpath, int fd, const char *sympath);

                            returns: 成功返回0, 失败返回-1

mkdir, mkdirat和rmdir函数

使用mkdir建立目录, 使用rmdir删除目录:

#include <sys/stat.h>

int mkdir(const char *pathname, mode_t mode);

int mkdirat(int fd, const char *pathname, mode_t mode);

                        returns: 成功返回0, 失败返回-1

#include <unistd.h>

int rmdir(const char *pathname);

                        returns: 成功返回0, 失败返回-1

例子: 遍历一个目录

#include <stdio.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
static long nreg, ndir, nblk, nchr, nfifo, nslink, nsock, ntot;

void myftw( char *, char * );

int main( int argc, char *argv[] )
{
	if ( argc != 2 ){
		printf("input error\n");
		return 1;
	}
	myftw( "", argv[ 1 ] );
	ntot = nreg + ndir + nblk + nchr + nfifo + nslink + nsock;
	if ( 0 == ntot ){
		ntot = 1;
	}
	printf("regular files=%7ld, %5.2f%%\n", nreg, nreg * 100.0 / ntot );
	printf("directory=%7ld, %5.2f%%\n", ndir, ndir * 100.0 / ntot );
	printf("block special=%7ld, %5.2f%%\n", nblk, nblk * 100.0 / ntot );
	printf("char special=%7ld, %5.2f%%\n", nchr, nchr * 100.0 / ntot );
	printf("FIFOs=%7ld, %5.2f%%\n", nfifo, nfifo * 100.0 / ntot );
	printf("symbolic links=%7ld, %5.2f%%\n", nslink, nslink * 100.0 / ntot );
	printf("sockets=%7ld, %5.2f%%\n", nsock, nsock * 100.0 / ntot );

	return 0;
}

void myftw( char *curPath, char *path )
{
	struct stat statbuf;
	DIR	*dp;
	struct dirent *dirp;
	char	fullPath[ 256 ];
	
	if ( strcmp( curPath, "" ) != 0 ){
		strncpy( fullPath, curPath, strlen( curPath ) );
		fullPath[ strlen( curPath ) ] = '/';
		strncpy( fullPath + strlen( curPath ) + 1, path, strlen( path ) );
		fullPath[ strlen( curPath ) + strlen( path ) + 1 ] = '\0';
	}
	else{
		strncpy( fullPath, path, strlen( path ) );
		fullPath[ strlen( path ) ] = '\0';
	}
	if ( lstat( fullPath, &statbuf ) < 0 ){
		printf("lstat error:%s\n", fullPath);
		return;
	}
	if ( S_ISREG( statbuf.st_mode ) )
		nreg++;
	else if ( S_ISDIR( statbuf.st_mode ) ){
		ndir++;
		if ( ( dp = opendir( fullPath ) ) == NULL ){
			printf("can't open %s\n", fullPath );
			return;
		}
		while ( ( dirp = readdir( dp ) ) != NULL ){
			if ( strcmp( dirp->d_name, "." ) == 0 ||
			     strcmp( dirp->d_name, ".." ) == 0 ){
				continue;
			}
			myftw( fullPath, dirp->d_name );
		}
	}
	else if ( S_ISCHR( statbuf.st_mode ) )
		nchr++;
	else if ( S_ISBLK( statbuf.st_mode ) )
		nblk++;
	else if ( S_ISFIFO( statbuf.st_mode ) )
		nfifo++;
	else if ( S_ISLNK( statbuf.st_mode ) )
		nslink++;
	else if ( S_ISSOCK( statbuf.st_mode ) )
		nsock++;
	else
		printf("file error\n");
}

程序输出:

leicj@leicj:~/test$ ./a.out /dev
regular files=     11,  1.88%
directory=     33,  5.65%
block special=     31,  5.31%
char special=    234, 40.07%
FIFOs=      0,  0.00%
symbolic links=    275, 47.09%
sockets=      0,  0.00%
相关文章
相关标签/搜索