> Linux编程 >

1. int fcntl(int fd,int cmd) / int fcntl(int fd,int cmd,long arg) / int fcntl(int fd,int cmd,struct flock *lock)     fcntl函数有以上3中调用格式,和open一样,都是用可变参数实现的,可变参数的类型和格式取决于前面的cmd

-----------------------------------------------------------------------------------------------------------------------
stat结构包含了一个文件有关的所有信息
struct stat {
    mode_t  st_mode;    // 文件类型 | set-user-ID/set-group-ID | 访问权限
    uid_t   st_uid;     // 文件所有者ID
    gid_t   st_gid;     // 文件组所有者ID
    off_t   st_size;    // 表示以字节为单位的文件长度,只对普通文件、目录文件、链接文件有意义
    nlink_t st_nlink;   // 文件的硬链接计数,只有当该值为0时,文件才可以被真正删除
    time_t  st_atime;   // 文件数据的最后访问时间
    time_t  st_mtime;   // 文件数据的最后修改时间
    time_t  st_ctime;   // i节点状态的最后修改时间
    dev_t   st_dev;     // 该文件所在的文件系统的设备号
    dev_t   st_rdev;    // 字符特殊文件和块特殊文件才有该值,代表实际设备的设备号
}
 
 
 
-----------------------------------------------------------------------------------------------------------------------
1. 用ls -l命令可以列出文件的所有信息,第一列表示文件类型:
    -   普通文件
    d   目录文件
    l   符号链接文件
    c   字符设备
    b   块设备
    s   套接字
    p   管道
    文件类型信息包含在stat结构的st_mode成员中,可以用S_IS*宏确定文件类型
 
2. linux权限机制采用ugo模式,u(user)表示所属用户,g(group)标识所属组,o(other)表示除了所属用户、所属组以外的其他用户
    u、g、o都有r、w、x三个权限,所以ugo模式是三类九种基本权限
 
    chown   修改文件的所属用户或组,用法如下:
            1). 若同时修改用户名和组名,则两者都写上
                    chown code5:code5 testfile
            2). 若只修改用户名,则去掉用户名后面的冒号和组名
                    chown code5 testfile
            3). 若只修改组名,则去掉冒号前面的用户名
                    chown :code5 testfile
            4). 若只修改用户名,组名采用登录时的组名,则去掉冒号后面的组名(这种不常用)
                    chown code5:
            备注: 上述用到的":"可以用"."替代;chown命令的操作权限为root
 
    @函数名 : mode_t umask(mode_t cmask)
    @作用   : 为当前进程设置file-mode-creation-mask,用来控制稍后创建文件的默认权限
    @规则   : file-mode-creation-mask通常以8进制表示,一位代表一种权限,置1表示屏蔽
               某一位设置屏蔽后,创建一个文件时(open/creat),mode_t参数中设置该位的操作将会无效
               umask修改file-mode-creation-mask对父进程无效
 
    粘住位/保存正文位(S_ISVTX)目前通常都是针对目录使用,设置了粘住位的目录,只有具备以下条件之一才能删除或重命名目录中的文件:
        拥有此目录
        拥有此文件
        是超级用户
    粘住位的典型应用就是/tmp目录,/tmp目录的用途就是给任一用户使用,所以任一用户对该目录都是RWX权限,
    而通过设置粘住位,使得用户不能删除或重命名属于其他人的文件。
 
    文件访问权限信息也包含在stat结构的st_mode成员中
 
3. 内核会给每个进程关联三个和进程ID无关的用户ID(组ID同理):
        真实用户ID:就是登陆用户的uid,用来标识我们究竟是谁
        有效用户ID:用来决定我们对文件的访问权限
        保存的set-user-ID: 由exec函数拷贝有效用户ID而来,如果要执行的文件设置了set-user-ID位,则先根据文件所有者的用户ID设置进程的有效用户ID,然后再拷贝有效用户ID
 
    每个文件也都有一个所有者的用户ID和组ID,分别对应stat结构中的st_uid和st_gid.
 
    默认情况下,当执行一个程序文件时,进程的有效用户ID就是真实用户ID;
    但如果程序文件的st_mode字段中设置了set-user-ID位,当执行该文件时,进程的有效用户ID就会变成文件所有者的用户ID(st_uid)
    设置方法为"chmod +s filename"或"chmod 4755 filename"
 
    注意: 拥有超级用户特权,意味着其有效用户ID必为0,其余两个用户ID随意!!!
 
    @函数名 : int setuid(uid_t uid)
    @作用   : 用来设置上面3种用户ID
    @规则   : 若进程具有超级用户特权,则将3种用户ID全部设置为uid,且uid可以随意指定
               若进程没有超级用户特权,uid存在限制(必须为实际用户ID或保存的set-user-ID),该函数才会将有效用户ID设置为uid
 
    综合上面这些规则,产生一种重要的应用场景,依次以/usr/bin/passwd和/usr/bin/sudo为例:
        文件/usr/bin/passwd所有者是root,而且设置了set-user-ID位,所以当任何进程执行了该文件后,都得到了超级用户特权,从而可以将新口令写入口令文件
        文件/usr/bin/sudo则更进一步,当任何进程执行了该文件得到超级用户特权后,还执行了setuid(ROOT_UID),至此进程的3种用户ID全部变成了0,成为彻底的root用户
 
    @函数名 : int seteuid(uid_t uid)
    @作用   : 只用来设置有效用户ID
    @规则   : 若进程具有超级用户特权,则只将有效用户ID设置为uid,且uid可以随意制定
               若进程没有超级用户特权,uid存在限制(必须为实际用户ID或保存的set-user-ID),该函数才会将有效用户ID设置为uid,这种情况下其实和上面的setuid用法一样
 
    @函数名 : int access(const char *pathname,int mode)
    @作用   : 测试当前进程对文件的访问权限
    @规则   : 当进程尝试打开一个文件时,内核是以进程的有效用户ID和有效组ID对其进行访问权限测试,
               而access函数是用进程的实际用户ID和实际组ID进行访问权限测试。
               所以实际应用时可能会出现可以打开文件读写但是access测试通不过,或者access测试通过但打不开的现象
 
4. UFS文件系统
    一个磁盘可以分成多个分区,每个分区可以包含一个文件系统,UFS文件系统的i节点和数据块结构图大致如下:
            +++++++++
            +       +   <----------------   i节点号(目录项a)
            +       +   <----------------   i节点号(目录项b)
            +       +
            + i节点 +   <----------------   数据块1     
            +       +   <----------------   数据块2 
            +       +   <----------------   数据块3
            +++++++++
    1). i节点是固定长度的记录项,包含了有关文件的大部分信息,
    2). 每个i节点中都有一个硬链接计数(上图该值为2),代表指向该i节点的目录项数,只有当硬链接计数减少至0时,才可以删除该文件
    3). 对于符号链接文件,其数据块中存放了该符号链接所指向的文件的名字
    4). 每个文件系统都各自对它们的i节点进行编号,所以目录项中的i节点号只能指向本文件系统中的相应i节点,这也就是硬链接不能跨文件系统的原因
    5). 对于目录文件,其链接计数值最小为2(因为必有本目录中的"."项和上级目录中命名它的目录项),而本目录中每个子目录都会使本目录的链接计数增1
 
    @函数名 : int unlink(const char *pathname)
    @作用   : 删除一个现有的目录项(目录项并不特指目录文件),并将由pathname所引用文件的链接计数减1
    @规则   : 只有当链接计数为0并且不再有进程打开了该文件,其文件内容才可被删除,
               这种性质通常被程序用来确保即使在该程序崩溃时,它所创建的临时文件也不会遗留下来(open创建临时文件之后,立即调用unlink)
 
5. 例子(验证set-user-ID位)
   
#define _GNU_SOURCE
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
 
    int main(int argc,char *argv[])
    {
        uid_t uid,euid,suid;
        if(getresuid(&uid,&euid,&suid) < 0){
            perror("getresuid");
            return -1;
        }
        printf("uid = %d,euid = %d,suid = %d\n",uid,euid,suid);
        return 0;
    }
 
 
    操作步骤:
    [1]. 进入例子test.c所在目录,查看当前shell下的用户ID
            $ id
            uid=1000(code5) gid=1000(code5) 组=1000(code5) ...
    [2]. 执行编译
            $ gcc -Wall test.c
    [3]. 编译成功后,在当前目录下生成对应的可执行文件a.out,执行该程序
            $ ./a.out
            uid = 1000,euid = 1000,suid = 1000       
    [4]. 以root权限再次执行该程序
            $ sudo ./a.out
            uid = 0,euid = 0,suid = 0
    [5]. 为可执行文件a.out设置set-user-ID位
            $ chmod +s a.out
    [6]. 以root权限再次执行该程序
            $ sudo ./a.out
            uid = 0,euid = 1000,suid = 1000
    小结:可执行文件设置了set-user-ID位后,执行时进程的有效用户ID和保存的设置用户ID都会被设置为该可执行文件的所有者ID


 


(责任编辑:IT)