0x0D-单线程备份(上)

写在最前方

  • 源路径:即 From-Path,你准备要备份的资料
  • 目的路径: 即 To-Path,你准备要存储备份的资料的地方
  • 稍微回想一下,上一次写的代码,本次的任务是遍历目录及其子目录,那么这回要干的就是将上次遍历过的数据,挪一下窝,到我们想要他们去的位置。
  • 这涉及到两个操作,遍历拷贝,前一个动作我们在上一回已经实现了,只需做小小的改动,就能够使用。后一个动作也是需要靠 Windows API来完成,至于哪些,稍后再提。
  • 现在先让我们完成一个魔法,3, 2, 1!

    1. do{
    2. puts("-------------------------------------------------");
    3. fprintf(stdout, "The Default Path is : %s \n", DEFAULT_TO_PATH);
    4. fprintf(stdout, "Now The Path is : %s \n", get_backup_topath());
    5. puts("-------------------------------------------------");
    6. puts("That is a System Back Up Software for Windows! ");
    7. puts("List of the software function : ");
    8. puts("1. Back Up ");
    9. puts("2. Set Back Up TO-PATH ");
    10. puts("3. Show TO-PATH History");
    11. puts("4. Read Me ");
    12. puts("5. Exit ");
    13. puts("-------------------------------------------------");

    对界面稍微有了一些改动。

    新增了第三行和第四行的 系统默认目的路径和当前使用的目的路径。

    新增了倒数第四行的查看目的路径历史纪录的功能。

    main函数外头需要 extern DEFAULT_TO_PATH;因为引用了setPath.c里的一个全局变量。

写在中间

  • 前一次我们曾经提到要让函数的功能更加清晰,为了达到这个目的,应该把可能用到的一些原生库函数包裹一下,让可能发生的错误尽量掌握在我们自己的手里

  • 安全函数

    • 新建 safeFunc.h safeFunc.c
    • 考虑一下我们需要包裹的函数: mallocfreefopen 三个库函数。

      • 为了不让后方的多线程实现产生更多的以后,不单独使用全局错误输出。
      • 让我来将他们实现一下
      • 我不会省略一些看似不必要的东西,例如注释,而是完整的呈现出来,如果觉得篇幅过长,可以选择跳跃的阅读。
      • 魔法来了,3, 2, 1!

        1. #include <stdio.h> /* size_t */
        2. #include <stdlib.h>
        3. #include <setjmp.h>
        4. #define TRY_TIMES 3
        5. typedef struct _input_para{
        6. char * file; /* 待打开或创建的文件名 */
        7. char * mode; /* 打开的模式 */
        8. }params;
        9. jmp_buf malc_jmp; /*Malloc_s*/
        10. jmp_buf fopn_jmp; /*Fopen*/
        11. /**
        12. * @version 1.0 2015/10/01
        13. * @author wushengixin
        14. * @param ... 参看结构体说明
        15. 可传入任意的个数的,形式为 .file = "xxx", .mode = "x" 的参数
        16. * function 用于使用默认参数,并调用函数 Fopen 进行打开操作
        17. */
        18. #define Fopen_s(...) Fopen((params){.file = NULL, .mode = "r", __VA_ARGS__})
        19. FILE* Fopen(const params file_open);
        20. /**
        21. * @version 1.0 2015/10/01
        22. * @author wushengxin
        23. * param sizes 输入需要分配的大小
        24. * function 用于隐藏一些对错误的处理,并调用malloc库函数分配空间
        25. */
        26. void * Malloc_s(size_t sizes);
        27. /**
        28. * @version 1.0 2015/10/01
        29. * @author wushengxin
        30. * @param input 外部传入的等待释放的指针
        31. * function 用于隐藏一些对错误的处理,并调用free库函数进行释放指针
        32. */
        33. void Free_s(void * input);

        里面用到了一些新的特性,如果使用 GCC/Clang作为编译器的,记得要开启-std=c11 支持。

        这几个函数就不再详细解释,而是简略说几个,接下来放上实现代码:

        1. FILE* Fopen(const params file_open)
        2. {
        3. int times = 0;
        4. FILE* ret_p = NULL;
        5. if (file_open.file == NULL)
        6. {
        7. fputs("The File Name is EMPTY! Comfirm it and Try Again", stderr);
        8. return ret_p;
        9. }
        10. setjmp(fopn_jmp); /* fopn_jmp To there */
        11. ret_p = fopen(file_open.file, file_open.mode);
        12. if (ret_p == NULL)
        13. {
        14. if (times++ < TRY_TIMES)
        15. longjmp(fopn_jmp, 0); /* fopn_jmp From here */
        16. fprintf(stderr, "The File : %s Open with Mode (%s) Fail!\n", file_open.file, file_open.mode);
        17. }
        18. return ret_p;
        19. }
        20. void * Malloc_s(size_t sizes)
        21. {
        22. int times = 0;
        23. void * ret_p = NULL;
        24. if (sizes == 0)
        25. return NULL;
        26. setjmp(malc_jmp); /* malc_jmp To There */
        27. ret_p = malloc(sizes);
        28. if (ret_p == NULL)
        29. {
        30. if (times++ < TRY_TIMES) /* malc_jmp From Here */
        31. longjmp(malc_jmp, 0);
        32. fputs("Allocate Memory Fail!", stderr);
        33. }
        34. return ret_p;
        35. }
        36. void Free_s(void * input)
        37. {
        38. if (input == NULL)
        39. {
        40. #if !defined(NOT_DEBUG_AT_ALL)
        41. fputs("Sent A NULL pointer to the Free_s Function!" stderr);
        42. #endif
        43. return;
        44. }
        45. free(input);
        46. input = NULL;
        47. }

        第一个函数是用外部定义的宏 Fopen_s启动它,这里没有实现隐藏它。

        最后一个函数中使用了预处理的机制,如果在头文件中定义了 #define NOT_DEBUG_AT_ALL,这个输出将不在出现

  • 安全函数已经撰写完成,接下来就是干正事了

    • setPath.h

      • 我们首先要将程序里保存上默认的目的路径,首先想到用常量#define ...
      • 其次应该要确保当前目的路径不被其他非法的渠道访问,那就应该用一个static 字符数组存储。
      • 接下来就是要提供一个函数当作接口(这里用了接口这个术语不知道合不合适),来获取当前实际在使用的目的路径 get_backup_topath
      • 这里还需要将之前实现过的 repl_str ,再次实现一次,因为之前的显示功能只是测试,并不会实际应用到程序当中。
      • 完成这两个功能函数以后,再去考虑实现怎么样设置路径存储路径,以及使用文件流操作来缓存历史目的路径

        1. #include "safeFunc.h"
        2. #define SELF_LOAD_DEFAULT_PATH "C:/"
        3. #define MIN_PATH_NAME _MAX_PATH /* 最小的限制 */
        4. #define LARGEST_PATH_NAME 32767 /* 路径的最大限制 */
        5. /*
        6. * @version 1.0 2015/10/02
        7. * @author wushengxin
        8. * @function 用于返回当前使用的目的路径
        9. */
        10. const char * get_backup_topath();
        11. /**
        12. * @version 1.0 2015/09/28
        13. * @author wushengxin
        14. * @param src 外部传入的,用于调整
        15. * @function 用于替换路径中的 / 为 \ 的
        16. */
        17. void repl_str(char * src);

        对应的实现中,会定义一个静态的字符数组,且在头文件中能够看见,很多是在showFiles里定义过的。

        定义过的函数,例如 repl_str需要把showFiles.c中的实现,使用#if 0 ... #endif 进行注释掉,不然会发生重定义的错误。

      • setPath.c

        1. #include "setPath.h"
        2. static char to_path_buf[LARGEST_PATH_NAME] = SELF_LOAD_DEFAULT_PATH;
        3. const char * DEFAULT_TO_PATH = SELF_LOAD_DEFAULT_PATH;
        4. const int LARGEST_PATH = LARGEST_PATH_NAME;
        5. const char * get_backup_topath()
        6. {
        7. return to_path_buf;
        8. }
        9. void repl_str(char * src)
        10. {
        11. size_t length = strlen(src);
        12. for (size_t i = 0; i <= length; ++i)
        13. {
        14. if (src[i] == '/')
        15. src[i] = '\\';
        16. }
        17. return;
        18. }
      • 有了上面的代码,主界面就再次能够无误运行了,那么剩下的就是实现,设置目的路径,存储目的路径到本地,显示目的路径,分别对应主界面的2, 3

      • 怎么实现比较好,再开始之前,分析一下会遇到的情况:
        • 我们在得到目的路径之后,会将其拷贝给默认路径 to_path_buf,并且将其存储到本地缓存文件中,以便下次程序开始时可以直接使用上一次的路径
        • 还可以使用另一个文件存储所有用过的历史路径,包含时间信息。
      • 那么这就要求我们首先实现存储目的路径的功能,其次再实现设置目的路径的功能,最后实现显示目的路径的功能
      • 注:两个看似无用的全局变量(const)是为了其他文件的可见性而设立的,且相对于#define能够省一些无足轻重的空间。

      • 存储目的路径 store_hist_path

        • setPath.h

          1. #include <time.h>
          2. /**
          3. * @version 1.0 2015/10/02
          4. * @version wushengxin
          5. * @param path 需要存储的路径
          6. * @function 用于存储路径到本地文件 "show_hist" 和 "use_hist"
          7. */
          8. void store_hist_path(const char * path);
        • setPath.c

          1. void store_hist_path(const char * path)
          2. {
          3. time_t ctimes;
          4. time(&ctimes); /* 获取时间 */
          5. FILE* input_use = Fopen_s(.file = "LastPath.conf", .mode = "w"); /* 每次写入覆盖 */
          6. FILE* input_show = Fopen_s(.file = "PathHistory.txt", .mode = "a");
          7. if (!input_show || !input_use)
          8. {
          9. #if !defined(NOT_DEBUG_AT_ALL)
          10. fputs("Open/Create the File Fail!", stderr);
          11. #endif
          12. return;
          13. }
          14. fprintf(input_use, "%s\n", path); /* 写入 */
          15. fprintf(input_show, "%s %s", path, ctime(&ctimes));
          16. fclose(input_show);
          17. fclose(input_use);
          18. return;
          19. }

          timectime 函数的使用网路上的介绍更加全面,这里不做解释。

          完成了存储的函数之后,便是实现从键盘读取并且设置默认路径

      • 设置目的路径 set_enter_path

        • 在此处需要停下来在此思考一下,如果用户输入了错误的路径(无效路径或者恶意路径),也应该被读取吗?所以应该增加一个检查,用于确认路径的有效性。
        • setPath.h

          1. #include <string.h>
          2. #include <io.h> /* _access */
          3. enum {NOT_EXIST = 0, EXIST = 1};
          4. /**
          5. * @version 1.0 2015/10/02
          6. * @author wushengxin
          7. * @function 用于读取从键盘输入的路径并将之设置为默认路径,并存储。
          8. */
          9. void set_enter_path();
          10. /**
          11. * @version 1.0 2015/10/02
          12. * @author wushengxin
          13. * @param path 用于检查的路径
          14. * @function 用于检查用户输入的路径是否是有效的
          15. */
          16. int is_valid_path(const char * path);
        • setPath.c

          1. int is_valid_path(const char * path)
          2. {/* _access 后方有解释 */
          3. if (_access(path, 0) == 0) /* 是否存在 */
          4. return EXIST;
          5. else
          6. return NOT_EXIST;
          7. }
          8. void set_enter_path()
          9. {
          10. int intJudge = 0; /* 用来判断是否决定完成输入 */
          11. char tmpBuf[LARGEST_PATH_NAME]; /** 临时缓冲区 **/
          12. while (1)
          13. {
          14. printf("Enter The Path You want!\n");
          15. fgets(tmpBuf, LARGEST_PATH_NAME*sizeof(char), stdin); /* 获取输入的路径 */
          16. sscanf(tmpBuf, "%s", to_path_buf);
          17. if (is_valid_path(to_path_buf) == NOT_EXIST)
          18. {
          19. fprintf(stderr, "Your Enter is Empty, So Load the Default Path\n");
          20. fprintf(stderr, "%s \n", SELF_LOAD_DEFAULT_PATH);
          21. strcpy(to_path_buf, SELF_LOAD_DEFAULT_PATH);
          22. }
          23. fprintf(stdout, "Your Enter is \" %s \" ?(1 for yes, 0 for no) \n", to_path_buf);
          24. fgets(tmpBuf, LARGEST_PATH_NAME*sizeof(char), stdin);
          25. sscanf(tmpBuf, "%d", &intJudge); /* 获取判断数的输入 */
          26. if (intJudge != 0)
          27. {
          28. if (to_path_buf[strlen(to_path_buf) - 1] != '/')
          29. strcat(to_path_buf, "/");/* 如果最后一个字符不是'/',则添加,这里没考虑是否越界 */
          30. store_hist_path(to_path_buf);
          31. break;
          32. } /* if(intJudge) */
          33. }/* while (1) */
          34. return;
          35. }/* set_enter_path */

          这一组函数的功能稍微复杂,大体来说便是 读取路径输入->检查路径有效性->读取判断数->是否结束循环

          其中_access 函数有些渊源,因为这个函数被大家所熟知的是这个形式 access,但由于这个形式是 POSIX 标准,故 Windows 将其实现为_access,用法上还是一样的,就是名字不同而已。

      • 显示历史路径 show_hist_path

        • setPath.h

          1. /**
          2. * @version 1.0 2015/10/02
          3. * author wushengxin
          4. * function 用于在窗口显示所有的历史路径
          5. */
          6. void show_hist_path();
        • setPath.c

          1. void show_hist_path()
          2. {
          3. system("cls");
          4. char outBufName[LARGEST_PATH_NAME] = {'\0'};
          5. FILE* reading = Fopen_s(.file = "PathHistory.txt", .mode = "r");
          6. if (!reading)
          7. return;
          8. for (int i = 1; i <= 10 && (!feof(reading)); ++i)
          9. {
          10. fgets(outBufName, LARGEST_PATH_NAME*sizeof(char), reading);
          11. fprintf(stdout, "%2d. %s", i, outBufName);
          12. }
          13. fclose(reading);
          14. system("pause");
          15. return;
          16. }
      • 剩下最后一个收尾工作

        • 初始化路径
        • 每次程序启动的时候,我们都会读取本地文件,获取上一次程序使用的最后一个路径,作为当前使用的目的路径
      • 初始化目的路径 init_path

        • setPath.h

          1. /**
          2. * @versions 1.0 2015/10/02
          3. * @author wushengxin
          4. * @function 用于每次程序启动时初始化目的路径
          5. */
          6. void init_path();
        • setPath.c

          1. void init_path()
          2. {
          3. int len = 0;
          4. char last_path[LARGEST_PATH_NAME] = { '\0' };
          5. FILE* hist_file = Fopen_s(.file = "LastPath.conf", .mode = "r");
          6. if (!hist_file) /* 打开失败则不初始化 */
          7. return;
          8. fgets(last_path, LARGEST_PATH_NAME, hist_file);
          9. len = strlen(last_path);
          10. if (len > 1)
          11. {
          12. last_path[len - 1] = '\0'; /* 消除一个多余的 ‘\n’ */
          13. strcpy(to_path_buf, last_path);
          14. }
          15. return;
          16. }

          这样就大功告成了,对于这个函数中的后8行代码,没使用惯用的fgets 配合 sscanf 是因为如果这么干的话,需要搭配一个memset函数清零,后面会有解释。

写在最后方

  • 具体思路代码完全都贴出来了,除了主界面的某些细微区别没有贴出来,但是自己应该能够完成。
  • 对于memset的解释
    • 这个函数对于大的内存块的初始化实际上是很慢的,当然我们这个30KB左右大概的内存可能影响还没有那么大,但是上以后,调用memset就是一种性能问题了,很多情况下,编译器在开启高优化等级之后会自动帮你取消memset的隐式调用
    • 什么隐式调用,例如 init_path第二行代码,声明并且用花括号初始化这个数组的时候,就会调用隐式memset

结束

  • 下一次要实现的就是,本程序的主体 备份