2011-12-04 23:04:04 jfkidear 阅读数 572

NO.4 UNIX系统的文件属性和存取权限

作者:NetDemon
netdemon@20cn.com
www.20cn.net

--------------------

我们都知道UNIX是一个多用户的操作系统,那么,它是怎样来区分一个文件是属于谁的,这个文件是什么类型的文件的呢?下面我们通过一个例子来说明这个问题

我们现在使用ls命令列出当前目录下的文件

#ls -la
# -rw-rw-rw-    1    root      wheel    170    jan 7 19:46    mnk
# -rw-r-----    1    root      wheel    18204  jan 8 20:34    nmap.tar.gz
# -rwxr-xr--    1    candy     user     1204   may 23 13:00   mysh.sh
# drwx------    2    netdemon  user     512    may 23 14:23   mydoc

|-----1------|--2--|----3-----|---4---|---5---|------6------|-----7-------|

在这里第一部分是文件属性,第二部分是文件数量,第三是所有者,第四是所属组,第五是文件大小,第六是文件修改时间,第七是文件名,其中文件名,时间,文件大小应该不用解释大家都能明白,我就不再说明了,第三部分表明了一个文件的拥有者是谁,就mnk这个文件来说,它的拥有者是root,是属于wheel这个组的,而mysh.sh的拥有者是candy,user组的,通常情况下,如果用户属于这个组,那么这个文件也就属于这个组,第二部分指出连接到此文件的连接的数量,我们都知道,在win系统上,我们可以创建快捷方式,比如在桌面上创建一个快捷方式,指向某个文件,UNIX的连接也大致是一样的概念,如果在系统尚有一个连接是指向mnk的,那么在这里它的1就会变成2。而第一部分呢,表面看起来很不知所谓,很难一下子明白这个表示了什么,但这个就恰恰是UNIX的精粹所在,UNIX的最大特点之一,如果没有这个,UNIX将不是UNIX。
这个部分一共有10位数来表示,第一位表示文件类型,"-"表示这个一个文件,"d"表示这个一个目录,"s"表示这是一个连接,详细的说明请看最后的表格,接下来的9位,我们把他们每3位分为一段来看,第一段对应于文件拥有者用u表示(user),第二段对应属组用g表示(group),第三段对应任何人用o表示(other),而每一段的第一位代表读权限(r),第二为代表写权限(w),第三位代表执行(x)(对文件而言)或可进入(对目录而言)权限,现在我们拿第二个文件nmap.tar.gz来作说名,第一位"-"表明这是一个文件,接下来的"rw-"表明root可以读写这个文件,但不能执行它,再接下来的"r--"表明了属于wheel这个组的人可以读这个文件,但不能修改(不可写)也不能执行这个文件,最后的"---"表明了其他的任何人都不能读、写、执行这个文件。由此,我们现在可以知道,mnk这个文件是任何一个人都可以读写但不能执行的一个文件,因为它的每一部分都是"rw-",而第三个mysh.sh就是candy可读可写可执行,netdemon可以执行但不能修改或删除,为什么呢?通过第4个mydoc,我们看出netdemon也是属于user组的一个用户,而mysh.sh的表示组权限的这一段是"r-x",所以netdemon有执行这个文件的权限,但是其他的人就只能看了,这是因为最后的"r--"说明的这个规则。第四个mydoc,由第一位的"d",说明这是一个目录而不是一个文件,netdemon可以读写进入这个目录,但其他的人都不可以包括同一组的candy,因为他最后都是"---"。但是,root可不可以呢?答案是可以的,因为root是整个系统权限最高的一个用户的名字,几乎所有的UNIX系统都一样,整个系统所有的东西都是属于root的,尽管在某些UNIX系统上root去删除mydoc这个目录时会出错,但root还是通过可以使用chown或chmod来改变文件的属性再删除或修改的,(关于chown及chmod等命令自行用'man chmod'命令察看说明)
那么,我们通常也接触到的一些资料说的属性为700,644,755的这些又是怎么回事呢?其实,这只是表示的方式不一样而已,照前面的说法,把表示属性的9位数分为三段,user,group,other,各段的权限(rwx)换为二进制,再变为10进制的结果,有"r"或"w","x"权限的用1表示,没有的用0表示,即"---"为000,"rwx"为111,那么,"r-x"的二进制就表示为 101,而101的十进制数为 1*2e2+0*2e1+1*2e0=1*4+0*2+1*1=5,再把u,g,o各自的值串起来就成了755 644等的这些表示法了,比如"rwxr-xr-x" ,因为u为rwx,二进制是111,十进制是7,g和o都为r-x,二进制是101,十进制是5,所以,"rwxr-xr-x"也可以用755表示,下面给出各种权限的二进制和十进制的值

权限        二进制      十进制
---------------------------------------
---         000         0
--x         001         1
-w-         010         2
-wx         011         3
r--         100         4
r-x         101         5
rw-         110         6
rwx         111         7
---------------------------------------


文件类型的表示符
---------------------------------------
d         目录
b         二进制特殊文件
c         文本特殊文件
l         符号连接
p         Pipe
s         Socket
-         普通文件
---------------------------------------


顺便说一个问题,就是UNIX系统不是用扩展名(如win系统的.exe .com)来标示一个文件是否是可执行文件,一个文件是否可以执行和文件名无关,只和文件属性的x的值有关系。
看到这里,我相信你对文件权限这个问题你应该是可以弄清楚了,可如果你比较细心又聪明的话同时你也许会有一个疑问,如果一个文件的属性是"-------rwx",那么企不是任何人都能读写和执行,但此文件的所有者自己却没有呢?这个问题,我就卖个关子了,我希望你可以自己试一下,毕竟实践还是非常的重要的,你需要自己安装一个UNIX的系统来用一用,对于UNIX的命令的这一部分我不准备写了,因为基本上,在 /bin 及 /usr/bin下面的所有文件都是UNIX的命令,UNIX并没有像DOS那样有内部命令和外部命令之分的,所有的可执行文件都是UNIX的命令,你可以把bin下面的每个都尝试一下,不懂的呢,就用 man "文件名" 这个命令来得到他的作用和使用方法,当然,man 出来的都是E文的,你不要说你E文不好看不懂,你既然知道你E文不好,就应该去花点时间学习E文,而通过看man来学习是一个很有效的方法,我就是这样,我读书的时候E文不及格,后来是通过给老师送礼才过了关的,但是我现在还是可以勉强和外国佬在网上聊天,因为这都是man给逼出来的。我可不希望现在还在读书的朋友学我那样通过送礼过关哦,一边看着屏幕一边翻着字典一个一个查的滋味非常痛苦的,况且我是虽然E文不及格,但专业科的模拟电子线路却是全系最高分的,而且人还长得帅那教E文的老太太才有怜悯之心的哦,你就不知道啦,呵呵

接下去的这段时间,你应该去安装UNIX系统啦,弄清楚各种常用的命令等,我本来是准备在下一篇说说程序和进程的权限的,但考虑到这个时候说也许大家的水平接不上,所以下一篇就说说网络基础方面的了,到了一定的程度再回过头来说UNIX,这样并发而来,也许更加好一点,你在UNIX方面有什么问题可以到论坛发问或者写信给我,但拜托不要在QQ上来问我,因为我那上有好友几百多个,加上我通过而没有加为好友的总共可能有2000个,在这样的情况下,我根本不可能回答你什么除非20个字之内可以说完的,而写信或者在论坛提问我有充分的时间详细的解答你的问题,这样彼此都有好处,请大家理解我的苦衷

下一篇

2008-12-07 11:53:00 luosl 阅读数 820
 一、ls命令
ls命令显示目录下包含的文件清单
$ls
readme
names
tmp
显示当前目录下有readme, names, tmp三个文件。
ls -l 显示目录下文件的详细属性
ls -a 显示目录下的所有文件,包括隐藏文件“.”和“..”文件。
ls命令后面可以带参数指定要显示的目录的路径。

二、cat命令
cat命令是查看文件的内容。cat命令要求指定需要查看的文件名。
$cat names
Susan
Jeff
Henry
Allan
Ken

三、wc命令
wc命令用于统计文件中的行数、单词数和字符数等信息。
$wc names
5 5 27 names
wc的命令的输出包含三个数字,分别是文件names中的行数、单词数和字符数。
wc -l 显示文件中的行数
wc -w 显示文件中的单词数
wc -c 显示文件中的字符数
$wc -l names
5
$wc -w names
5
$wc -c names
27

四、cp命令
cp命令是执行文件拷贝的命令,cp命令带有两个参数,第一个参数是原文件,第二个参数是目标文件。
$cp file1 file2
将file1中的内容拷贝到file2中。其中file1是已经存在的文件,当file2存在的时候,将使用file1的内容覆盖file2中的内容,否则新建一个文件并保存。
一条特殊的拷贝命令
$cp /dev/null file2
将file2中的内容清空

五、mv命令
mv命令完成文件的重命名操作,mv文件携带两个参数,第一个参数是原文件名,第二个参数是命名有的新文件名。
$mv file1 file2
将名称为file1的文件重命名为file2
mv与cp命令的差别:
mv携带的两个参数中,完成操作有只有第二个参数名称的文件存在。
cp携带的两个参数中,完成操作后,两个文件是同时存在的。

六、rm命令
rm命令是删除指定的文件。
如果需要删除多个文件,可以在一条命令中同时携带多个参数来指定要删除的对象。
$rm readme names tmp
将删除readme, names, tmp三个文件。



2016-10-16 15:58:11 MonroeD 阅读数 972

UNIX中有句话“一切皆为文件”,因此全方位了解文件属性是非常必要的。

stat函数如下,使用stat函数最多的可能是ls -l命令了

int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
struct stat {
    dev_t     st_dev;     /* ID of device containing file */
    ino_t     st_ino;     /* inode number */
    mode_t    st_mode;    /* protection */
    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; /* blocksize for file system I/O */
    blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
    time_t    st_atime;   /* time of last access */
    time_t    st_mtime;   /* time of last modification */
    time_t    st_ctime;   /* time of last status change */
          };

本章围绕stat的属性进行介绍UNIX文件相关的属性。

1. 检测文件类型

我们知道有很多文件类型,如普通文件、目录文件、字符特殊文件、块文件、FIFO、套接口、符号链接文件(stat函数检测不出来,得是lstat函数才行)文件类型的信息包含在st_mode,可以用下表的宏确定文件类型。

S_ISREG(m)  //is it a regular file
S_ISDIR(m)  //directory
S_ISCHR(m) // character device
S_ISBLK(m) // block device
S_ISFIFO(m) //FIFO (named pipe)
S_ISLNK(m)  //symbolic link
S_ISSOCK(m) //socket

下面看一个程序

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <errno.h>

int
main(int argc, char *argv[])
{
    int         i;
    struct stat buf;
    char        *ptr;

    for (i = 1; i < argc; i++) {
        printf("%s: ", argv[i]);
        if (lstat(argv[i], &buf) < 0) {
            perror("lstat error");
            continue;
        }
        if (S_ISREG(buf.st_mode))
            ptr = "regular";
        else if (S_ISDIR(buf.st_mode))
            ptr = "directory";
        else if (S_ISCHR(buf.st_mode))
            ptr = "character special";
        else if (S_ISBLK(buf.st_mode))
            ptr = "block special";
        else if (S_ISFIFO(buf.st_mode))
            ptr = "fifo";
        else if (S_ISLNK(buf.st_mode))
            ptr = "symbolic link";
        else if (S_ISSOCK(buf.st_mode))
            ptr = "socket";
        else
            ptr = "** unknown mode **";
        printf("%s\n", ptr);
    }
    exit(0);
}

2. 设置用户ID(SUID)和设置用户组ID(SGID)

详情见uid,suid

3. 文件访问权限

st_mode也包含了对文件的访问权限,下面看下文件权限。

S_IRUSR  //owner has read permission
S_IWUSR  //owner has write permission
S_IXUSR  //owner has execute permission
S_IRWXG  //mask for group permissions
S_IRGRP  //group has read permission
S_IWGRP  //group has write permission
S_IXGRP // group has execute permission
S_IRWXO  //mask for permissions for others (not in group)
S_IROTH  //others have read permission
S_IWOTH  //others have write permission
S_IXOTH  //others have execute permission

目录的读权限允许获取该目录中所有文件名的列表,执行权限使可以通过该目录(搜索目录,寻找一个特定文件名)
1. 一个文件的读权限,这与open函数的O_RDONLY和O_RDWR有关
2. 一个文件的写权限,这与open函数的O_WRONLY和O_RDWR有关
3. 为在open函数中创建一个新文件,必须对该目录具有写权限和执行权限
4. 为删除一个文件,必须对包含该文件的目录具有,但对该文件则不需要读,写权限

  • 文件测试
    进程每次打开、创建或删除文件时,内核就进行访问权限测试,而这种测试可能涉及文件的所有者(st_uid,st_gid)、进程的有效ID(euid,egid)以及进程的附加组ID。其中两个所有者ID是文件的性质,两个有效ID和附加组ID是进程的性质。内核进行的测试如下
    (1)若进程euid是root,则干什么都行;
    (2)若进程euid是等于uid,按照相应用户的权限读写;
    (3)若进程的egid或进程的附加组id等于文件gid,按照组相应的权限读写;
    (4)若其他用户适当的访问权限被设置,则允许访问,否则拒接访问;

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

当用open或creat创建文件时
1. 新文件的UID设置为进程的有效用户ID(euid)
2. 组ID可以是进程的有效组ID或它所在目录的组ID
(/var/spool/mail)
如下测试例子

#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>

int main(void)
{
        if (creat("test.txt",0666) < 0)
        {
                perror("create file error");
                exit(1);
        }
        printf("create file success\n");
        exit(0);
}

测试过程

#测试1
$ ./a.out //以david用户执行,euid=uid=david
create file success
$ ll test.txt
-rw-rw-r-- 1 david david 0 Oct 16 09:26 test.txt
$ rm test.txt //删除test.txt
#测试2
$ ./a.out //以root用户执行,euid=uid=root
create file success
$ ll test.txt
-rw-r--r-- 1 root root 0 Oct 16 09:29 test.txt
#测试3
$ chmod u+s a.out //设置SUID
$ rm test.txt //删除test.txt
$ ll a.out
-rwsrwxr-x 1 david david 8666 Oct 16 09:26 a.out
$ ./a.out //以root用户执行,uid=root,euid=david
create file success
$ ll test.txt 
-rw-r--r-- 1 david root 0 Oct 16 09:35 test.txt

从上测试可看出,新创建的文件的uid始终和euid一样。

5. access函数

当用open函数打开一个文件时,内核以进程的有效用户ID和有效组ID为基础执行其访问权限测试,但当一个进程使用SUID或SGID作为另一个用户运行时,可能会需要测试其实际用户(uid)和gid能否访问一个给定的文件,因此需要access函数,access函数是按实际用户ID和实际组ID进行访问测试的。

#include <unistd.h>
int access(const char *pathname, int mode);
/*
成功返回0,失败返回-1
mode常量:
R_OK:测试读权限
W_OK:测试写权限
X_OK:测试执行权限
F_OK:测试文件是否存在
*/

测试例子

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

int main(int argc, char *argv[])
{
    if (argc != 2)
    {   
        printf("uasge: ./aout");
        exit(1);
    }   
    if (access(argv[1],R_OK) < 0)  //测试实际uid能否访问shadow文件
    {   
        perror("access error");
    }   
    else
        printf("read access OK\n");
    if (open(argv[1],O_RDONLY) < 0)  //测试进程的有效id(euid)能否访问shadow文件
    {   
        perror("open error");
        exit(1);
    }   
    else
        printf("open for reading OK\n");
    exit(0);
}

测试1,简单测试功能

# ll a.out
-rwxr-xr-x. 1 david david 5358 Apr 14 06:53 a.out
# ./a.out a.out //一般用户,此时a.out的euid和uid都是david
read access OK
open for reading OK

测试2,更改a.out为root用户

# ll /etc/shadow
-r--------. 1 root root 1047 Mar 13 08:00 /etc/shadow
# ./a.out /etc/shadow // 以david用户执行,uid是david,euid=uid
access error: Permission denied  
open error: Permission denied  
$ chown root a.out //更改a.out用户是root
# ll a.out
-rwxr-xr-x. 1 root david 5338 Apr 14 07:01 a.out
# ./a.out /etc/shadow  //以david用户执行,此时进程的uid是david,euid=uid,也是david
access error: Permission denied
open error: Permission denied
$ ./a.out /etc/shadow  //以root用户执行,此时进程的uid是root,euid=uid,也是root
read access OK
open for reading OK

测试3,设置EUID位

$ chmod u+s a.out  //并打开euid,将用户的进程的有效用户ID(euid)设置为a.out所有者的id,为root
# ll a.out
-rwsr-xr-x. 1 root david 5338 Apr 14 07:01 a.out
# ./a.out /etc/shadow  //以david用户执行,uid是david,euid是root
access error: Permission denied
open for reading OK  
$ ./a.out /etc/shadow  //以root用户执行,此时uid是root,euid是root
read access OK
open for reading OK

注意进程的uid就是执行当前文件的用户(并不是当前用户所有者的ID),而euid一般是等于uid的,但如果设置了euid,则euid等于可执行文件所有者的ID,所以判读一个进程的uid,euid,先看谁执行的进程,则uid就是谁;若没有设置euid,则euid=uid,若设置了euid,euid=可执行文件的所有者ID

6. umask函数

umask函数为进程设置文件创建屏蔽字,并返回以前的值。(少数几个没有出错返回函数的一个),umask设置创建文件时要屏蔽的位

#include <sys/stat.h>
mode_t umask(mode_t cmask);
/*返回值:以前的文件创建屏蔽字*/

当进程创建文件或目录时,一定会使用umask创建屏蔽字,如下栗子

#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>

int main(void)
{
    umask(0);
    if (creat("foo",0666) < 0)
        perror("create error for foo");
    umask(0066);
    if (creat("bar",0666) < 0)
        perror("create error for foo");
    exit(0);
}

测试过程

$ umask  //打印当前mask值
0002
$ ll foo bar
-rw------- 1 zty zty 0 Oct 16 09:42 bar
-rw-rw-rw- 1 zty zty 0 Oct 16 09:42 foo

通常umask命令并不影响父进程(常常是shell)的屏蔽字,所有shell都内置umask命令,用户可以设置umask值控制他们创建文件的默认权限。在写创建新文件程序时,若需确保指定的访问权限位已激活,需在进程运行时修改umask值,否则会继承父进程(通常是shell)的mask值

$ umask -S //打印符号形式
u=rwx,g=rwx,o=rx
$ umask 027 //更改文件模式创建屏蔽字
$ umask -S
u=rwx,g=rx,o=

7. chmod、fchmod

#include <sys/stat.h>
int chmod(const char *path, mode_t mode); //在指定的文件上进行操作
int fchmod(int fildes, mode_t mode); //已打开的文件进行操作
/*成功返回0,失败返回-1*/

为改变一个文件的权限位,进程的euid必须等于文件的所有者id,或进程必须具有root权限

8. chown、fchown、lchown函数

#include <unistd.h>
int chown(const char *path, uid_t owner, gid_t group);
int fchown(int fildes, uid_t owner, gid_t group);
int lchown(const char *path, uid_t owner, gid_t group);
/*成功返回0,失败返回-1*/
  1. 除了引用文件是符号链接外,这三个函数操作相似,在符号链接的情况下,lchown更改符号链接本身的所有者,而不是该符号链接所指向的文件
  2. owner、group中任意一个是-1,则对应ID不变
  3. 谁能更改UID,GID(root?进程拥有者?)

9. 文件长度、文件截短

stat结构成员st_size表示以字节为单位的文件长度;
1. 对目录:文件长度通常是一个数的倍数(16或512);
2. 对符号链接,文件长度是文件名中的实际字节数;
如今UNIX系统大都提供st_blksize(best I/O block size)和st_blocks(number of disk blocks allocated),其中,第一个是对文件I/O较适合的块长度,第二个是所分配的实际512字节块的数量(依赖于版本实现),当我们将st_blksize用于读操作时,读一个文件所需要的时间量最少,为了效率的缘故,标准I/O库也试图以此读、写st_blksize个字节。

  • 文件的空洞
    空洞是由设置的偏移量超过文件尾端,并写了某些数据后造成的。
    截图103页。

这两个函数将现有的文件长度截短为length字节,如将一个文件清空为0

#include <unistd.h>
int truncate(const char *path, off_t length);
int ftruncate(int fildes, off_t length);
/*成功返回0,出错返回-1*/

10. 文件系统

主要讲i节点指向i节点的目录项,说不清。。。

11. link、unlink、remove和rename函数

一个文件可以有多个指向i节点,创建一个指向现有文件链接的函数

int link(const char *oldpath, const char *newpath);
/*成功返回0,失败返回-1*/

不能有指向一个目录的硬链接,因为这样可能在文件系统中存在循环,但是符号链接却可以,见为什么硬链接不能指向目录, 因此很多文件系统不允许对目录实现硬链接
删除一个现有的目录项函数

#include <unistd.h>
int unlink(const char *pathname);
/*成功返回0,失败返回-1*/

该函数删除目录项,并将由pathname所引用的文件链接计数减1;如果pathname是符号链接,那么unlink删除该符号链接,而不会删除由该链接所引用的文件。关闭一个文件时:内核先检查打开该文件的进程数,如果为0,接着检查其链接数,如果也为0,则删除该文件的内容。unlink这种性质经常被程序用来确保即使在该程序崩溃时,它所创建的临时文件也不会遗留下来,用open或create创建一个文件,然后立即调用unlink。因为该文件仍旧是打开的,所以不会对其内容删除,只有当进程关闭或终止时,该文件内容才会被删除。
例子,打开一个文件然后unlink它
这里写图片描述
同样可以用remove函数解除对一个文件或目录的链接,对于文件,remove功能和unlink相同,对于目录,remove功能和rmdir相同。

#include <stdio.h>
int remove(const char *pathname);

文件或目录用rename更名

#include <stdio.h>
int rename(const char *oldpath, const char *newpath);
/*成功返回0,失败返回-1*/
  1. 如果oldname指的是一个文件而不是目录,那么为该文件或符号链接更名;如果newpath存在,则不能是一个目录;若newpath存在且不是目录,则会删除原newpath文件,并将oldpath的名更改为newpath
  2. 如果oldname指的是一个目录,那么为该目录更名;原理同2
  3. 如果oldname指的是一个符号链接,则为符号链接更名,而不是它所引用的文件;
  4. 如果oldname和newname相同,则不做任何处理

12. 符号链接

符号链接是指向一个文件的间接指针,与硬链接不同
1. 硬链接通常要求链接和文件位于同一文件系统汇总
2. 只有具有root权限的进程才能创建指向目录的硬链接
对于符号链接以及它所指向的对象无文件系统的限制,符号链接一般用于将一个文件或整个目录结构移到系统中的另一个位置。
注意

当使用以名字引用文件的函数时,应当了解该函数是否处理符号链接,即是对符号链接处理还是对符号链接指向的文件处理

符号链接引可能在文件系统中引入循环,如下操作

$ mkdir foo
$ touch foo/a
$ ln -s foo foo/foo.soft

这样打不开该符号链接,即foo.soft文件

#file并不存在,但可以创建成功
$ ln -s file file.soft

13. symlink和readlink函数

创建符号链接

#include <unistd.h>
int symlink(const char *oldpath, const char *newpath);
/*成功返回0,失败返回-1*/

由于open函数是跟随符号链接(即指向符号链接的文件,而不是针对符号链接本身操作),因此需要读取链接文件中的名字(并不是读符号链接指向的文件)

#include <unistd.h>
ssize_t readlink(const char *path, char *buf, size_t bufsiz);
/*成功返回到读到的字节数,出错返回-1
buf返回的符号链接内容不以null字符终止
*/

14. 文件的时间,utime函数

每个文件保持有三个时间字段,如图
这里写图片描述
注意修改时间(st_mtime)和更改时间(st_ctime)之间的区别,修改时间是文件内容最后一次被修改的时间,更新状态时间是该文件的i节点最后一次被修改的时间,由上可知,很多影响到i节点的操作,如更改文件的访问权限,uid,连接数,但并没有更改文件的实际内容。i节点中的信息和文件中的实际内容是分开存放的,注意系统并不保存对一个i节点的最后一次访问时间,所以access和stat函数并不更改这三个时间中任何一个

一个文件的访问和修改时间可以用utime函数更改。

15. mkdir和rmdir函数

#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
/*成功返回0,失败返回-1*/

该函数创建一个新的空目录,所指定的文件权限mode由进程的文件模式创建屏蔽字修改

#include <unistd.h>
int rmdir(const char *pathname);
/*成功返回0,失败返回-1*/

该函数可以删除一个空目录(空目录是只包含.和..这两项的目录)

16. 读目录

#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name); //成功返回指针,失败返回NULL
struct dirent *readdir(DIR *dirp); //成功返回指针,失败返回NULL
void rewinddir(DIR *dirp); 
int closedir(DIR *dirp); //成功返回0,失败返回-1
long telldir(DIR *dirp); //返回值:与dp相关联的目录中的当前位置
void seekdir(DIR *dirp, long offset); 

其中头文件

struct dirent
{
    ino_t  d_ino;       File serial number.
    char   d_name[];    Name of entry.
}

例子,递归遍历目录层次结构

 #include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <dirent.h>

#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

int main(int argc, char *argv[])
{
    DIR *dir;
    struct dirent *de;
    if (argc != 2)
    {
        fprintf(stderr,"a single argument is required\n");
        exit(EXIT_FAILURE);
    }
    if ( (dir=opendir(argv[1])) == NULL)
        ERR_EXIT("cant open dir");
    while ( (de=readdir(dir)) != NULL)
    {
        fprintf(stdout,"%s\n",de->d_name);
    }

    closedir(dir);
    exit(EXIT_SUCCESS);
}

17. chdir、fchdir和getcwd函数

以下函数可以更换当前目录

#include <unistd.h>
int chdir(const char *path);
int fchdir(int fd);
/*成功返回0,,失败返回-1*/

由于当前目录是进程的一个属性,所以它只影响调用chdir的进程本身,而不影响其他进程,如下例子

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    if (chdir("/tmp") < 0)
    {   
        perror("chdir");
        exit(1);
    }   
    printf("chdir to /tmp succeeded\n");
    exit(0);
}

执行此程序,并没有切换到/tmp目录,但程序却显示“chdir to /tmp succeeded”;因为shell创建了一个子目录,由该子进程具体执行了次程序;可见若要改变shell进程的工作目录,需shell自己调用chdir函数。

如果内核保持当前目录的信息,直接取出即可;可是内核为每个进程只保存指向该目录v节点的指针等目录本身信息,并不保存目录的完整路径名,因此需要getcwd函数

#include <unistd.h>
char *getcwd(char *buf, size_t size);
/*成功返回buf,失败返回NULL*/

getcwd可以保存当前目录,然后当应用程序进入其他目录时,可返回到原目录;而fchdir则更容易,用open打开当前工作目录,然后保存文件描述符号,当希望返回到原工作目录时,只要将文件描述符传递给fchdir即可。

2015-04-27 18:55:50 u014507230 阅读数 551

此文着重介绍系统是如何表示目录的以及pwd命令的编写。

我们都知道Unix将磁盘分为三部分:超级块(superblock),节点表(inode table)以及数据区。超级块中记录文件系统本身的结构信息。节点表中记录文件的属性,文件系统中每个文件在表中都至少有一个i-节点,表中每个节点的大小相同。数据区就是文件数据真实的存储位置。

简要介绍磁盘知识后,我们来谈谈目录。在Unix中,目录是包含了文件名字列表的特殊文件,其抽象模型是一个包含i-节点号和文件名的表。所以一个目录的内容有两个部分:一个i-节点表和文件名表,其相互对应。对于文件来说,目录是找到该文件的一个索引(链接),本质上就是负责记录文件i-节点表与文件名的对应关系(一个文件可能对应好多个i-节点)。对于open、cat、cp等命令,都是通过在目录中寻找目的文件名,然后根据对应关系获取i-节点号以获取文件属性,最终获取文件内容来实现的。

假设目录demodir中有一个文件y,y文件对应的i-节点号是491。从系统角度来看,目录中有一个包含文件名y和i-节点号为491的入口,也可以说目录demodir中有一个指向i-节点号为491的链接,这个链接所附加的文件名为y。

说来说去这么绕,书上有一句结论概括地很好:目录包含的是文件的引用,每个引用被成为链接。文件的内容存储在数据块,文件的属性被记录在一个被称为i-节点的结构中,i-节点的编号和文件名存储在目录中。就像我前面说的,目录本质上是记录对应关系。

了解了pwd的内核属性后,我们来实现一下pwd。
此程序中使用递归语句实现,从当前目录不断循环深入直到”.”与”..”的i-节点号相同,即代表到达根目录,再由内向外打印出文件夹名。
逻辑不难理解,有一点需要注意,通过stat结构体我们可以获得”.”与”..”目录的i-节点号,我们需要通过节点号获取文件夹名。有点类似于一个trick,通过获得父目录的DIR结构体,取出父目录中包含当前目录在内的文件及文件夹的dirent结构体,通过比对结构体中d_ino字段与当前目录的inode,找到此inode所对应的的d_name即可。因此,程序中chdir必须在num_to_name之前。

/*pwd.c          -print the directory where you in*/

#include "stdio.h"
#include "string.h"
#include "dirent.h"
#include "sys/stat.h"
#include "unistd.h"
#include "stdlib.h"

#define SIZE 100

ino_t get_inode(char *path);
void print_path(ino_t cur_inode);
void num_to_name(ino_t inode, char *name);

int main(int argc, char const *argv[])
{
     ino_t cur_inode;

     cur_inode = get_inode(".");
     print_path(cur_inode);
     printf("\n");
     return 0;
}
ino_t get_inode(char *path){
     struct stat info;

     if(stat(path, &info) == -1){
          perror("Get Inode");
          exit(1);
     }
     return info.st_ino;
}

void print_path(ino_t cur_inode){
     char dname[SIZE];
     ino_t next_inode;

     if(cur_inode != get_inode("..")){
          chdir("..");
          num_to_name(cur_inode, dname);               //必须放在chdir之后,要从父目录中找到innode和文件夹名的对应关系
          next_inode = get_inode(".");
          print_path(next_inode);
          printf("/%s", dname);
     }
}

void num_to_name(ino_t inode, char *name){
     DIR *dir_ptr;
     struct dirent *direntp;

     dir_ptr = opendir(".");
     if(dir_ptr == NULL){
          perror("opendir");
          exit(1);
     }
     while((direntp = readdir(dir_ptr)) != NULL){
          if(direntp->d_ino == inode)
          {
               strcpy(name, direntp->d_name);
               closedir(dir_ptr);
               return;
          }
     }
     perror("Can not fine with the innode");
     exit(1);
}
2011-07-22 12:42:18 NowDoIT 阅读数 2520

Unix/Linux系统,有三个命令比较常用。

1.  chmod 该命令可以更改文件或者目录的属性,比如增加可执行(+x)、为其它用户增加可读等权限。

2.  chgrp 该命令作用是更改文件或者目录所属的组,如 chgrp -R test /home/test 表示将目录 /home/test 下面的文件及子目录的属主改为 test 所有。

3.  chown 该命令作用是更改文件或者目录的属主和属组,如 chown -R test /home/test 表示将目录 /home/test 下面的文件及子目录的属主和属组改为 test。

unix命令行

阅读数 39

没有更多推荐了,返回首页