[APUE]文件和目录(上)

时间:2022-04-18 12:19:28

一、文件权限

  1. 各种ID

  我在读这一章时遇到了各种ID,根据名字完全不清楚什么意思,幸好看到了这篇文章,http://blog.csdn.net/ccjjnn19890720/article/details/6990656,总结一下

  每一个进程其实对应了6个以上的ID,它们分别是:实际用户ID、实际组ID(我们实际上是谁,执行这个程序的用户和组),有效用户ID、有效组ID、附加组ID(用于文件访问权限检查),保存设置用户ID,保存设置组ID(由exec函数保存)

  实际用户ID/实际组ID:当前执行这个进程的ID,比如我现在是orlion用户,那么我执行foo程序,那么这个foo进程的实际用户ID就是orlion。组同理。一般来说就是当前登陆的用户。

  有效用户ID/有效组ID:这个ID是unix一直在使用的一个ID,因为即使你只是一个很简单的访问文件,那也是要通过这个有效用户ID的,因为每一个文件都有一定的访问权限,而一个进程或者一个程序去访问它,操作系统本身就是根据你的有效用户ID给与一定的权限.

  实际用户ID与有效用户ID到底什么区别?!:这两个ID在一般情况下是相同的,比如当前用户是orlion,那么它的实际用户ID是orlion,而有效用户ID也是orlion。可是在不一般的情况下那么这两个ID就可能不一样了,那么什么样的情况下是不一样的呢?那就是当一个用户要进行一个合理的特权的时候就需要啦,那么到底是怎么样的情况呢?

      比如我们在Linux系统中的passwd这个命令或者这个passwd这个程序,一个用户对自己进行修改密码是一种很正常的事情,可是保存密码的文件/etc/passwd却是root用户可写的这样的权利,那么也就是用如果你要修改密码,必须通过root用户帮你修改

  这个事情的处理是这样的,让用户去运行passwd这个程序的时候,os给与root用户的权利,然后用户就可以修改自己的密码。具体的讲就是让用户去运行passwd这个程序的时候,unix将它的有效用户ID变成了拥有passwd的用户的ID,也就是root,所以就可以修改这个/etc/passwd这个文件。

  保存设置用户ID:这个ID是用来保存有效ID的副本,让我们运行程序的过程其实就是os调用exec系列函数来调用我们程序的main函数,exec函数是kernel唯一执行程序的方法,或者那么讲不管什么用户程序的运行,其实也就是os的exec的调用过程。而exec在调用过程中会将这个程序的有效用户ID拷贝给保存用户ID。

  在P O S I X . 1中,这些保存的I D是可选择的。一个应用程序在编译时可测试常数_ POSIX_SAVED_ IDS,或在运行时以参数_ SC_SAVED_IDS调用函数sysconf,以判断此实现是否支持这种特征。

  上边是与进程相关联的ID,下面提到的设置用户ID位则是文件方式字中的一位(与进程无关,是文件属性)。

  文件的设置用户ID位:每一个文件都有一个文件模式字(st_mode),这个字可以通过stat函数去获取,而这个模式字包含了很多文件的属性,包括文件的类型,以及文件的访问权限的,当然设置用户ID位也在其中。通过设置这个位,就能当执行这个文件的时候,进程的有效ID设置为该文件本身的用户,这里这个文件可以认为是可执行文件,当运行这个文件的时候,进程会改变其有效用户ID,变成这些文件本身的ID

  终端中我们查看/usr/bin/passwd这个文件

$ ll /usr/bin/passwd
-rwsr-xr-x. 1 root root 30768 2月  22 2012 /usr/bin/passwd

  可以看到有一个s权限,这就是设置了设置用户ID位的标志。

  

#include <stdio.h>  
#include <stdlib.h>  
#include <sys/stat.h>  
#include <unistd.h>  
  
int main()  
{  
    printf("real user ID = %d\n",getuid());  
    printf("effect user ID = %d\n",geteuid());  
  
    return 0;  
}  
 
终端中执行: orlion$ .
/main real user ID = 1000 effect user ID = 1000 orlion$ su root# chown root main root# chmod u+s main root# ll main -rwsr-xr-x 1 root orlion 9809 2016-12-21 22:20 main root# exit orlion$ exit orlion$ ./main real user ID = 1000 effect user ID = 0

  从以上可以看到有效用户id发生了变化

 2. 新文件和目录的所有权

  新目录所有权与新文件的所有权的规则相同

  新文件的用户ID设置为进程的有效用户ID。组ID POSIX.1允许选择下列之一作为新文件的组ID

    (1) 新文件的组ID可以是进程的有效组ID

    (2) 新文件的组ID可以是它所在目录的组ID

二、chmod和fchmod函数

  这两个函数使我们可以更改现存文件的存取许可权(权限)

#include <sys/types.h>
#include <sys/stat.h>

int chmod(const char *pathname, mode_t mode);

int fchmod(int fileds, mode_t mode); fileds即为文件句柄

返回值: 若成功则为0,若出错则为-1

  为了改变一个文件的权限,进程的有效用户ID必须等于文件的所有者,或者有root权限

  参数mode是下面所示逐位或运算

  

mode                说明明

S_ISUID            执行时设置 -用户-ID
S_ISGID            执行时设置 -组-ID
S_ISVTX            保存正文
S_IRWXU          用户(所有者)读、写和执行
S_IRUSR           用户(所有者)读
S_IWUSR          用户(所有者)写
S_IXUSR           用户(所有者)执行
S_IRWXG          组读、写和执行
S_IRGRP           组读
S_IWGRP          组写
S_IXGRP           组执行
S_IRWXO 其他读、写、和执行
S_IROTH 其他读
S_IWOTH 其他写
S_IXOTH 其他执行

   实例:

#include <stdio.h>
#include <sys/stat.h>

int main(void)
{
    struct stat statbuf;

    if (stat("foo", &statbuf) < 0) {
        fprintf(stderr, "stat error for foo\n");
    }

    if (chmod("foo", (statbuf.st_mode & -S_IXGRP) | S_ISGID) < 0) {
        fprintf(stderr, "chmod error for foo\n");
    }

    /* set absolute mode to "rw-r--r--" */
    if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) {
        fprintf(stderr, "chmod error for bar\n");
    }

    return 0;
}

 

三、 粘住位
  上一小节中S_ISVTX位,UNIX早期版本中,有一位被称为粘住位(sticky bit)。如果一个可执行程序文件的这一位被设置了,那么在该程序第一次执行并结束时,该程序正文的一个文本被保存在交换区。 (程序的正文部分是机器指令部分。 )这使得下次执行该程序时能较快地将其装入内存区。其原因是:在交换区,该文件是被连续存放的,而在一般的 U N I X文件系统中,文件的各数据块很可能是随机存放的。对于常用的应用程序,例如文本编辑程序和编译程序的各部分常常设置它们所在文件的粘住位。后来的UNIX版本称之为保存 -正文位( saved-text bit ),因此也就有了常数 S _ I S V T X。现今较新的UNIX系统大多数都具有虚存系统以及快速文件系统,所以不再需要使用这种技术。
S V R 4和4 . 3 + B S D中粘住位的主要针对目录。如果对一个目录设置了粘住位,则只有对该目录具有写许可权的用户并且满足下列条件之一,才能删除或更名该目录下的文件:
• 拥有此文件。
• 拥有此目录。
• 是超级用户。
目录/tmp和/var/spool/uucppublic是设置粘住位的候选者 —,这两个目录任何用户都可在其中创建文件。这两个目录对任一用户 (用户、组和其他 )的许可权通常都是读、写和执行。但是用户不应能删除或更名属于其他人的文件,为此在这两个目录的文件方式中都设置了粘住位。

四、 chown、fchown和lchown函数

#include <sys/types.h>
#include <unistd.h>

int chown(const char *pathname, uid_t owner, gid_t group);

int fchown(int filedes, uid_t owner, gid_t group);

int lchown(const char *pathname, uid_t owner, gid_t group);
返回值:成功0,失败-1.

  lchown更改符号连接本身的所有者,而不是符号链接所指向的文件

  根据_POSIX_CHOWN_RESTRICTED的值,POISX.1可以选择只有超级用户才能更改某个文件的所有者或者任何用户都能修改他们所拥有的文件的所有者。

  若_POSIX_CHOWN_RESTRICTED对指定的文件起作用,则

  (1) 只有超级用户进程能更改该文件的用户 ID。
  (2) 若满足下列条件,一个非超级用户进程可以更改该文件的组 ID:
    (a) 进程拥有此文件(其有效用户 ID等于该文件的用户 ID)。
    (b) 参数owner等于文件的用户ID,参数group等于进程的有效组ID或进程的添加组ID之一。
  这意味着,当 _ P O S I X _ C H O W N _ R E S T R I C T E D有效时,不能更改其他用户的文件的用户ID。你可以更改你所拥用的文件的组 ID,但只能改到你所属于的组。
  如果这些函数由非超级用户进程调用,则在成功返回时,该文件的设置 -用户-ID位和设置-组-ID位都被清除。

五、 文件长度
  stat结构的成员st_size包含了以字节为单位该文件的长度。此字段只对普通文件、目录、符号连接有意义。
  对于目录文件长度通常市一个数,例16或512的整数倍;
  对于符号连接,文件长度是实际文件的长度。

六、 文件截短
  有时我们需要在文件尾端截去一些数据以缩短文件,截短文件可以调用以下函数

#include <sys/types.h>
#include <unistd.h>

int truncate(const char *pathname, off_t length);
int ftruncate(int filedes, off_t length);
返回值:成功0,失败-1.

  这两个函数将文件的长度截短为length, 如果之前文件长度大于length,则超过length以外的数据就不再存取,如果以前的长度小于length,则其结果与系统有关。如果某个系统的处理是扩展该文件,则超过旧文件尾端与新文件尾端数据将读作0。

七、 文件系统

  传统的UNIX系统V文件系统,可以将一个硬盘分为多个分区,每个分区可以包含一个文件系统

  [APUE]文件和目录(上)

 

    i节点是固定长度的记录项,包含有关文件的信息。

  [APUE]文件和目录(上)

  • 上图中有两个目录指向同一i节点。每个i节点中都有一个连接计数,其值是指向该i节点的目录项数。只有当连接计数为0时才能删除该文件(也就是可以释放该文件所占的数据块)。在stat结构中连接计数包含在st_nlink中,其基本系统数据类型是nlink_t。这种连接称为硬连接。POSIX.1常数LINK_MAX指定了一个文件连接的最大值。
  • 另外一种连接是符号连接(symbolic link)。对于这种连接,该文件的实际内容(在数据块中)包含了该符号连接所指向的文件的名字。
  • i节点包含了所有与文件有关的信息:文件类型、文件存取许可权位、文件长度和指向该文件所占用的数据块的指针等。stat结构中大多数信息都取自i节点。只有两项数据存放在目录项中:文件名和i节点编号数。i节点编号数的数据类型是ino_t。
  • 因为目录项中的i节点编号数指向同一文件系统中的i节点,所以不能使一个目录项指向另一个文件系统的i节点。(所以ln命令不能跨文件系统)
  • 当在不更改文件系统的情况下为一个文件更名时,该文件的实际内容并未移动,只需构造一个指向现存i节点的新目录项。

 

  对于目录文件的连接计数字段:假如我们创建了一个testdir目录:

$ mkdir testdir

  下图显示了其结果,显式的显示了.和..目录项

  [APUE]文件和目录(上)

  2549的i节点,其类型字段表示它是一个目录,其类型字段表示它是一个目录。而连接计数为2。任何一个叶目录(不包含任何目录(子目录)的目录)其连接计数总是2,数值2来自于命名该目录(testdir)的目录以及在该目录中的.项。编号为1267的i节点,其类型字段表示它是一个目录,而其连接计数则大于或等于3。它大于等于3的原因是至少有三个目录项指向它:一个是命名它的目录项,一个是在该目录中的.项,第三个是在自子目录testdir中的..项。