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 權限
- execute a file using one of the
如果出現 -
代表沒有該權限,另外還有三種特殊權限
- user
x
→s
(set-user-ID bit) :
當一個檔案被 execute 時,將 process 的 effective user ID 設定成該檔案的 owner (st_uid
)
e.g.passwd
is a set-user-ID program - group
x
→s
(set-group-ID bit) :
當一個檔案被 execute 時,將 process 的 effective group ID 設定成該檔案的 owner (st_gid
) - other
x
→t
(sticky bit) : 如果一個目錄被設定了 sticky bit,那要 removed 或 renamed 目錄內的檔案必須符合以下條件:
user hasw
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,只要有其中一個符合就通過:
- If effective UID== 0 (superuser)
- If effective UID ==
st_uid
(owner of file) - If effective GID or one of process’ supplementary GID ==
st_gid
(owner group of file) - 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 permissionW_OK
: test for write permissionX_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);
如果 owner 或 group 是 -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
-
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 : 沒有任何意義
-
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
Links
i-node and i-node link count

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,將舊的刪除
Hard Link
- 一個 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
Symbolic (Soft) 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

建立硬連結 : link()
// 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
建立軟連結 : symlink()
// returns 0 if OK, -1 on error
int symlink(const char *actualpath, const char *sympath);
建立 symbolic link sympath,連接到一個檔案 actualpath
- actualpath 不一定要存在
- sympath actualpath 可以在不同 file system 下
刪除連結 : unlink()
// 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,對應的檔案不會被影響
讀取軟連結 : readlink()
// 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.actime 和 times.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()
)
- mode 用來設定目錄權限 (
- 如果符合以下所有條件,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:
- 包含了 major 和 minor number (被儲存在
st_dev
和st_rdev
內) - major number 用來識別 device 的類別、使用的 drivers
- minor number 用來識別被 major number 指定的 driver 服務的每個 physical devices
- 可以用
major(dev_t)
minor(dev_t)
來從 dev_t 裡面獲取 major 和 minor 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