linux c目录与链接

2018-06-07 13:37:10

相关系统调用:link(),unlink(),rename(),symlink(),readlink(),mkdir(),rmdir(),remove(),opendir(),readdir(),nftw(),getcwd(),chdir(),chroot(),realpath(),dirname(),basename()

硬链接(目录条目)

在文件系统中,目录的存储方式类似于普通文件。目录和普通文件区别有2。

1.i-node记录中,类型不一样
2.目录由特殊组织而成的文件。本质上类似于表格,包含文件名和i-node编号,其实右边的一个条目就相当于一个硬链接

从上图我们可以看出,如果我们创建一个硬链接(/etc/group_solid)指向 /etc/group,那么就相当于在上表中加入一条记录,文件名为 group_solid,inode为282。

[root@izj6cfw9yi1iqoik31tqbgz c]# ll -i a.out
142108 -rwxr-xr-x 1 root root 9008 Jun  7 08:51 a.out

//在这里创建2个硬链接
[root@izj6cfw9yi1iqoik31tqbgz c]# ln a.out a.out.solid
[root@izj6cfw9yi1iqoik31tqbgz c]# ln a.out a.out.solid1


#可以看出这两个文件inode一样,都为 142108
[root@izj6cfw9yi1iqoik31tqbgz c]# ll -i
142108 -rwxr-xr-x 3 root   root   9008 Jun  7 08:51 a.out
142108 -rwxr-xr-x 3 root   root   9008 Jun  7 08:51 
a.out.solid
142108 -rwxr-xr-x 3 root   root   9008 Jun  7 08:51 a.out.solid1

此时我们看书第三列的链接数变为3了,这个链接数除了告诉我们有多少个文件名指向这个数据,还用来判断是否可以移除文件里面的内容。

当我们删除这3个文件中的其中一个的时候,链接数就会 -1,当链接数 <1 时,142108 inode和里面的内容块将会被释放(也就是真的删除了该文件)。

[root@izj6cfw9yi1iqoik31tqbgz c]# rm -f a.out

#此时,只是删除了上图右边的一条记录,文件的inode和内容都还在
#链接数变为2
[root@izj6cfw9yi1iqoik31tqbgz c]# ll -i
142108 -rwxr-xr-x 2 root   root   9008 Jun  7 08:51 a.out.solid
142108 -rwxr-xr-x 2 root   root   9008 Jun  7 08:51 a.out.solid1

#内容还在,链接数变为1
[root@izj6cfw9yi1iqoik31tqbgz c]# rm -f a.out.solid
[root@izj6cfw9yi1iqoik31tqbgz c]# ll -i
142108 -rwxr-xr-x 1 root   root   9008 Jun  7 08:51 a.out.solid1

#内容和inode被释放,因为链接数变为0了
[root@izj6cfw9yi1iqoik31tqbgz c]# rm -f a.out.solid1

硬链接的限制有二,均可用符号链接来加以规避

1.因为目录条目(硬链接)对文件指代采用i-node编号,而i-node编号的唯一性仅在同一个文件系统中,所以硬链接不能跨文件系统。
2.不能为目录创建硬链接,避免系统陷入混乱的链接环路。

软连接

软连接 也叫作 符号链接,拥有独立的inode表,所以会占用一个i-node号,只是数据块指针指向的内容是别的文件内容,符号链接不会增加链接计数,所以当一个文件拥有N个符号链接时,还是可以被删除,而这个符号链接继续存在,但是指向的数据块已经找不到,此链接就会失效,称为悬空链接。

软链接解引用

有一个约定:系统调用总是会解引用路径名中的目录部分。因此,在路径/aa/bb/cc中,如果aa,bb为符号链接,则一定会先解引用,而cc则由系统调用内部自己决定是否解引用。

大部分操作会无视符号链接的所有者和权限(创建符号链接时会赋予其所有权限-0777)。是否允许操作反而取决于符号链接所指向的文件的权限来决定。仅当在带有黏着位的目录中对符号链接进行移除或改名操作时,才会考虑符号链接自身的权限。

创建移除(硬)链接

#include <unistd.h>

int link(const char *oldpath, const char *newpath);

//成功返回0, 失败-1

创建硬链接,如果newpath已存在,则报错。不会对符号链接解引用,如果oldpath为符号链接,则会创建符号链接的硬链接。


#include <unistd.h>

int unlink(const char *pathname);

//成功返回0, 失败-1

移除一个链接(删除一个文件名),如果此链接指向文件的最后一个链接,那么还将移除文件本身。不会对符号链接进行解引用,直接删除符号链接。

不能移除目录,想删除目录需使用rmdir或remove()。

仅当关闭所有文件描述符时,方可删除一个已打开的文件

内核除了为每个i-node维护链接计数之外,还对文件的打开文件描述计数。当移除指向文件的最后一个链接时,如果仍有进程持有该文件的打开文件描述符,那么在关闭所有此类描述符之前,系统实际上不会删除该文件。这一特性妙用在于允许取消对文件的链接,而无需担心是否有其他进程已将其打开。

基于这样的事实,我们可以创建临时文件后立马unlink(),然后接下来任然可以通过打开文件描述符继续使用该文件。例如tmpfile()函数。

更改文件名:rename()

#include <stdio.h>

int rename(const char *oldpath, const char *newpath);

//成功返回0, 失败-1

既可以重命名文件,又可以将文件移动到同一文件系统中的另一目录。

调用会将现有的一个路径名oldpath重命名为newpath参数所指定的路径名。 rename() 调用仅操作目录条目,而不移动文件数据。改名既不影响指向该文件的其他硬链接,也不影响持有该文件打开描述符的任何进程,因为这些文件描述符指向的是打开文件描述,与文件名并无瓜葛。 以下规则适用与对rename()的调用。

1.若newpath已经存在,则将其覆盖。
2.若newpath,oldpath指向同一文件,则不发生变化(且调用成功)。
3.rename() 系统调用对其两个参数中的符号链接均不进行解引用。
4.重命名目录必须保证newpath不存在或者是一个空目录。

符号链接

#include <unistd.h>

int symlink(const char *filepath, const char *linkpath);

//成功返回0, 失败-1

创建符号链接,可以是绝对路径,也可以是相对路径。


#include <unistd.h>

ssize_t readlink(const char *pathname, char *buffer, size_t bufsiz);

//成功返回存入buffer的字节数,失败-1

读取符号链接指向的文件名,bufsiz是一个整形参数,告知readlink调用buffer中可用的字节数,buffer尾部并未放置终止空字符。

#include <stdio.h>
#include <string.h>
#include <unistd.h>

int
main(int argc, char *argv[]){
    char bf[20];
    memset(bf, 0, 20);
    ssize_t sum = readlink("aaa", bf, 20);
    
    printf("%d %s\n", sum, bf);
}
[root@izj6cfw9yi1iqoik31tqbgz c]# ll
lrwxrwxrwx 1 root   root      3 Jun  7 11:19 aaa -> a.c
-rw-r--r-- 1 nobody nobody  191 Jun  6 23:02 a.c

[root@izj6cfw9yi1iqoik31tqbgz c]# gcc main.c 
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out
3 a.c

创建移除目录

#include <sys/stat.h>

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

//成功返回0, 失败-1


#include <unistd.h>

int rmdir(const char *pathname);

//成功返回0, 失败-1

只能删除空目录。

移除文件或目录

#include <stdio.h>

int remove(const char *pathname);

//成功返回0, 失败-1

如果pathname是文件,则调用unlink();如果是目录,调用rmdir();

读目录:opendir(),readdir()

本节所述的库函数可以打开一个目录,并逐一获取其包含的文件名称。

#include <dirent.h>

DIR *opendir(const char *dirpath);

//成功返回stream句柄,失败返回NULL

DIR *fdopendir(int fd);

//成功返回stream句柄,失败返回NULL

返回目录流,目录流将指向目录中首条记录。


#include <dirent.h>

struct dirent{
    ino_t d_ino; /* File i-node number */
    char d_name[]; /* Null-terminated name of file */
};

struct dirent *readdir(DIR *dirp);

//成功返回静态分配的内存结构,失败或目录结尾返回NULL

每调用一次readdir(),就会从dirp所指代的目录流中读取下一目录条目,并返回静态分配的结构体 dirent,每次调用都会覆盖该结构。未做排序处理,按照天然次序。如果内容在readdir()之间发生变化,那么可能无法察觉到。

errno = 0;
direntp = readdir(dirp);
if (direntp == NULL) {
    if (errno != 0) {
        /* 处理错误 */
    } else {
        /* 读到目录结尾了 */
    }
}


将目录流移到开始处

#include <dirent.h>

void rewinddir(DIR *dirp);

关闭流

#include <dirent.h>

int closedir(DIR *dirp);

返回目录流相关联的文件描述符

#include <dirent.h>

int dirfd(DIR *dirp);

可重入函数readdir_r(),可重入和非可重入将后面文章讲解。

#include <dirent.h>
int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);

//成功返回0, 失败返回正整数

readdir_r()将下一项目录条目置于entry指向的dirent结构体,并让result指向该结构体,到达文件结尾result中返回NULL。

文件数遍历:nftw()

nftw()函数循序程序对整个目录及子目录进行遍历,并为子树中每个文件执行某些操作(程序员自定义函数)。

#define _XOPEN_SOURCE 500
#include <ftw.h>

int nftw(const char *dirpath,
int (*func) (const char *pathname, const struct stat *statbuf,
int typeflag, struct FTW *ftwbuf),
int nopenfd, int flags);

//成功返回0, 错误发生或者func返回非0时返回非0

默认情况下对各目录的处理要先于各目录下的文件和子目录,参数nopenfd 为打开最多的文件描述符,如果超过了就会先关闭之前的再打开来避免持有过多的文件描述符。现代操作系统一般可以指定10+。

flags 参数有0个或多个常量相或(|):

FTW_CHDIR
在处理目录内容之前先调用 chdir()进入每个目录。

FTW_DEPTH
nftw()会在对目录本身执行func之前先对目录中的所有文件执行func调用。

FTW_MOUNT
不会越界到另一文件系统,也就是如果里面有目录时挂载点,则不会对其进行遍历。

FTW_PHYS
默认情况下,nftw() 对符号链接进行解引用操作。加上这个标志告知nftw()函数不要这么做。相反,func会把typeflag值置为FTW_SL。

nftw() 为每个文件调用func 时传入4个参数。第一个参数是路径名,可以输绝对路径或者是相对路径。如果dirpath是绝对路径,pathname可能是相对路径。反之如果指定dirpath使用的相对路径,则pathname中的路径可能是相对于进程当前工作目录而言。第二个参数statbuf是指针指向stat结构。第三个参数typeflag提供了有关该文件的深入信息,特征如下:

FTW_D    目录

FTW_DNR    不能读取的目录

FTW_DP    当前项是目录,其所包含的文件和子目录已经处理完毕。

FTW_F    除目录和符号链接以外的类型

FTW_NS    对改文件调用stat() 失败。

FTW_SL    符号链接,仅当使用FTW_PHYS 标志调用才返回。

FTW_SLN    失效符号链接(悬空符号链接),仅当使用FTW_PHYS 标志调用才返回。

第4个参数 ftwbuf 是指针,指向结构如下

struct FTW {
    int base; /* Offset to basename part of pathname */
    int level; /* Depth of file within tree traversal */
};

base为函数中pathname参数内文件名部分的偏移量。level字段是指该条目相对于遍历起点的深度。

每次调用func都必须返回整形值,返回0继续遍历,非0终止遍历并返回相同的非0值。由于nftw()使用的数据结构是动态内存分配的,故而终止遍历唯一的方法就是让func 调用返回一个非0,如果使用longjmp() 终止会造成内存泄漏。

例子

#define _XOPEN_SOURCE 600       /* Get nftw() and S_IFSOCK declarations */
#include <ftw.h>
#include <sys/types.h>  /* Type definitions used by many programs */
#include <stdio.h>      /* Standard I/O functions */
#include <stdlib.h>     /* Prototypes of commonly used library functions,
                           plus EXIT_SUCCESS and EXIT_FAILURE constants */
#include <unistd.h>     /* Prototypes for many system calls */
#include <errno.h>      /* Declares errno and defines error constants */
#include <string.h>     /* Commonly used string-handling functions */

static void
usageError(const char *progName, const char *msg){
    if (msg != NULL)
        fprintf(stderr, "%s\n", msg);
    fprintf(stderr, "Usage: %s [-d] [-m] [-p] [directory-path]\n", progName);
    fprintf(stderr, "\t-d Use FTW_DEPTH flag\n");
    fprintf(stderr, "\t-m Use FTW_MOUNT flag\n");
    fprintf(stderr, "\t-p Use FTW_PHYS flag\n");
    exit(EXIT_FAILURE);
}

static int                      /* Function called by nftw() */
dirTree(const char *pathname, const struct stat *sbuf, int type,
        struct FTW *ftwb)
{
    switch (sbuf->st_mode & S_IFMT) {       /* Print file type */
    case S_IFREG:  printf("-"); break;
    case S_IFDIR:  printf("d"); break;
    case S_IFCHR:  printf("c"); break;
    case S_IFBLK:  printf("b"); break;
    case S_IFLNK:  printf("l"); break;
    case S_IFIFO:  printf("p"); break;
    case S_IFSOCK: printf("s"); break;
    default:       printf("?"); break;      /* Should never happen (on Linux) */
    }

    printf(" %s  ",
            (type == FTW_D)  ? "D  " : (type == FTW_DNR) ? "DNR" :
            (type == FTW_DP) ? "DP " : (type == FTW_F)   ? "F  " :
            (type == FTW_SL) ? "SL " : (type == FTW_SLN) ? "SLN" :
            (type == FTW_NS) ? "NS " : "  ");

    if (type != FTW_NS)
        printf("%7ld ", (long) sbuf->st_ino);
    else
        printf("        ");

    //根据层级缩进
    printf(" %*s", 4 * ftwb->level, "");
    printf("%s %s\n",  pathname, &pathname[ftwb->base]);
    return 0;                                   /* Tell nftw() to continue */
}

int
main(int argc, char *argv[])
{
    int flags, opt;

    flags = 0;
    while ((opt = getopt(argc, argv, "dmp")) != -1) {
        switch (opt) {
        case 'd': flags |= FTW_DEPTH;   break;
        case 'm': flags |= FTW_MOUNT;   break;
        case 'p': flags |= FTW_PHYS;    break;
        default:  usageError(argv[0], NULL);
        }
    }

    if (argc > optind + 1)
        usageError(argv[0], NULL);

    if (nftw((argc > optind) ? argv[optind] : ".", dirTree, 10, flags) == -1) {
        perror("nftw");
        exit(EXIT_FAILURE);
    }
    exit(EXIT_SUCCESS);
}
[root@izj6cfw9yi1iqoik31tqbgz c]# ll
total 24
drwxr-xr-x 2 root   root   4096 Jun  7 11:29 aaa
-rw-r--r-- 1 nobody nobody  191 Jun  6 23:02 a.c
-rw-r--r-- 1 root   root      0 May 16 21:06 a.h
-rwxr-xr-x 1 root   root   9032 Jun  7 16:57 a.out
-rw-r--r-- 1 root   root      0 May 14 11:22 b.c
-rw-rw-r-- 1 root   root   2757 Jun  7 17:03 main.c
prw-r--r-- 1 root   root      0 Jun  6 22:10 tmp_fifo
[root@izj6cfw9yi1iqoik31tqbgz c]# gcc main.c
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out /root/c
d D     142109  /root/c c
- F     142115      /root/c/a.out a.out
- F     142110      /root/c/main.c main.c
- F     142112      /root/c/a.h a.h
- F     142119      /root/c/a.c a.c
p F     142117      /root/c/tmp_fifo tmp_fifo
- F     142114      /root/c/b.c b.c
d D     142108      /root/c/aaa aaa
- F     142118          /root/c/aaa/c.txt c.txt
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out /root/c -d
- F     142115      /root/c/a.out a.out
- F     142110      /root/c/main.c main.c
- F     142112      /root/c/a.h a.h
- F     142119      /root/c/a.c a.c
p F     142117      /root/c/tmp_fifo tmp_fifo
- F     142114      /root/c/b.c b.c
- F     142118          /root/c/aaa/c.txt c.txt
d DP    142108      /root/c/aaa aaa
d DP    142109  /root/c c

进程当前工作目录

进程的当前工作目录为解析相对路径的起点。新进程的当前工作目录继承自父进程。

#include <unistd.h>
char *getcwd(char *cwdbuf, size_t size);

//成功返回指向cwdbuf指针,失败NULL

返回的缓冲区含绝对路径(包括空字符)。调用者必须为 cwdbuf 缓冲区分配至少 size 个字节空间。通常为 PATH_MAX相当。若cwdbuf为NULL,size 为0,那么glibc会按需为你分配缓冲区,为了可移植性考虑,一般不建议这样做。

改变当前的工作目录

可以是绝对路径或者相对路径,如果是符号链接,会解引用。

#include <unistd.h>
int chdir(const char *pathname);

#define _XOPEN_SOURCE 500 /* Or: #define _BSD_SOURCE */
#include <unistd.h>
int fchdir(int fd);

//成功返回0,失败-1

改变进程的根目录:chroot()

进程的根目录用来解释绝对路径时的起点,如果是符号链接会解引用,一旦设置了根目录,就会将应用程序限定于文件系统的特定区域,有时也称为chroot监禁区。

#define _BSD_SOURCE
#include <unistd.h>
int chroot(const char *pathname);

//成功返回0,失败-1

为了安全尽量做到以下
1.通常不要在监禁区文件系统内放置set-user-id-root程序。
2.调用chroot()并未改变进程的当前工作目录,因此,通常在调用chroot()之后调用一次chdir()。如果没有这么做,程序可以利用相对路径访问监禁区外的文件。
3.如果进程对监禁区之外的某一目录持有一打开文件描述符,那么结合fchdir()和chroot()即可越狱成功,为了避免应该关闭所有监禁区外的目录文件描述符。

int fd;
fd = open("/", O_RDONLY);
chroot("/home/mtk");  //设置监禁

//当前工作目录切换到根
fchdir(fd);

chroot(".");    //成功越狱

即使做到了上面几条也不一定安全,比如文件描述符可以通过unix域套接字来传递目录描述符。

解析路径名:realpath()

realpath()库函数对pathname(以空字符结尾的字符串)中的所有符号链接解引用并解析/. 和 /.. 的引用,从而生成以空字符结尾的字符串,内含绝对路径名。

#include <stdlib.h>
char *realpath(const char *pathname, char *resolved_path);

//成功返回指针指向解析的路径缓冲区,错误返回NULL

resolved_path 自己分配,长度至少为PATH_MAX个字节。glibc允许 resolved_path参数为NULL,它会帮你分配PATH_MAX个字节的内存(自行调用free来释放)。

解析路径名字符串:dirname()和basename()

类似于linux命令 dirname,basename

#include <libgen.h>
char *dirname(char *pathname);
char *basename(char *pathname);

//返回空字符结尾的字符串(一般为静态分配的内存)



备注
1.编译器版本gcc4.8,运行环境centos7 64位
2.原文地址http://www.freecls.com/a/2712/4a  

©著作权归作者所有
收藏
推荐阅读
简介
天降大任于斯人也,必先苦其心志。