Bob Cheng
System Programming

File Properties

File Structure

Unix 裡面用來儲存檔案屬性的結構:

struct stat {
	mode_t			    st_mode;  	/* file type & mode (permissions) */
	ino_t			      st_ino;	  	/* i-node number (serial number) */
	dev_t			      st_dev;	  	/* device number (file system) */
	dev_t			      st_rdev;	  /* device number for special files */
	nlink_t			    st_nlink;  	/* number of links */
	uid_t			      st_uid;		  /* user ID of owner */
	gid_t			      st_gid;		  /* group ID of owner */
	off_t			      st_size;	  /* size in bytes, for regular files */
	struct timespec	st_atim;	  /* time of last access */
	struct timespec	st_mtim;	  /* time of last modification */
	struct timespec	st_ctim;	  /* time of last file status change */
	blksize_t		    st_blksize;	/* best I/O block size */
	blkcnt_t		    st_blocks;	/* number of disk blocks allocated */
}

獲取檔案屬性 : stat(), fstat(), lstat()

// all returns 0 if OK, -1 on error
int stat(const char *pathname, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *pathname, struct stat *buf)

三個函數皆會將指定檔案的屬性結構 (struct stat) 填入 buf 裡面
stat()lstat() 的差別 : 如果 pathname 是 symbolic link, lstat() 會返回 link 的屬性, stat() 會返回 link 指向的 file 的屬性


File Types

一個檔案的 file type 被儲存在 st_mode 裡面

  • Regular Files: text, binary, etc. Unix kernel does not distinguish if the file is a text or binary — the application decides how to interpret
  • Directory Files: a file that contains the name of other files and pointers to the info of these files
  • Block Special Files: ex: disk
  • Character Special Files: ex: tty, audio
  • FIFO: named pipes
  • Sockets: the type of file used for network communication between processes
  • Symbolic Links: the type of file that points to another file

可以將 st_mode 作為 macros 的參數

struct stat buf;
if (S_ISREG(buf.st_mode)){
	...
}

Permisson Bits

一個檔案的權限資訊被儲存在 st_mode 裡面,每個檔案都有 9 個 permission bits,如下

(user)-(group)-(other)
rwx-rwx-rwx
  • r (read) :
    • open an existing file for reading
    • obtain a list of all the filenames in the directory
  • w (write) :
    • open an existing file for writing
    • update the directory
  • x (execute) :
    • execute a file using one of the exec function
    • access the i-nodes of files in the directory
      • 要打開某個檔案,必須擁有其所有上層目錄的 X 權限
      • 要 create 或 delete 目錄裡的檔案,必須擁有該目錄的 W+X 權限

如果出現 - 代表沒有該權限,另外還有三種特殊權限

  • user xs (set-user-ID bit) :
    當一個檔案被 execute 時,將 process 的 effective user ID 設定成該檔案的 owner (st_uid)
    e.g. passwd is a set-user-ID program
  • group xs (set-group-ID bit) :
    當一個檔案被 execute 時,將 process 的 effective group ID 設定成該檔案的 owner (st_gid)
  • other xt (sticky bit) : 如果一個目錄被設定了 sticky bit,那要 removed 或 renamed 目錄內的檔案必須符合以下條件:
    user has w for directory AND ( user owns the file OR user owns the directory OR user is superuser )
    e.g. /tmp allows users to create files in /tmp but can’t removed or renamed files owned by others

permission bits 一般會用數字來表示,舉例來說,4755 = rwsr-xr-x


IDs of User and Group

每個 User/Group 都有三種 ID :

  • Real User/Group ID (UID and GID)
    • who you are
    • /etc/passwd maps user names to IDs
    • used for login
  • Effective User/Group ID, Supplementay Group IDs
    • used for processes when file access permission checks
    • 當一個 process 想要 execute program/file 時 process’ effective user/group ID == real user/group ID (在沒有 set-user/group-ID bit 的情況下)
  • Saved Set-User/Group ID
    • contain copies of effective user ID and effective group ID, respectively
    • exec() 設定

File Access Test

每當一個 process 想要 execute、create、open、delete 一個檔案,kernel 就會對其做 File Access Test,從 1 檢查到 4,只要有其中一個符合就通過:

  1. If effective UID== 0 (superuser)
  2. If effective UID ==st_uid (owner of file)
  3. If effective GID or one of process’ supplementary GID == st_gid (owner group of file)
  4. kernel performs the accesss permission check for other

檢查使用者的檔案存取權 : access()

// returns 0 if OK, -1 on error
int access(const char *pathname, int mode);

一個 process 可以用 access() 來確認 real user/group 是否有該檔案的存取權 mode :

  • R_OK: test for read permission
  • W_OK: test for write permission
  • X_OK: test for execute permission

Ownership of a New File

當一個新檔案被 open()create() 建立時:
owner user ID (st_uid) : effective UID of the process
owner group ID (st_gid) : effective GID of the process 或 GID of file’s parent directory

當 process 建立新檔案時阻擋特定權限 : umask()

// returns previous file mode creation mask
mode_t umask(mode_t cmask);

每個 process 都有一個 file mode creation mask,當 process 要建立新的檔案/目錄時,這時 mask 會剝奪其部分權限

  • process 的 mask 預設來自於其 parent process
  • 更改一個 process 的 mask 不會影響其他 process

example :
一個 process 的 mask 為 0022
想要建立檔案權限為 0777
則最後結果為 0777 - 0022 = 0755

cmask 由三個數字組成,意義如下 :

更改檔案權限 : chmod(), fchmod()

// returns 0 if OK, -1 on error
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);

用法就不贅述,mode 就是那三個數字

使用資格 : process 的權限必須是 superuser 或是 effective ID == st_uid

更改檔案擁有者 : chown(), fchown(), lchown()

// returns 0 if OK, -1 on error
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);

如果 ownergroup 是 -1,則該對應 ID 不會被更改

使用資格 :

  • only a privileged process (with the CAP_CHOWN capability) can change file owner
  • for non-privileged process, file owner 可以將 group 改成 owner 所在的任一個 group ; for privileged process, 可以改成任何 group

File Size

https://blog.csdn.net/zhoulaowu/article/details/14156801

  1. st_size 代表該檔案的大小 (in bytes)

    • regular file : 0 ~ MAX (in off_t)
    • directory : multiples of 16 or 512
    • symbolic link : the length of the pathname
    • other : 沒有任何意義
  2. st_blocks 代表該檔案占用的 block 數量
    st_blksize 代表該檔案的 block 單位大小
    一個 block 的大小代表系統在給檔案分配空間時的最小單位,一般為 512-byte unit

所以一個檔案的實際大小為 st_size,但實際占用的空間為 st_blksize * st_blocks

更改檔案大小 : truncate(), ftruncate()

// returns 0 if OK, -1 on error
int truncate(const char *pathname, off_t length);
int ftruncte(int fd, off_t length);

length : 截斷後的大小

  • 如果 previous size > length : 超過 length 的部分會消失
  • 如果 previous size < length : 在不足的部分會補上 0

File System

File : 一段 bytes
Directory : 一個檔案包含如何找到其他檔案的資訊

有許多不同的 Unix File System : BSD-based Unix file system (UFS), Hierarchical File System (HFS: classic file system for MacOS), ZFS, Ext4, etc.

UFS File System


i-node and data blocks

i-node : 一個固定大小的 entry,包含一個檔案的所有資訊

  • size : Unix System V7 : 64B, 4.3+BSD : 128B, UFS: 128B, etc.
  • an i-node includes file type, access permission bits, pointers to file’s data blocks, the link count, etc.

i-node link count :

  • 指向該 i-node 的 directory entries 數量 (hard link 數量)
  • i-node 指向的檔案被刪除代表 i-node link count = 0
  • 被包含在 st_nlink 裡面
  • POSIX.1 constant LINK_MAX : 一個檔案最多能有多少 link

directory entry : 可以看成一個 structure,裡面包含 filename 還有 i-node 的位置

在同一個 file system 底下,要移動一個檔案只需要更改相關的 directory blocks : 新增一個 directory entry 指向檔案的 i-node,將舊的刪除

  • 一個 directory entry 指向某個檔案的 i-node,可以看成該檔案的另一個名稱
  • file type : none
  • 缺點
    • 不能連結到 directory
    • 只能在同一種 file system 下使用

建立 hard link:

ln [option] [file] [link]
  • create link points to file
  • create hard link by default
  • option : -s for symbolic link
  • 一個實際的檔案,裡面包含另一個檔案的 pathname,可以看成 Windows 系統裡的「捷徑」
  • file type : S_IFLINK
  • 為了解決 hard link 的缺點被提出
    • 可以連結到 directory
    • 可以在不同 file system 下使用

關於 pathname :
如果 symbolic link 裡面的 pathname 是 relative path,那麼是相對於 link 所在目錄

link = "system/program/is"
/usr/tanimal/link/suck -> /usr/tanimal/system/program/is/suck

關於使用 filename 為參數的函數 :

  • function follows symbolic link : 函數參考 symbolic link 內的 pathname 所代表的檔案
  • function doesn’t follow symbolic link : 函數參考 symbolic link 本身

File Sharing

hard link : each directory entry creates a hard link to the i-node of the shared` file
symbolic link : create a file that contains the pathname of the file that the symbolic link points to

// returns 0 if OK, -1 on error
int link(const char *existingpath, const char *newpath);

建立一個新的 hard link newpath,指向一個檔案 existingpath

  • 建立一個新的 directory entry 指向 existingpath 的 i-node
  • existingpath 的 i-node link count +1
  • return ==error== if newpath already exist
// returns 0 if OK, -1 on error
int symlink(const char *actualpath, const char *sympath);

建立 symbolic link sympath,連接到一個檔案 actualpath

  • actualpath 不一定要存在
  • sympath actualpath 可以在不同 file system 下
// returns 0 if OK, -1 on error
int unlink(const char *pathname);

刪除一個 hard link pathname

  • 刪除該 directory entry
  • 將該 directory entry 指向的 i-node link count -1
  • 如果 pathname 是指向該檔案的最後一個 link,而且當前沒有 process 打開該檔案,那麼該檔案就會被刪除
  • 如果 pathname 是 symbolic link,會移除該 symbolic link,對應的檔案不會被影響
// returns the number of bytes read if OK, -1 on error
int readlink(const char *restrict pathname, char *restrict buf, size_t bufsize);

由於 open() 會打開 symbolic link 所連結的檔案,因此我們需要另一個函數來讀取 symbolic link 本身的內容
readlink() 將 symbolic link pathname 的內容讀取到 bufsize 大小的 buf 裡面

  • readlink() 會將讀取的內容 truncate 成 bufsize 大小,避免 buf 裝不下
  • 讀取的內容不會加上 ==\0== 結尾

重新命名檔案 : rename()

// returns 0 if OK, -1 on error
int rename(const char *oldname, const char *newname);

為 file 或 directory 重新命名

  • 該 file 的其他 hard links 不受影響
  • 該 file 已經打開的 fds 不受影響

File Times

st-atim : last-access time

  • 最後一次該檔案被 read()/write() 的時間
  • Unix 不會記錄 i-node 的 last-access time : access()stat() 不會更改 last-access time

st_mtim : last-modification time

  • 最後一次該檔案內容被更改的時間

st_ctim : last-changed-status time

  • 最後一次該檔案的 i-node 被更改的時間
  • 有許多操作只會改變 i-node 而不改變檔案內容 ex : change file access permissions, user ID, etc.

Linux 指令

ls -l 查看 mtim, ls -lc 查看 ctim, ls -lu 查看 atim

Effect of various functions on the access, modification, and changed-status times

更改檔案時間 : utime(), utimes()

time_t: number of seconds since 00:00:00 UTC on 1970/1/1

// returns 0 if OK, -1 on error
int utime(const char *pathname, const struct utimbuf *times);
int utimes(const char *pathname, const struct timeval times[2]);

utime() 會將檔案 pathname 的 access 和 modification time 分別更改成 times.actimetimes.modtime

struct utimbuf {
	time_t actime;
	time_t modtime;
}

utimes() 會將檔案 pathname 的 access 和 modification time 分別更改成 times[0]times[1]

struct timeval {
	time_t tv_sec;
	long tv_usec;
}

Year 2038 Problem

On most 32-bit system, time_t 被儲存在 signed 32-bit interger 內
也就是說在 03:14:07 UTC on 2038/1/19 過後,時間會被重置成 20:45:52 UTC on 1901/12/13


Directory

建立或刪除目錄 : mkdir(), rmdir()

// returns 0 if OK, -1 on error
int mkdir(const char *pathname, mode_t mode);
int rmdir(const char *pathname);
  • mkdir() 建立一個全新的空目錄 pathname (內建 . ..)
    • mode 用來設定目錄權限 (open(), openat())
  • 如果符合以下所有條件,rmdir() 會刪除一個空目錄 pathname :
    • 目錄的 link count 在此命令執行後變為 0
    • 沒有其他 process 正在打開這個目錄
    • 如果目錄 pathname 不是空的,或是有其他的 hard links (除了 . ..) 連接到該目錄,rmdir() 會失敗

讀取目錄 : opendir(), readdir(), rewinddir(), closedir()

// returns pointer if OK, -1 on error
// DIR: directory stream, elements are directory entries
DIR *opendir(const char *pathname);

// returns pointer if OK, NULL at the end of the directory on error
struct dirent *readdir(DIR *dp);

// returns 0 if OK, -1 on error
struct dirent *rewinddir(DIR *dp);
struct dirent *closedir(DIR *dp);
struct dirent {
	ino_t  d_ion;                /* i-node number */
	char   d_name[NAME_MAX+1];   /* null-terminated filename */
}

example :

/*
* Use directory routines to traverse a file hierarchy.
* Also, produce a count of the various types of files
*/
#include "apue.h"
#include <dirent.h>
#include <limits.h>

/* function type that is called for each filename */
typedef int Myfunc(const char *, const struct stat *, int);
static Myfunc myfunc;
static int myftw(char *, Myfunc *);
static int dopath(Myfunc *);
static long nreg, ndir, nblk, nchr, nfifo, nslink, nsock, ntot;

int main(int argc, char *argv[]){
	int ret;
	if (argc != 2)
		err_quit("usage: ftw <starting-pathname>");

	ret = myftw(argv[1], myfunc); /* does it all */
	ntot = nreg + ndir + nblk + nchr + nfifo + nslink + nsock;

	if (ntot == 0) ntot = 1; /* avoid divide by 0; print 0 for all counts */

	printf("regular files = %7ld, %5.2f %%\n", nreg, nreg*100.0/ntot);
	printf("directories = %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);
	exit(ret);
}
/*
* Descend through the hierarchy, starting at "pathname".
* The caller’s func() is called for every file.
*/
#define FTW_F 1 /* file other than directory */
#define FTW_D 2 /* directory */
#define FTW_DNR 3 /* directory that can’t be read */
#define FTW_NS4 /* file that we can’t stat */
static char *fullpath; /* contains full pathname for every file */
static size_t pathlen;
static int /* we return whatever func() returns */

myftw(char *pathname, Myfunc *func){
	fullpath = path_alloc(&pathlen); /* malloc PATH_MAX+1 bytes */
	/* (Figure 2.16) */
	if (pathlen <= strlen(pathname)) {
		pathlen = strlen(pathname) * 2;
		if ((fullpath = realloc(fullpath, pathlen)) == NULL)
			err_sys("realloc failed");
	}
	strcpy(fullpath, pathname);
	return(dopath(func));
}
/*
* Descend through the hierarchy, starting at "fullpath".
* If "fullpath" is anything other than a directory, we lstat() it,
* call func(), and return. For a directory, we call ourself
* recursively for each name in the directory.
*/
static int /* we return whatever func() returns */

dopath(Myfunc* func){
	struct stat statbuf;
	struct dirent *dirp;
	DIR *dp;
	int ret, n;

	if (lstat(fullpath, &statbuf) < 0) /* stat error */
		return(func(fullpath, &statbuf, FTW_NS));
	if (S_ISDIR(statbuf.st_mode) == 0) /* not a directory */
		return(func(fullpath, &statbuf, FTW_F));
	/*
	* It’s a directory. First call func() for the directory,
	* then process each filename in the directory.
	*/
	if ((ret = func(fullpath, &statbuf, FTW_D)) != 0)
		return(ret);

	n = strlen(fullpath);

	if (n + NAME_MAX + 2 > pathlen) { /* expand path buffer */
		pathlen *= 2;
		if ((fullpath = realloc(fullpath, pathlen)) == NULL)
			err_sys("realloc failed");
	}
	fullpath[n++] =/’;
	fullpath[n] = 0;

	if ((dp = opendir(fullpath)) == NULL) /* can’t read directory */
		return(func(fullpath, &statbuf, FTW_DNR));

	while ((dirp = readdir(dp)) != NULL) {
		if (strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0)
			continue; /* ignore dot and dot-dot */
		strcpy(&fullpath[n], dirp->d_name); /* append name after "/" */
		if ((ret = dopath(func)) != 0) /* recursive */
			break; /* time to leave */
	}
	fullpath[n-1] = 0; /* erase everything from slash onward */

	if (closedir(dp) < 0)
		err_ret("can’t close directory %s", fullpath);
		return(ret);
}

static int myfunc(const char *pathname, const struct stat *statptr, int type){
	switch (type) {
	case FTW_F:
		switch (statptr->st_mode & S_IFMT) {
		case S_IFREG: nreg++; break;
		case S_IFBLK: nblk++; break;
		case S_IFCHR: nchr++; break;
		case S_IFIFO: nfifo++; break;
		case S_IFLNK: nslink++; break;
		case S_IFSOCK: nsock++; break;
		case S_IFDIR: /* directories should have type = FTW_D */
			err_dump("for S_IFDIR for %s", pathname);
		}
		break;
	case FTW_D:
		ndir++;
		break;
	case FTW_DNR:
		err_ret("can’t read directory %s", pathname);
		break;
	case FTW_NS:
		err_ret("stat error for %s", pathname);
		break;
	default:
		err_dump("unknown type %d for pathname %s", type, pathname);
	}
	return(0);
}

Working Directory

  • 每個 process 都有自己的 current working directory → relative path 從該目錄開始尋找
  • /etc/passwd 在每個用戶登入時決定該用戶的 working directory
    • 通常是該用戶的家目錄 ~

取得工作目錄 : getcwd()

// returns buf if OK, NULL on error
char *getcwd(char *buf, size_t size);

將 current working directory 儲存到 size 大小的 buffer buf 裡面

size 必須大於 (目錄的絕對路徑 + \0),否則會報錯

更改工作目錄 : chdir(), fchdir()

// returns 0 if OK, -1 on error
int chdir(const char *pathname);
int fchdir(int fd);

將新的 current working directory 指定成 pathname 或是 fd


Device Special File

In Unix-like operating systems, a device file or special file is an interface to a device driver that appears in a file system as if it were an ordinary file.

  • 簡單來講,使用者可以透過 device special file 來和 device driver 互動
  • 所謂的 device driver 是可以讓硬體和軟體互動的程式
  • linux 系統下的 device special file 被放置在 /dev 目錄底下

st_dev : device special file 所在的 file system
st_rdev : device special file 的類型,以及相關的 device driver

Unix 系統下,每個 device 都有一個獨特的 identifier:

  • 包含了 majorminor number (被儲存在 st_devst_rdev 內)
  • major number 用來識別 device 的類別、使用的 drivers
  • minor number 用來識別被 major number 指定的 driver 服務的每個 physical devices
  • 可以用 major(dev_t) minor(dev_t) 來從 dev_t 裡面獲取 majorminor number
  • 通常 device files 會有相同 major number 以及不同的 minor number

linux 系統把 devices 分成三種:

  • Block devices : 可以提供對大量資料 (blocks) 進行隨機存取的裝置;例如 hard drive, cdrom, etc. — 通常用來儲存檔案系統
  • Character devices : 可以提供對少量資料 (byte) 進行連續存取的裝置;例如 keyboard, mouse, serial ports, etc.
  • Network devices : 用戶使用 networking subsystem 來和裝置互動,而非 device special system