Unix文件与目录(1)
在Unix中,一切都以文件的方式存在,文件可以分为:
- 常规文件(regular file),包含某种形式数据,其数据是二进制还是字符文本对内核无意义。
- 目录夹(directory file),包含目录中文件的名称和指向该文件的指针。
- 块特殊文件(block special file),用于系统中的某些设备,如磁盘设备。
- 字符特殊文件(character spcial file),用于系统中的某些设备。
- 管道文件(FIFO),用于进程间通信。
- 套接字文件(socket),用于进程间网络通信。
- 字符链接(symbolic link),指向另一个文件。
可以使用stat、lstat、fstat函数获取已经存在的文件的信息,而不需要针对该文件具有任何必要的权限。文件信息以stat结构体的方式保存,结构体定义如下:
1 struct stat 2 { 3 mode_t st_mode; // file type & mode (permissions) 4 ino_t st_ino; // i-node number (serial number) 5 dev_t st_dev; // device number (file system) 6 dev_t st_rdev; // device number for special files 7 nlink_t st_nlink; // number of links 8 uid_t st_uid; // user ID of owner 9 gid_t st_gid; // group ID of owner 10 off_t st_size; // size in bytes, for regular files 11 time_t st_atime; // time of last access 12 time_t st_mtime; // time of last modification 13 time_t st_ctime; // time of last file status change 14 blksize_t st_blksize; // best I/O block size 15 blkcnt_t st_blocks; // number of disk blocks allocated 16 }
可以使用宏读取文件类型,其参数为st_mode。示例代码如下:
1 #include<errno.h> 2 #include<stdio.h> 3 #include<unistd.h> 4 #include<sys/stat.h> 5 6 int main(int argc, char *argv[]) 7 { 8 int i; 9 struct stat buf; 10 char *ptr; 11 for (i = 1; i < argc; i++) { 12 printf("%s: ", argv[i]); 13 if (lstat(argv[i], &buf) < 0) { 14 perror("lstat error"); 15 continue; 16 } 17 if (S_ISREG(buf.st_mode)) 18 ptr = "regular"; 19 else if (S_ISDIR(buf.st_mode)) 20 ptr = "directory"; 21 else if (S_ISCHR(buf.st_mode)) 22 ptr = "character special"; 23 else if (S_ISBLK(buf.st_mode)) 24 ptr = "block special"; 25 else if (S_ISFIFO(buf.st_mode)) 26 ptr = "fifo"; 27 else if (S_ISLNK(buf.st_mode)) 28 ptr = "symbolic link"; 29 else if (S_ISSOCK(buf.st_mode)) 30 ptr = "socket"; 31 else 32 ptr = "** unknown mode **"; 33 printf("%s\n", ptr); 34 } 35 exit(0); 36 }
当使用Linux操作系统时,必须先宏定义_GNU_SOURCE才能使用宏S_ISSOCK。
在每一个进程中,由六种或六种以上的ID与进程相关。其中实际用户ID(real user ID)和实际用户组(real group ID)告诉我们真实身份是谁;有效用户ID(effective user ID)、有效用户组(effective group ID)和添加组ID(supplementary group IDs)告诉我们是以什么样的身份(权限)来访问进程中的文件;保存设置用户(saved set-user-ID)和保存设置组(saved set-group-ID)由exec函数保存,使我们以程序文件的所有者身份来作为有效用户ID。在一般情况下有效用户ID与实际用户ID相同,我们可以通过设置set-user-ID位来改变有效用户。
Unix中文件访问权限分别为对文件所有者用户的读、写、执行权限,对文件所有者用户组的读、写、执行权限,对其他用户(非文件所有者用户)的读、写、执行权限。可以通过chmod或fchmod函数改变文件访问权限。对所有文件来说,如果我们想要打开一个文件,必须要有对该文件的可读权限,如果我们要对一个文件进行数据写入,必须要有对该文件的可写权限,如果要对一个文件进行执行操作,必须对该文件具有可执行权限。特殊的,对于目录文件(directory file)来说,可读权限使我们可以读取目录中的文件信息(但无法进入),可写权限使我们可以修改目录文件的信息(但无法进入目录),可执行权限使我们可以进入该目录。如果需要在目录中添加或者删除文件必须具有对该目录的可写与可执行权限。如果要打开一个文件,我们必须具有对该文件所在路径下所有目录的可执行(可进入)权限。在进程中新建文件时,该文件的所有者为该进程的有效用户,文件的所有者用户组为该进程的有效用户组或者文件所在目录的用户组。
当我们调用open函数时,内核会对文件进行基于有效用户的权限验证,当我们调用access函数时,内核会对文件进行基于实际用户的权限验证。当进程中实际用户和有效用户不同时,我们可以使用access函数验证实际用户是否能够有效的对文件进行操作。umask函数可以指定文件在创建时不应该具有哪些权限,可以避免用户得到不应得到的权限。子进程中的umask函数调用不影响父进程的umask结果。当需要对已存在的文件权限进行修改时,可以使用chmod或fchmod函数。使用chmod函数时,有效用户必须是被修改文件的所有者,或者进程具有超级用户权限。使用chmod函数同样可以设置文件的set-user-ID位、set-group-ID位与粘着位(stick bit)。设置粘着位时,进程必须具有超级用户权限,否则粘着位将被自动调整为关闭状态。设置set-group-ID位时,文件所有者用户组必须等于进程的有效用户组或添加组,否则set-group-ID位将自动调整为关闭状态。
当粘着位(stick bit)被设置时,该文件执行结束后,交换分区中将保留该进程的数据,以便于该文件被多次执行(可加快数据读取)。粘着位经常在常用程序文件中被设置。如果目录文件中粘着位被打开,只有具有对目录具有可写权限且满足下列条件之一时才能够删除或重命名目录中的文件。
- 是文件的所有者
- 是目录的所有者
- 是超级用户
使用chown、fchown、lchown函数可以修改文件的所有者用户与所有者用户组。当所有者用户或所有者用户组ID为-1时,将不对该选项进行任何修改。当_POSIX_CHOWN_RESTRICTED宏生效时,我们无法改变其他用户的文件,无法改变不属于我们所在用户组文件的所属用户组。当使用超级用户权限调用该函数时,文件的set-user-ID位与set-group-ID位将被清空。
文件中的st_size信息表示文件(常规文件、目录、字符链接文件)的大小。当文件为目录文件时,该值往往是16或512的倍数。当文件为字符链接文件时,该值表示字符链接文件名长度。字符链接文件名并不是以NULL结尾的C风格字符串。
我们可以使用truncate或ftruncate函数使已经存在的文件被截断。当文件大小小于函数中指定的长度时,只有指定范围内的数据可以被读取,当文件大小大于指定长度时,文件将被扩展。