Posts Tagged ‘linux’

list.h头文件分析(2)

13 8 月, 2010

Last Update:8/27

Last Update:8/15

9.合并链表

既然我们可以切割链表,那么当然也可以合并了。先看最基本的合并函数,就是将list这个链表(不包括头结点)插入到prev和next两结点之间。这个代码阅读起来不困难,基本上是“见码知意”。

01271static inline void __list_splice(const struct list_head *list,
02272                                 struct list_head *prev,
03273                                 struct list_head *next)
04274{
05275        struct list_head *first = list->next;
06276        struct list_head *last = list->prev;
07277
08278        first->prev = prev;
09279        prev->next = first;
10280
11281        last->next = next;
12282        next->prev = last;
13283}

理解了最基本的合并函数,那么将它封装起来,就可以形成下面两个函数了,分别在head链表的首部和尾部合并。这里的调用过程类似增加,删除功能。

01290static inline void list_splice(const struct list_head *list,
02291                                struct list_head *head)
03292{
04293        if (!list_empty(list))
05294                __list_splice(list, head, head->next);
06295}
07 
08302static inline void list_splice_tail(struct list_head *list,
09303                                struct list_head *head)
10304{
11305        if (!list_empty(list))
12306                __list_splice(list, head->prev, head);
13307}

合并两个链表后,list还指向原链表,因此应该初始化。在上述两函数末尾添加初始化语句INIT_LIST_HEAD(list);后,就安全了。

10.遍历

下面我们要分析链表的遍历。虽然涉及到遍历的宏比较多,但是根据我们前面分析的那样,掌握好最基本的宏,其他宏就是进行“封装”。便利中的基本宏是:

1381#define __list_for_each(pos, head) \
2382        for (pos = (head)->next; pos != (head); pos = pos->next)

head是整个链表的头指针,而pos则不停的往后移动。但是你有没有觉得,这里有些奇怪?因为我们在上篇文章中说过,struct list_head结构经常和其他数据组成新的结构体,那么现在我们只是不停的遍历新结构体中的指针,如何得到其他成员?因此我们需要搞懂list_entry这个宏:

1348#define list_entry(ptr, type, member) \
2349        container_of(ptr, type, member)

这个宏的作用是通过ptr指针获取type结构的地址,也就是指向type的指针。其中ptr是指向member成员的指针。这个list_entry宏貌似很简单的样子,就是再调用container_of宏,可是当你看了container_of宏的定义后……

1443#define container_of(ptr, type, member) ({                      \
2444        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
3445        (type *)( (char *)__mptr - offsetof(type,member) );})

是不是让人有点抓狂?别急,我们一点点来分析。

首先这个宏包含两条语句。第一条:const typeof( ((type *)0)->member ) *__mptr = (ptr);首先将0转化成type类型的指针变量(这个指针变量的地址为0x0),然后再引用member成员(对应就是((type *)0)->member ))。注意这里的typeof(x),是返回x的数据类型,那么 typeof( ((type *)0)->member )其实就是返回member成员的数据类型。那么这条语句整体就是将__mptr强制转换成member成员的数据类型,再将ptr的赋给它(ptr本身就是指向member的指针)。

第二句中,我们先了解offsetof是什么?它也是一个宏被定义在:linux/include/stddef.h中。原型为:

1#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER);

这个貌似也很抓狂,不过耐心耐心:((TYPE *)0)->MEMBER)这个其实就是提取type类型中的member成员,那么&((TYPE *)0)->MEMBER)得到member成员的地址,再强制转换成size_t类型(unsigned int)。但是这个地址很特别,因为TYPE类型是从0x0开始定义的,那么我们现在得到的这个地址就是member成员在TYPE数据类型中的偏移量。

我们再来看第二条语句, (type *)( (char *)__mptr – offsetof(type,member) )求的就是type的地址,即指向type的指针。不过这里要注意__mptr被强制转换成了(char *),为何要这么做?因为如果member是非char型的变量,比如为int型,并且假设返回值为offset,那么这样直接减去偏移量,实际上__mptr会减去sizeof(int)*offset!这一点和指针加一减一的原理相同。

有了这个指针,那么就可以随意引用其内的成员了。关于此宏的更具体了解,不妨亲自动手测试这里的程序。

好了,现在不用抓狂了,因为了解了list_entry宏,接下来的事情就很简单了。

下面这个宏会得到链表中第一个结点的地址。

1359#define list_first_entry(ptr, type, member) \
2360        list_entry((ptr)->next, type, member)

真正遍历的宏登场了,整个便利过程看起来很简单,可能你对prefetch()陌生,它的作用是预取节点,以提高速度。

1367#define list_for_each(pos, head) \
2368        for (pos = (head)->next; prefetch(pos->next), pos != (head); \
3369                pos = pos->next)

我们再来看一开始我们举例的那个便利宏。注意它和上述便利宏的区别就是没有prefetch(),因为这个宏适合比较少结点的链表。

1381#define __list_for_each(pos, head) \
2382        for (pos = (head)->next; pos != (head); pos = pos->next)

接下来这个遍历宏貌似长相和上面那几个稍有不同,不过理解起来也不困难,倒着(从最后一个结点)开始遍历链表。

1389#define list_for_each_prev(pos, head) \
2 390        for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \
3 391                pos = pos->prev)

下面两个宏是上述两个便利宏的安全版,我们看它安全在那里?它多了一个与pos同类型的n,每次将下一个结点的指针暂存起来,防止pos被释放时引起的链表断裂。

1399#define list_for_each_safe(pos, n, head) \
2 400        for (pos = (head)->next, n = pos->next; pos != (head); \
3 401                pos = n, n = pos->next)
4 
5 409#define list_for_each_prev_safe(pos, n, head) \
6 410        for (pos = (head)->prev, n = pos->prev; \
7 411             prefetch(pos->prev), pos != (head); \
8 412             pos = n, n = pos->prev)

前面我们说过,用在list_for_each宏进行遍历的时候,我们很容易得到pos,我们都知道pos存储的是当前结点前后两个结点的地址。而通过list_entry宏可以获得当前结点的地址,进而得到这个结点中其他的成员变量。而下面两个宏则可以直接获得每个结点的地址,我们接下来看它是如何实现的。为了方便说明以及便于理解,我们用上文中的结构struct stu来举例。pos是指向struct stu结构的指针;list是一个双链表,同时也是这个结构中的成员,head便指向这个双链表;member其实就是这个结构体中的list成员。

在for循环中,首先通过list_entry来获得第一个结点的地址;&pos->member != (head)其实就是&pos->list!=(head);它是用来检测当前list链表是否到头了;最后在利用list_entry宏来获得下一个结点的地址。这样整个for循环就可以依次获得每个结点的地址,进而再去获得其他成员。理解了list_for_each_entry宏,那么list_for_each_entry_reverse宏就显而易见了。

1420#define list_for_each_entry(pos, head, member)                          \
2421        for (pos = list_entry((head)->next, typeof(*pos), member);      \
3422             prefetch(pos->member.next), &pos->member != (head);        \
4423             pos = list_entry(pos->member.next, typeof(*pos), member))
5 
6431#define list_for_each_entry_reverse(pos, head, member)                  \
7432        for (pos = list_entry((head)->prev, typeof(*pos), member);      \
8433             prefetch(pos->member.prev), &pos->member != (head);        \
9434             pos = list_entry(pos->member.prev, typeof(*pos), member))

下面这两个宏是从当前结点的下一个结点开始继续(或反向)遍历。

1456#define list_for_each_entry_continue(pos, head, member)                 \
2457        for (pos = list_entry(pos->member.next, typeof(*pos), member);  \
3458             prefetch(pos->member.next), &pos->member != (head);        \
4459             pos = list_entry(pos->member.next, typeof(*pos), member))
5 
6470#define list_for_each_entry_continue_reverse(pos, head, member)         \
7471        for (pos = list_entry(pos->member.prev, typeof(*pos), member);  \
8472             prefetch(pos->member.prev), &pos->member != (head);        \
9473             pos = list_entry(pos->member.prev, typeof(*pos), member))

与上述宏不同的是,这个宏是从当前pos结点开始遍历。

1483#define list_for_each_entry_from(pos, head, member)                     \
2484        for (; prefetch(pos->member.next), &pos->member != (head);      \
3485             pos = list_entry(pos->member.next, typeof(*pos), member))

接下来几个宏又分别是上述几个宏的安全版。安全原因上面已经说过,在此不再赘述。

1list_for_each_entry_safe(pos, n, head, member)
2list_for_each_entry_safe_continue(pos, n, head, member)
3list_for_each_entry_safe_from(pos, n, head, member)
4list_for_each_entry_safe_reverse(pos, n, head, member)

以上即是list.h头文件中的大部分内容分析。关于hash表部分在此暂不分析。

list.h头文件分析(1)

12 8 月, 2010

Last Update: 8/15

双链表的应用在内核中随处可见,list.h头文件集中定义了双链表(struct list_head结构体)的相关操作。比如这里的一个头文件中就有大量的struct list_head型的数据。

关于list.h的分析,网上资料很多,这里只是记录我在分析list.h中遇到的问题。

0.struct list_head结构体

可能这样写,更让我们习惯:

1struct list_head {
2struct list_head *next;
3struct list_head *prev;
4};

这个结构经常作为成员与其他数据类型一起组成一个新的结构体(后文若无特别提示,“新结构体”均指类似下面举例的嵌套型结构体),比如:

1struct stu
2{
3    char name[20];
4    int id;
5    struct list_head list;
6}

我们已经看到,struct list_head这个结构比较特殊,它内部没有任何数据,只是起到链接链表的作用。对于它当前所在的这个结点来说,next指向下一个结点,prev指向上一个结点。通常我们通过指向struc list_head的指针pos来获取它所在结点的地址,尽而获取其他数据。也许你现在还比较困惑这一过程,别着急,后面有特别解释。

1.链表的初始化

其实可以从后往前看,这样更容易理解。INIT_LIST_HEAD函数形成一个空链表。这个list变量一般作为头指针(非头结点)。

128static inline void INIT_LIST_HEAD(struct list_head *list)
229{
330        list->next = list;
431        list->prev = list;
532}

下面的宏生成一个头指针name,如何生成?请看LIST_HEAD_INIT(name)。

125#define LIST_HEAD(name) \
226        struct list_head name = LIST_HEAD_INIT(name)

LIST_HEAD_INIT(name)将name的地址直接分别赋值给next和prev,那么它们事实上都指向自己,也形成一个空链表。现在再回头看宏LIST_HEAD(name),它其实就是一个定义并初始化作用。

123#define LIST_HEAD_INIT(name) { &(name), &(name) }

3.添加元素

这两个函数分别给链表头结点后,头结点前添加元素。前者可实现栈的添加元素,后者可实现队列的添加元素。
static inline void list_add(struct list_head *new, struct list_head *head);
static inline void list_add_tail(struct list_head *new, struct list_head *head);

这两个函数如何实现的?它们均调用的下面函数:

141static inline void __list_add(struct list_head *new,
242                              struct list_head *prev,
343                              struct list_head *next)
444{
545        next->prev = new;
646        new->next = next;
747        new->prev = prev;
848        prev->next = new;
949}

现在我们要关注的是,list_add和list_add_tail两函数在调用__list_add函数时,对应的各个参数分别是什么?通过下面所列代码,我们可以发现这里的参数运用的很巧妙,类似JAVA中的封装。

164static inline void list_add(struct list_head *new, struct list_head *head)
265{
366        __list_add(new, head, head->next);
467}
5 
678static inline void list_add_tail(struct list_head *new, struct list_head *head)
779{
880        __list_add(new, head->prev, head);
981}

注意,这里的形参prev和next是两个连续的结点。这其实是数据结构中很普通的双链表元素添加问题,在此不再赘述。下面的图可供参考,图中1~4分别对应__list_add函数的四条语句。


3.删除元素

这里又是一个调用关系,__list_del函数具体的过程很简单,分别让entry节点的前后两个结点(prev和next)“越级”指向彼此。请注意这个函数的后两句话,它属于不安全的删除。

1103static inline void list_del(struct list_head *entry)
2104{
3105        __list_del(entry->prev, entry->next);
4106        entry->next = LIST_POISON1;
5107        entry->prev = LIST_POISON2;
6108}

想要安全的删除,那么可以调用下面函数。还记得INIT_LIST_HEAD(entry)吗,它可以使entry节点的两个指针指向自己。

1140static inline void list_del_init(struct list_head *entry)
2141{
3142        __list_del(entry->prev, entry->next);
4143        INIT_LIST_HEAD(entry);
5144}

4.替换元素

用new结点替换old结点同样很简单,几乎是在old->prev和old->next两结点之间插入一个new结点。画图即可理解。

1120static inline void list_replace(struct list_head *old,
2 121                                struct list_head *new)
3 122{
4 123        new->next = old->next;
5 124        new->next->prev = new;
6 125        new->prev = old->prev;
7 126        new->prev->next = new;
8 127}

同样,想要安全替换,可以调用:

1129static inline void list_replace_init(struct list_head *old,
2130                                        struct list_head *new)
3131{
4132        list_replace(old, new);
5133        INIT_LIST_HEAD(old);
6134}

5.移动元素

理解了删除和增加结点,那么将一个节点移动到链表中另一个位置,其实就很清晰了。list_move函数最终调用的是__list_add(list,head,head->next),实现将list移动到头结点之后;而list_move_tail函数最终调用__list_add_tail(list,head->prev,head),实现将list节点移动到链表末尾。

01151static inline void list_move(struct list_head *list, struct list_head *head)
02152{
03153        __list_del(list->prev, list->next);
04154        list_add(list, head);
05155}
06156
07 
08162static inline void list_move_tail(struct list_head *list,
09163                                  struct list_head *head)
10164{
11165        __list_del(list->prev, list->next);
12166        list_add_tail(list, head);
13167}

6.测试函数

接下来的几个测试函数,基本上是“代码如其名”。

list_is_last函数是测试list是否为链表head的最后一个节点。

1174static inline int list_is_last(const struct list_head *list,
2175                                const struct list_head *head)
3176{
4177        return list->next == head;
5178}

下面的函数是测试head链表是否为空链表。注意这个list_empty_careful函数,他比list_empty函数“仔细”在那里呢?前者只是认为只要一个结点的next指针指向头指针就算为空,但是后者还要去检查头节点的prev指针是否也指向头结点。另外,这种仔细也是有条件的,只有在删除节点时用list_del_init(),才能确保检测成功。

01184static inline int list_empty(const struct list_head *head)
02185{
03186        return head->next == head;
04187}
05 
06202static inline int list_empty_careful(const struct list_head *head)
07203{
08204        struct list_head *next = head->next;
09205        return (next == head) && (next == head->prev);
10206}

下面的函数是测试head链表是否只有一个结点:这个链表既不能是空而且head前后的两个结点都得是同一个结点。

1226static inline int list_is_singular(const struct list_head *head)
2227{
3228        return !list_empty(head) && (head->next == head->prev);
4229}

7.将链表左转180度

正如注释说明的那样,此函数会将这个链表以head为转动点,左转180度。整个过程就是将head后的结点不断的移动到head结点的最左端。如果是单个结点那么返回真,否则假。

1212static inline void list_rotate_left(struct list_head *head)
2213{
3214        struct list_head *first;
4215
5216        if (!list_empty(head)) {
6217                first = head->next;
7218                list_move_tail(first, head);
8219        }
9220}

上述函数每次都调用 list_move_tail(first, head);其实我们将其分解到“最小”,那么这个函数每次最终调用的都是:__list_del(first->prev,first->next);和__list_add(list,head->prev,head);这样看起来其实就一目了然了。

8.将链表一分为二

这个函数是将head后至entry之间(包括entry)的所有结点都“切开”,让他们成为一个以list为头结点的新链表。我们先从宏观上看,如果head本身是一个空链表则失败;如果head是一个单结点链表而且entry所指的那个结点又不再这个链表中,也失败;当entry恰好就是头结点,那么直接初始化list,为什么?因为按照刚才所说的切割规则,从head后到entry前事实上就是空结点。如果上述条件都不符合,那么就可以放心的“切割”了。

01257static inline void list_cut_position(struct list_head *list,
02258                struct list_head *head, struct list_head *entry)
03259{
04260        if (list_empty(head))
05261                return;
06262        if (list_is_singular(head) &&
07263                (head->next != entry && head != entry))
08264                return;
09265        if (entry == head)
10266                INIT_LIST_HEAD(list);
11267        else
12268                __list_cut_position(list, head, entry);
13269}

具体如何切割,这里的代码貌似很麻烦,可是我们画出图后,就“一切尽在不言中”了。

01231static inline void __list_cut_position(struct list_head *list,
02232                struct list_head *head, struct list_head *entry)
03233{
04234        struct list_head *new_first = entry->next;
05235        list->next = head->next;
06236        list->next->prev = list;
07237        list->prev = entry;
08238        entry->next = list;
09239        head->next = new_first;
10240        new_first->prev = head;
11241}

图示:

实现cp命令(8)

10 8 月, 2010

问题不断的文件权限!

在实现cp命令(6)(以下简称cp(6))中我们加入了-p选项,即使用cp命令时加入-p才会将源文件的属性复制给目的文件,否则只拷贝文件中的内容。但是我们看看下面cp的运行结果。

cp(6)中不是说只有加-p才会复制源文件的属性吗?怎么cao.c和源文件的属性一模一样?

1gues@huangwei-desktop:~/code/shell_command$ ls -l ch222.c
2-rwxr--r-- 1 gues gues  5327 2010-08-09 20:45 ch222.c
3gues@huangwei-desktop:~/code/shell_command$ cp ch222.c cao.c
4gues@huangwei-desktop:~/code/shell_command$ ls -l cao.c
5-rwxr--r-- 1 gues gues 5327 2010-08-09 20:54 cao.c

为什么此时目的文件的属性又是644(cp(6)中说644是新建文件的默认属性)?

1gues@huangwei-desktop:~/code/shell_command$ ls -l changemode.c
2-rw-rw-r-- 1 gues gues  5327 2010-08-09 10:31 changemode.c
3gues@huangwei-desktop:~/code/shell_command$ cp changemode.c wo.c
4gues@huangwei-desktop:~/code/shell_command$ ls -l wo.c
5-rw-r--r-- 1 gues gues 5327 2010-08-09 20:56 wo.c

起初,当我发现上述结果后,脑中有数个为什么。为什么复制文件的时候,文件属性会这么多变?不断出问题?其实,是因为我们忽略了文件屏蔽字:umask。在新建文件或者目录的时候,新文件的实际存取权限是mode&~umask。比如用open创建(或打开)文件,那mode就是open函数的第三个参数。既然这样,那我们来检验一下上述命令。通过输入umask命令,我们可以看到当前屏蔽字为022。与上述源文件进行mode&~umask运算后,刚好和目的文件的属性一致。那么现在我们终于找到问题的原因所在了。

好了,让我们忘掉cp(6)中所说的一切,重新整理思路:

在使用cp命令的时候,当目的文件不存在时,会新建与源文件同名的新文件。这个新文件的实际权限由:mode&~umask运算后的结果来决定。此时的mode为源文件的权限。而当目的文件存在时,则会保持原目的文件的属性,除非在cp命令中加入-p选项。

好了,我们现在搞清楚cp命令在不加-p选项时候的文件权限问题,至于代码修改,只需修改将open中的第三个参数修改成源文件的权限即可。具体代码实现如下:

1//open the dest file
2if((fdwt=open(dest_path,O_CREAT|O_TRUNC|O_RDWR,src_buf.st_mode))==-1)
3{
4    perror("open_destfile");
5    exit(1);
6}

实现cp命令–文件夹的拷贝

27 7 月, 2010

刚刚完成了my_cp的另一个功能:将一个目录拷贝到指定目录。加上昨天实现的将一个文件拷贝到指定地址下,现在已经完成了我们实现前所定下的要求。也许你会有疑问,那多个文件的拷贝的实现呢?我面前面已经说过,只要完成上述两个功能,并且你在主函数中“分流”正确,那么只要在合适的位置调用这两个函数即可,具体办法我们下面会讨论。

在详解如何实现将一个目录拷贝到指定目录(cp_directory函数)之前,我们首先应该弄明白下面的内容:

1.如果目标目录中的最低级目录不存在,则会新建这个目录,并把源目录中的所有文件拷贝到此新建的目录下。比如cp -r dir ./newdir。我们可以看到./newdir(这个路径中最低级的目录是newdir)在cp前是不存在的,但是cp后新建了这个目录,并且将dir中的所有文件拷贝到这个新建的目录下。

1gues@huangwei-desktop:~/code/shell_command$ ls
2cptest  ls   my_cp   my_cp.c  my_ls_plus    my_shell.c    nothisdirectory  tdir         test
3dir     ls1  my_cp1  my_ls.c  my_ls_plus.c  newdirectory  nothisfile       tdirmy_ls.c  ttfile.c
4gues@huangwei-desktop:~/code/shell_command$ ls dir
5ed  my_cp1  test  ttfile.c
6gues@huangwei-desktop:~/code/shell_command$ cp -r dir ./newdir
7gues@huangwei-desktop:~/code/shell_command$ ls newdir
8ed  my_cp1  test  ttfile.c

2.如果最低级的目标目录存在,则会将源目录(当然也包含源目录下的所有文件)拷贝到这个目标目录。我们仍执行上面那个命令:cp -r dir ./newdir。但是这次结果是不一样的,由于1的操作,newdir目录已经存在,这次cp后将dir目录拷贝到了已存在的newdir目录下(即./newdir/dir/)。

1gues@huangwei-desktop:~/code/shell_command$ ls newdir
2ed  my_cp1  test  ttfile.c
3gues@huangwei-desktop:~/code/shell_command$ cp ./dir -r ./newdir
4gues@huangwei-desktop:~/code/shell_command$ ls newdir
5dir  ed  my_cp1  test  ttfile.c

如果我说的还不够明白,你也可以自己亲自验证一下cp命令。

下面我们来详解。还是先保留传递过来的路径。然后如果源文件夹不包含/,则添加。

01void cp_directory(char* original_src_path,char* original_dest_path)
02{
03    struct stat buf;
04    DIR *dir;
05    struct dirent *ptr;
06    char path[PATH_MAX+1];
07    char src_path[PATH_MAX+1],dest_path[PATH_MAX+1];
08 
09    strcpy(src_path,original_src_path);
10    strcpy(dest_path,original_dest_path);
11 
12    if(src_path[strlen(src_path)-1]!='/')
13    {
14        strncat(src_path,"/",1);
15    }
16        //the following code be omited
17}

如果目标目录中最低级的目录不存在,则创建它。如果次低级目录也不存在,则在创建的时候就发生错误。如果目标目录存在,并且是目录文件,那么就如同上面举例2中所述,我们需要将源路径中最低级的目录拷贝到目标目录中。这里面设计到提提取源路径最低级的目录,以及将其连接在目标目录后等。这些都不难理解。注意当完成目标路径的拼接后,如果这个目录本身就存在,那么我们将其删除,创建新目录。

01if(stat(dest_path,&buf)==-1)
02    {
03        //create a directory which name is dest_path
04        stat(src_path,&buf);
05        if(mkdir(dest_path,buf.st_mode)==-1)
06        {
07            printf("my_cp:create the directory \"%s\" error.\n",dest_path);
08            return ;
09        }
10    }
11    else
12    {
13        //exist
14        if(!S_ISDIR(buf.st_mode))
15        {
16            printf("my_cp:the directory \"%s\" can't cover the no-directory \"%s\".\n",src_path,dest_path);
17            return ;
18        }
19        else
20        {
21            if(dest_path[strlen(dest_path)-1]!='/')
22            {
23                strncat(dest_path,"/",1);
24            }
25            //extract the lowest directory
26            int i,k=0;
27            char lowestdir[PATH_MAX+1];
28            for(i=strlen(src_path)-1-1;i>\0;i--)
29            {
30                if(src_path[i]=='/')
31                {
32                    i=i+1;
33                    break;
34                }
35            }
36 
37            for(;i<\strlen(src_path);i++)
38            {
39                lowestdir[k++]=src_path[i];
40            }
41            strncat(dest_path,lowestdir,strlen(lowestdir));
42            struct stat temp_buf;
43            char temp_path[PATH_MAX+1]="rm -rf ";
44            if(stat(dest_path,&temp_buf)==0)
45            {
46                strcat(temp_path,dest_path);
47                system(temp_path);
48            }
49                    if(mkdir(dest_path,buf.st_mode)==-1)
50                {
51                printf("my_cp:create the directory \"%s\" error.\n",dest_path);
52                    return ;
53                }
54        }
55    }

接着我们打开源目录,读取其下的所有文件名。这个方法在my_ls的时候就已经使用过。我们将这些文件名与目的路径拼接后,检查他们是否是目录文件。如果是普通文件那么就调用cp_single函数,否则调用cp_directory函数。

01if((dir=opendir(src_path))==NULL)
02{
03    printf("my_cp:open the srouce path \"%s\" error.\n",src_path);
04    return ;
05}
06char temp_dest_path[PATH_MAX+1];
07strcpy(temp_dest_path,dest_path);
08while((ptr=readdir(dir))!=NULL)
09{
10    if(!strcmp(ptr->\d_name,"."))
11        continue;
12    if(!strcmp(ptr->\d_name,".."))
13        continue;
14    strcpy(path,src_path);
15    strcat(path,ptr->\d_name);
16    if(stat(path,&buf)==-1)
17    {
18        printf("my_cp:open the file \"%s\" error.\n",path);
19        return ;
20    }
21    strcpy(dest_path,temp_dest_path);
22    //get the right dest_path
23    if(S_ISDIR(buf.st_mode))
24    {
25        cp_directory(path,dest_path);
26    }
27    else
28    {
29        cp_single(path,dest_path);
30    }
31}

其实这是一个递归的过程,对于递归,最重要的是能返回到调用函数。对于任何目录,最终要么这个目录是空的,要么全是普通文件,所以肯定能返回到上一级函数中,不会无限的去嵌套。

以上就是my_cp函数的实现过程,需要源码的同学留下邮箱即可。如果发现了不妥之处,欢迎指正。

实现cp命令–单个文件的拷贝

26 7 月, 2010

昨天我们主要从主函数入手,对命令行参数进行合法性检测,并引导主程序进入相应的子函数。今天我们要实现一个最基本的复制功能,将一个源文件复制到指定路径。之所以说路径,是因为目的文件可能是一个存在的文件,也可能是一个不存在的文件或者是一个目录(不存在的目录会出错)。在我们详细分析代码前,先看看我做的这个my_cp的运行结果吧。
1.成功将一个已存在源文件复制到另一个指定文件名的文件中。

01gues@huangwei-desktop:~/code/shell_command$ ls
02cptest  dd  dd1  ed  ls  ls1  my_cp  my_cp1  my_cp.c  my_ls.c  my_shell.c  newls.c  tdir  test  tfile.c
03gues@huangwei-desktop:~/code/shell_command$ ./my_cp tfile.c ttfile.c
04gues@huangwei-desktop:~/code/shell_command$ ls -l
05总用量 124
06-rw-r--r-- 1 gues gues  7378 2010-06-22 23:58 my_ls.c
07-rw-r--r-- 1 gues gues  6271 2010-07-17 14:29 my_shell.c
08-rw-r--r-- 1 gues gues  7378 2010-07-25 17:20 newls.c
09drwxr-xr-x 2 gues gues  4096 2010-07-25 18:03 tdir
10drwxr-xr-x 3 gues gues  4096 2010-07-25 18:03 test
11-rw-r--r-- 1 gues gues  6271 2010-07-25 16:35 tfile.c
12-rw-r--r-- 1 gues gues  6271 2010-07-26 10:14 ttfile.c

2.将已存在的源文件拷贝到一个不存在的目录下,会提示错误信息。

1gues@huangwei-desktop:~/code/shell_command$ ./my_cp tfile.c ~/nothisdirectory/
2my_cp:can't create the file:"/home/gues/nothisdirectory/":it is a directory.

3.将不存在的源文件拷贝到一个目录或文件中,提示相应错误。这里的目标文件或指定目录是否存在不确定。因为只有一个源文件时,cp命令总先检查源文件是否存在。

1gues@huangwei-desktop:~/code/shell_command$ ./my_cp nothisfile ~/nothisdirectory
2my_cp: can't get file status of "nothisfile" : no this file or directory.

4.成功将源文件拷贝到已存在的指定目录,由于指定路径没有文件名,因此目标文件名与源文件名相同。

1gues@huangwei-desktop:~/code/shell_command$ ./my_cp tfile.c ~/
2gues@huangwei-desktop:~/code/shell_command$ ls ~/
3code     Documents  EIOffice               EIOffice_Personal_Lin.tar.gz  Pictures   tfile.c  Yozo_Office
4cptest   Downloads  EIOfficelog.txt        examples.desktop              Public     tmp
5Desktop  edsionte   EIOffice_Personal_Lin  Music

5.之所以首先演示这些结果是因为我们在编写cp_single函数的时候都要考虑到这些情况,加之路径相对灵活可能少一个/就会产生不结果。比如下面的结果:

1gues@huangwei-desktop:~/code/shell_command$ ./my_cp tfile.c ~/nothisdirectory
2gues@huangwei-desktop:~/code/shell_command$ ls ~/
3code     Documents  EIOffice               EIOffice_Personal_Lin.tar.gz  nothisdirectory  Templates  Videos
4cptest   Downloads  EIOfficelog.txt        examples.desktop              Pictures         tfile.c    Yozo_Office
5Desktop  edsionte   EIOffice_Personal_Lin  Music

拷贝成功。这里我们输入的参数仅仅与2中输入的参数少一个/,为什么结果就大不相同?因为2中目标文件是一个不存在的目录(~/nothisdirectory/),而上面的命令是将已存在文件拷贝到已存在目录(~/)下,并且指定文件名为nothisdirectory。
好了,我们下面来分析代码。进入cp_single函数,我们将传递过来的路径拷贝到局部变量src_path和dest_path当中。因为cp_single函数可能在程序的一次运行中被调用多次,如果修改了传递过来的路径(指针)那么会导致下面的调用不正确。如果传递过来的源文件只是一个文件名,那么我们自动为其加上当前路径,这可以方便下面提取文件名。

01void cp_single(char *temp_src_path,char* temp_dest_path)
02{
03    struct stat buf;
04    int len;
05    char ch[10],filename[PATH_MAX+1],dest_dir[PATH_MAX+1];
06    int fdrd,fdwt,i,j,k;
07    char src_path[PATH_MAX+1],dest_path[PATH_MAX+1];
08 
09    strcpy(src_path,temp_src_path);
10    strcpy(dest_path,temp_dest_path);
11    for(k=0;k<\strlen(src_path);k++)
12    {
13        if(src_path[k]=='/')
14        break;
15    }
16    char temp_path[PATH_MAX+1]="./";
17    if(k==strlen(src_path))
18    {
19        strcat(temp_path,src_path);
20            strcpy(src_path,temp_path);
21    }
22 
23        //the following code be omited
24}

接着,从源文件路径中提取文件名。即提取最后一个/符号后面的字符串。

01//extract the file name from src_path
02for(i=strlen(src_path)-1;i>\0;i--)
03{
04    if(src_path[i]=='/')
05        break;
06}
07j=k=0;
08for(j=i;j<\strlen(src_path);j++)
09{
10    filename[k++]=src_path[j];
11}
12filename[k]='\0';

如果目标文件路径存在,并且不含文件名,那么这时候就用到了我们上面提取的源文件名,用strcat连接即可。当然在连接之前还要检查目标文件夹是否包含/,如果包含则删除,否则会连接成这样:existeddir//filename。当不存在此目标路径,我们要检测这个路径末尾这是一个不存在的目录(上述举例2)还是一个已存在目录下不存在的文件(举例5)。我们先找到目标路径中出现的最后一个/,然后检测这个/之前的路径是否存在。比如对于路径:~/existdirectory/nothisdirectory/nothisfile。我们需要检测的是~/existdirectory/nothisdirectory/是否存在,若不存在那就显示出错信息。如果存在,那么按照完整路径:~/existdirectory/nothisdirectory/nothisfile打开文件即可。实现代码如下:

01//check the if dest path has exsited
02if(stat(dest_path,&buf)==0)
03{
04    //the dest_path exsited
05    if(S_ISDIR(buf.st_mode))
06    {
07        if(dest_path[strlen(dest_path)-1]=='/')
08            dest_path[strlen(dest_path)-1]='\0';
09        strcat(dest_path,filename);
10    }
11}
12else
13{
14    //the dest_path didn't exsit
15    for(i=strlen(dest_path)-1;i>=0;i--)
16    {
17        if(dest_path[i]=='/')
18            break;
19    }
20    if(i>=0)
21    {
22        strncpy(dest_dir,dest_path,i+1);
23            if(stat(dest_dir,&buf)==-1)
24                 {
25                printf("my_cp:accessing:\"%s\" :it is't a directory.\n",dest_path);
26                exit(1);
27                }
28    }
29 
30}

下面是cp命令和本程序运行结果的比较。

1gues@huangwei-desktop:~/code/shell_command$ ./my_cp tfile.c ~/nothisdirectory/nothisfile
2my_cp:accessing:"/home/gues/nothisdirectory/nothisfile" :it is't a directory.
3gues@huangwei-desktop:~/code/shell_command$ cp tfile.c ~/nothisdirectory/nothisfile
4cp: 正在访问"/home/gues/nothisdirectory/nothisfile": 不是目录

完成上述功能,便进行真正的拷贝了。我们不仅要拷贝源文件的内容,还要拷贝相关文件属性,比如存取权限,用户ID,用户组ID等。下面的代码便是实现上述功能。如果你完成了my_ls,下面的代码并不困难理解,在此不在赘述。

01//fistly the content which was read from srouce file will be write to dest file
02if((fdrd=open(src_path,O_RDONLY))==-1)
03{
04    perror("open");
05    exit(1);
06}
07if(lseek(fdrd,0,SEEK_END)==-1)
08{
09    perror("lseek");
10    exit(1);
11}
12if((len=lseek(fdrd,0,SEEK_CUR))==-1)
13{
14    perror("lseek");
15    exit(1);
16}
17if(lseek(fdrd,0,SEEK_SET)==-1)
18{
19    perror("lseek");
20    exit(1);
21}
22//open the dest file
23if((fdwt=open(dest_path,O_CREAT|O_TRUNC|O_RDWR,S_IRWXU))==-1)
24{
25    perror("open");
26    exit(1);
27}
28close(fdwt);
29if((fdwt=open(dest_path,O_WRONLY|O_APPEND))==-1)
30{
31    perror("open");
32    exit(1);
33}
34 
35while(len-->\0)
36{
37    //write all characters to dest file
38    if(read(fdrd,ch,1)!=1)
39    {
40        perror("read");
41        exit(1);
42    }
43    if(write(fdwt,ch,1)!=1)
44    {
45        perror("write");
46        exit(1);
47    }
48 
49}
50 
51//get src file's attributes
52if(fstat(fdrd,&buf)==-1)
53{
54    perror("fstat");
55    exit(1);
56}
57//set the dset file's access right
58if(fchmod(fdwt,buf.st_mode)==-1)
59{
60    perror("fchmod");
61    exit(1);
62}
63//set file's user id and group id
64if(fchown(fdwt,buf.st_uid,buf.st_gid)==-1)
65{
66    perror("fchown");
67    exit(1);
68}
69close(fdwt);
70close(fdrd);

现在基本上完成了最基本的拷贝功能。如果上述代码有问题,欢迎留言指正。

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key