前言
本文是前面Unix编程系列的第3篇,
- 主要描述文件特征或者文件性质,在编程中怎么去获取属性和更改属性。
- 描述用户和组的一些概念,以及对应的文件权限操作。
文件信息
此处要讲的是关于文件的数据结构,在Unix中struct stat包含了所有类型文件的关键信息。很多系统调用核心都是在其基础之上操作。
stat(2)
获取文件的信息。
其中有个重要的struct stat,描述了文件的各种信息。
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* Inode number */
mode_t st_mode; /* File type and mode */
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) */
off_t st_size; /* Total size, in bytes */
blksize_t st_blksize; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
/* Since Linux 2.6, the kernel supports nanosecond
precision for the following timestamp fields.
For the details before Linux 2.6, see NOTES. */
struct timespec st_atim; /* Time of last access */
struct timespec st_mtim; /* Time of last modification */
struct timespec st_ctim; /* Time of last status change */
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
st_dev:文件所在设备的ID;
st_ino:文件的Inode号;
st_mode:文件类型和权限模式;
st_nlink:硬链接数;
st_uid:文件所有者的用户ID;
st_gid:文件所有者的组ID;
st_rdev:设备文件的设备ID;
st_size:文件大小(字节数);
st_blksize:文件系统I/O的块大小;
st_blocks:文件分配的块数。
文件的类型 struct stat:st_mode
其中st_mode文件类型有如下几种类型
S_IFMT 0170000 bit mask for the file type bit field 掩码
//下面为列举的文件类型
S_IFSOCK 0140000 socket 套接字(socket)类型,用于进程之间的通信;
S_IFLNK 0120000 symbolic link 符号链接(symbolic link)类型,常用于虚拟机、容器等环境下的软连接;
S_IFREG 0100000 regular file 普通文件(regular file)类型,最常见的文件类型;
S_IFBLK 0060000 block device 块设备文件(block device)类型,如硬盘、U盘等存储设备;
S_IFDIR 0040000 directory 目录(directory)类型,用于存放其他文件和目录;
S_IFCHR 0020000 character device 字符设备文件(character device)类型,如键盘、鼠标等输入设备;
S_IFIFO 0010000 FIFO 管道(FIFO)类型,可实现进程之间的通信
代码片段
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/sysmacros.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 (lstat(argv[1], &sb) == -1) {
perror("lstat");
exit(EXIT_FAILURE);
}
printf("ID of containing device: [%lx,%lx]\n",
(long) major(sb.st_dev), (long) minor(sb.st_dev));
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;
// }
# define S_ISSOCK(mode) __S_ISTYPE((mode), __S_IFSOCK)
if (S_ISREG(sb.st_mode))
printf("regular file\n");
else if (S_ISDIR(sb.st_mode))
printf("directory\n");
else if (S_ISCHR(sb.st_mode))
printf("character special\n");
else if (S_ISBLK(sb.st_mode))
printf("block special\n");
else if (S_ISFIFO(sb.st_mode))
printf("FIFO\n");
else if (S_ISLNK(sb.st_mode))
printf("symbolic link\n");
else if (S_ISSOCK(sb.st_mode))
printf("socket\n");
else
printf("unknown\n");
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);
}
用户UIDs和组GIDs
struct stat: st_mode, st_uid, st_gid
每一个进程拥有6个或更多IDs与之关联
| 实际用户 ID 实际组 ID | 我们实际上是谁 |
| 有效用户 ID 有效组 ID 附属组 ID | 用于文件访问权限检查 |
| 保存的设置用户 ID 保存的设置组 ID | 由exec函数保存 |
- 当文件被设置为setuid时,将有效的用户ID设置为st_uid。
- 当文件为setgid时,将有效的组ID设置为st_gid。
- st_uid和st_gid总是指定文件的所有者和组所有者,不管它是setuid还是setgid。
文件的权限模式
它由10个字符组成,包括第1个特殊字符和后面的9个普通字符,每个字符代表一种权限或者文件类型。其中:
-
第一个字符代表文件类型。在这个例子中,这个文件是一个可执行文件,并且设置了 setuid 位。具体来说:
-代表常规文件;d代表目录;l代表符号链接(软链接);s代表套接字(socket);p代表命名管道(named pipe);c代表字符设备文件;b代表块设备文件。
-
后面的9个字符每三个一组,分别表示对该文件的读、写、执行权限。其中:
r表示可读取(read);w表示可写入(write);x表示可以执行文件或进入目录(execute);-表示无对应权限。
-
特殊权限位:
s表示 setuid/setgid 位。对于可执行文件,设置 setuid 位表示在执行该文件时,使用该文件的所有者的权限来执行。S表示 setuid/setgid 位被设置,但与可执行文件无关(例如,对于一个文件夹)。t表示 sticky 位。对于一个目录,设置了 sticky 位后,只有目录的所有者和根用户才能删除该目录中的文件。T表示 sticky 位被设置,但与文件夹无关(例如,在一个空的文件夹中)。
如 -rwsr-xr-x 表示该文件是一个可执行文件,并且设置了 setuid 位,所有者具有读、写、执行权限,组用户和其他用户具有读和执行权限。
getuid(2),setuid(2)
#include <unistd.h>
#include <sys/types.h>
uid_t getuid(void);
uid_t geteuid(void);
//Returns: uid_t; never errors
int setuid(uid_t uid);
int seteuid(uid_t uid);
//Returns: 0 if OK, -1 on error
关于UIDs
- 每个进程都有一个有效的UID和一个真实的UID,一个有效的GID和一个真实的GID
- 如果在权限中设置了setuid (setgid)位,则在执行时,有效UID (GID)将成为文件结构stat的st_uid (st_gid)
- 可以通过seteuid(2)在它们之间切换
- setuid(2)不可撤销地设置了两者。
- 有效的UID和组ID是用于文件权限检查的UID和组ID,但可以通过access(2)系统调用来检查真实的ID是否具有权限
chmod(2) and chown(2)
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
#include <fcntl.h> /* Definition of AT_* constants */
#include <sys/stat.h>
int fchmodat(int dirfd, const char *pathname, mode_t mode, int flags);
用于修改文件权限的系统函数。其中mode_t mode类型可以对应设置值如下(可以ORing组合):
The new file mode is specified in mode, which is a bit mask created by ORing together zero or more of the following:
S_ISUID (04000) set-user-ID (set process effective user ID on execve(2))
S_ISGID (02000) set-group-ID (set process effective group ID on execve(2); mandatory locking, as described in fcntl(2); take a new file's
group from parent directory, as described in chown(2) and mkdir(2))
S_ISVTX (01000) sticky bit (restricted deletion flag, as described in unlink(2))
S_IRUSR (00400) read by owner
S_IWUSR (00200) write by owner
S_IXUSR (00100) execute/search by owner ("search" applies for directories, and means that entries within the directory can be accessed)
S_IRGRP (00040) read by group
S_IWGRP (00020) write by group
S_IXGRP (00010) execute/search by group
S_IROTH (00004) read by others
S_IWOTH (00002) write by others
S_IXOTH (00001) execute/search by others
代码片段
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
int
main() {
struct stat sbuf;
if (stat("file", &sbuf) == -1) {
perror("can't stat file");
exit(EXIT_FAILURE);
}
/* 关闭所有者读取权限并打开 setgid */
if (chmod("file", (sbuf.st_mode & ~S_IRUSR) | S_ISGID) == -1) {
perror("can't chmod file");
exit(EXIT_FAILURE);
}
/* 将绝对模式设置为 rw-r--r- */
if (chmod("file1", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1) {
perror("can't chmod file1");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
关于权限
- 只有根用户和文件所有者可以更改其权限。
- 只有root可以更改文件的所有者,但所有者可以更改文件的组所有者
- 上面的chmod(2) chown(2)对应于系统的命令chmod chown使用,详细使用方法和在程序中使用函数,可以查阅文档手册。
总结
Unix中了解文件的特征是必不可少的内容。了解掌握其中的关键点有助于更好的了解系统。