APUE學習筆記--文件系統(stat、fstat、lstat、空洞文件、讀取目錄內容、實現自己的du)

  • stat、fstat、lstat

    • 函數定義

      #include <sys/types.h>
      #include <sys/stat.h>
      #include <unistd.h>

      int stat(const char *path, struct stat *buf);
      int fstat(int fd, struct stat *buf);
      int lstat(const char *path, struct stat *buf);

    • 功能描述
      • 這三個函數都通過buf返回一個包含文件詳細信息的結構體。
      • 這些文件本身不需要權限,但是這些函數必須擁有在path指向的文件所在的文件夾下的執行權限。
      • stat:通過文件路徑獲取屬性。面對符號鏈接時,獲取的是符號鏈接指向的文件的屬性。
      • fstat:同stat,只是通過文件描述符獲取屬性。
      • lstat:面對符號鏈接時,獲取的是符號鏈接的屬性。
    • 參數解析
      • struct stat 結構體:

        struct stat {
        dev_t st_dev; /* 包含改文件的設備id號 /
        ino_t st_ino; /
        i節點號 /
        mode_t st_mode; /
        protection /
        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) /設備id號
        off_t st_size; /
        total size, in bytes /
        blksize_t st_blksize; /
        blocksize for file system I/O /文件系統IO的塊大小
        blkcnt_t st_blocks; /
        number of 512B blocks allocated /塊數
        time_t st_atime; /
        time of last access /最後一次訪問時間
        time_t st_mtime; /
        time of last modification /最後一次修改文件內容時間
        time_t st_ctime; /
        time of last status change */最後一次修改inode 信息的時間。(i.e., owner, group, link count, mode, etc.).

        };

      • st_mode
        st_mode是一個十六位的整形數,以位圖的方式存儲了文件類型、文件權限、特殊權限位。

        • 文件類型:
          • d: 目錄文件
          • c:字符設備文件
          • b: 二進制文件
          • -: 常規文件
          • l: 符號鏈接文件
          • s: 網絡socket文件
          • p: 匿名管道文件
        • 文件權限(也就是三類用戶的rwx權限)

        • 判斷一個文件的類型有兩種辦法:

          • 利用宏定義的函數判斷。爲真返回1;爲假返回0。
            S_ISREG(m) //is it a regular file?

            S_ISDIR(m) //directory?

            S_ISCHR(m) //character device?

            S_ISBLK(m) //block device?

            S_ISFIFO(m)//FIFO (named pipe)?
            S_ISLNK(m) //symbolic link? (Not in POSIX.1-1996.)

            S_ISSOCK(m)//socket? (Not in POSIX.1-1996.)

          • 利用位運算,得到結果。
            S_IFMT 0170000 //bit mask for the file type bit fields
            S_IFSOCK 0140000 // socket
            S_IFLNK 0120000 //symbolic link
            S_IFREG 0100000 // regular file
            S_IFBLK 0060000 //block device
            S_IFDIR 0040000 //directory
            S_IFCHR 0020000 //character device
            S_IFIFO 0010000 //FIFO
            S_ISUID 0004000 //set UID bit
            S_ISGID 0002000 //set-group-ID bit (see below)
            S_ISVTX 0001000 //sticky bit (see below)
            S_IRWXU 00700 //mask for file owner permissions
            S_IRUSR 00400 //owner has read permission
            S_IWUSR 00200 //owner has write permission
            S_IXUSR 00100 //owner has execute permission
            S_IRWXG 00070 //mask for group permissions
            S_IRGRP 00040 // group has read permission
            S_IWGRP 00020 //group has write permission
            S_IXGRP 00010 //group has execute permission
            S_IRWXO 00007 // mask for permissions for others (not in group)
            S_IROTH 00004 //others have read permission
            S_IWOTH 00002 // others have write permission
            S_IXOTH 00001 //others have execute permission
            如上所示,可以利用bit mask等得到想要的文件類型信息、文件權限信息、特殊位信息。(見下文中的例子。)

      • 返回值
        • 正常返回0,失敗返回-1,並設置對應的errno。
        • 通過buf返回該文件的詳細信息。
      • 例子
#include <sys/types.h>
       #include <sys/stat.h>
       #include <time.h>
       #include <stdio.h>
       #include <stdlib.h>

       int
       main(int argc, char *argv[])
       {
           struct stat sb;

           if (argc != 2) {
               fprintf(stderr, "Usage: %s <pathname>\n", argv[0]);
               exit(EXIT_FAILURE);
           }

           if (stat(argv[1], &sb) == -1) {
               perror("stat");
               exit(EXIT_SUCCESS);
           }

           printf("File type:                ");

           switch (sb.st_mode & S_IFMT) {
             case S_IFBLK:  printf("block device\n");            break;
           case S_IFCHR:  printf("character device\n");        break;
           case S_IFDIR:  printf("directory\n");               break;
           case S_IFIFO:  printf("FIFO/pipe\n");               break;
           case S_IFLNK:  printf("symlink\n");                 break;
           case S_IFREG:  printf("regular file\n");            break;
           case S_IFSOCK: printf("socket\n");                  break;
           default:       printf("unknown?\n");                break;
           }

           printf("I-node number:            %ld\n", (long) sb.st_ino);

           printf("Mode:                     %lo (octal)\n",
                   (unsigned long) sb.st_mode);

           printf("Link count:               %ld\n", (long) sb.st_nlink);
           printf("Ownership:                UID=%ld   GID=%ld\n",
                   (long) sb.st_uid, (long) sb.st_gid);

           printf("Preferred I/O block size: %ld bytes\n",
                   (long) sb.st_blksize);
           printf("File size:                %lld bytes\n",
                   (long long) sb.st_size);
           printf("Blocks allocated:         %lld\n",
                   (long long) sb.st_blocks);

           printf("Last status change:       %s", ctime(&sb.st_ctime));
           printf("Last file access:         %s", ctime(&sb.st_atime));
           printf("Last file modification:   %s", ctime(&sb.st_mtime));

           exit(EXIT_SUCCESS);
       }

  • 空洞文件

    空洞文件介紹.
    我們可以利用lseek構造一個包含空洞的文件。原本想構造5G的,結果我用的系統,long類型也是32位,只好改爲5MB。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc,char ** argv)
{
	int fd;
	printf("long SIZE :%d\n",sizeof(long));
	if(argc<2)
	{
		fprintf(stderr,"Usage....\n");
		exit(EXIT_FAILURE);
	}
	
	fd = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0600);
	if(fd==-1)
	{
		perror("open failed!\n");
		exit(EXIT_FAILURE);
	}
	if(lseek(fd,5LL*1024LL*1024LL-1LL,SEEK_SET)==-1)
	{
		perror("lseek()");
		exit(EXIT_FAILURE);
	}
	if(write(fd,"",1)==-1)
	{
		perror("write()");
		exit(EXIT_FAILURE);
	}
	exit(EXIT_SUCCESS);
}

執行後得到一個空洞文件。
在這裏插入圖片描述
從上圖可以注意到如下幾點:
1. 文件size爲5242800B,而塊數只有8,也就是文件真實分配的磁盤大小隻有4KB。
2. 使用cp命令後,得到的備份文件,磁盤塊數爲0。

  • 分析目錄/讀取目錄內容

    • 方法一:glob
      • 函數定義

        #include <glob.h>

        int glob(const char *pattern, int flags,
        int (*errfunc) (const char *epath, int eerrno),
        glob_t *pglob);

        void globfree(glob_t *pglob);//釋放glob申請的內存

      • 函數功能
        • 與pattern模式匹配符合的所有文件都存放在pglob中。
        • flags指定了多種執行模式。不指定模式,就設置爲0。
        • errfunc用於獲得函數執行出錯的具體信息。不需要出錯信息時,設置爲NULL。
        • pglob用於存儲模式匹配得到的信息。
          • typedef struct {
            size_t gl_pathc; /* Count of paths matched so far */
            char *gl_pathv; / List of matched pathnames. /
            size_t gl_offs; /
            Slots to reserve in gl_pathv. */
            } glob_t;
      • 注意事項
        • gl_pathv與main函數中的argv有相似之處。如argc爲1時,則argv[1]==NULL。所以可用pglob.gl_pathv[i]==NULL判別讀取完畢。
      • 返回值
        • 執行成功返回0。否則有如下情況:
          GLOB_NOSPACE
          // for running out of memory,

          GLOB_ABORTED
          //for a read error, and

          GLOB_NOMATCH
          // for no found matches.

      • 例子
#include <stdlib.h>
#include <stdio.h>
#include <glob.h>


#define PAT "*.c"

int main(int argc,char ** argv)
{
	glob_t pglob;
	int i;
	if(glob(PAT,0,NULL,&pglob))
	{
		fprintf(stderr,"glob error\n");
		exit(EXIT_FAILURE);
	}
	for(i=0;pglob.gl_pathv[i];i++)
	{
		puts(pglob.gl_pathv[i]);
	}
	exit(EXIT_SUCCESS);
}

  • 方法二:通過opendir、readdir、closedir(在man手冊的第三章)
    • opendir:

      • DIR *opendir(const char *name);
        DIR *fdopendir(int fd);
        • opendir通過目錄名字打開一個目錄流,返回一個目錄流指針。指針指向目錄流的起始位置。
        • fopendir與opendir一樣,只是是從一個已經打開的文件描述符讀取信息。
        • 執行成功返回目錄流,否則返回NULL,並設置errno。
    • readdir

      • #include <dirent.h>

        struct dirent *readdir(DIR *dirp);

        int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);

      • readdir通過目錄流drip返回一個dirent結構體指針,指向下一個目錄項位置。

      • 當讀到目錄流結束位置或者出現錯誤,返回NULL。出現錯誤時,設置errno。

      • dirent的定義如下:
        struct dirent {
        ino_t d_ino; /* inode number /
        off_t d_off; /
        offset to the next dirent /
        unsigned short d_reclen; /
        length of this record /
        unsigned char d_type; /
        type of file; not supported
        by all file system types /
        char d_name[256]; /
        filename */
        };

    • closedir

      • #include <sys/types.h>

        #include <dirent.h>

        int closedir(DIR *dirp);

      • 關閉drip對應的目錄流。執行成功的話,也會關閉目錄流drip所使用的的文件描述符fd。

      • 執行成功返回0;否則返回-1,並設置errno。

    • 例子

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#define PAT "."
int main()
{
	DIR * tmp;
	struct dirent * cur;
	if((tmp=opendir(PAT))==NULL)
	{
		perror("opendir()");
		exit(EXIT_FAILURE);
	}
	
	while((cur=readdir(tmp)) != NULL)
	{
		puts(cur->d_name);
	}
	closedir(tmp);	
	exit(EXIT_SUCCESS);
}

  • 實現自己的du

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <glob.h>


#define PATHSIZE 1024
//排除每個目錄中存在的 . 和 .. 目錄文件
static int path_noloop(const char * path)
{
	char * pos;
	pos = strrchr(path,'/');//尋找最後一個'/'
	if(pos == NULL)
		exit(1);	
	if(strcmp(pos+1,".") == 0 || strcmp(pos+1,"..")==0 )	
		return 0;
	return 1;
}

static int64_t mydu(const char * path)
{
	char pathname[PATHSIZE];
	struct stat buf;
	glob_t pglob;
	int64_t sum;
	int i;
	int flag;
	
	if(lstat(path,&buf) == -1)
	{
		perror("lstat()");
		exit(EXIT_FAILURE);
	}
	
	if(!S_ISDIR(buf.st_mode))
		return buf.st_blocks;
	else
	{
		strncpy(pathname,path,PATHSIZE);
		strncat(pathname,"/*",PATHSIZE);
		flag = glob(pathname,0,NULL,&pglob);
		if(flag == GLOB_NOSPACE || flag  == GLOB_ABORTED)
		{
			fprintf(stderr,"/* glob err!");
			exit(EXIT_FAILURE);
		}

		strncpy(pathname,path,PATHSIZE);
		strncat(pathname,"/.*",PATHSIZE);
		flag = glob(pathname,GLOB_APPEND,NULL,&pglob);//這裏使用追加模式。把隱藏文件名加入pglob中。
		if(flag == GLOB_NOSPACE || flag == GLOB_ABORTED)
		{
			fprintf(stderr,"/.* glob err!\n");
			exit(EXIT_FAILURE);
		}
		
		sum= buf.st_blocks;//目錄文件本身的大小也得加上
		for(i = 0;i<pglob.gl_pathc;i++)
		{
			if(path_noloop(pglob.gl_pathv[i]))
				sum += mydu(pglob.gl_pathv[i]);
		}	
		
		printf("%-8lld   %s\n",sum/2,path);
	}
	globfree(pglob);//一定要記得釋放glob申請的堆空間
	return sum;
}

int main(int argc,char ** argv)
{
	if(argc!=2)
	{
		fprintf(stderr,"Usage------\n");
		exit(EXIT_FAILURE);
	}
	printf("%lld\n",mydu(argv[1])/2);
	exit(EXIT_SUCCESS);
}

  • 注意事項
    • 注意這裏使用的int64_t類型,也就是long long 類型。
    • 遞歸的規程中,必須排除掉. 和 … 的干擾。
    • strrchr函數的使用
    • 遞歸函數能進行一些優化。
      • pathname、buf、flag都只在遞歸前使用,因此可以把他們定義爲static,減少每次遞歸時,棧空間的消耗。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章