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%
相關文章
相關標籤/搜索