There are two source files `andy_web.c` and `andy_cfg.c`. `andy_web.c` manages things related to Web pages in the form of program, called `andy_web.cgi` and is invoked by `lighttpd`. In brief, if `get=setting` is received, `andy_web.cgi` will take height and weight from andy's config file by calling functions in `andy_cfg.c`, and show them in pages; if `post=setting` is received, `andy_web.cgi` will get height and weight from `lighttpd` and set them to the config file also by calling functions in `andy_cfg.c`. /*andy_web.c*/ #include "web_api.h" #include "andy_cfg.h" #define MIN_HEIGHT 150 #define MAX_HEIGHT 250 #define MIN_WEIGHT 50 #define MAX_WEIGHT 150 static andy_config_t* andy_config = NULL; static web_t web = {0}; static void andy_web_page_show(void); static int andy_web_page_setting_check_then_save(char Msg[], uint16_t MsgSize); int main(void) { andy_config = andy_config_init(180, 80); if (ANDY_CFG_FAILED == andy_config) { return -1; } web_get(&web); const uint8_t METHOD = web.nv_ct - 1; if (!strcmp(web.nameval[METHOD].name, "get")) { if (!strcmp(web.nameval[METHOD].value, "setting")) { andy_web_page_show(); } goto DONE; } char Msg[100] = {0}; if (!strcmp(web.nameval[METHOD].name, "post")) { if (!strcmp(web.nameval[METHOD].value, "setting")) { if (andy_web_page_setting_check_then_save(Msg, sizeof(Msg)) == -1) { goto SETTING_CHECK_ERROR; } } andy_web_page_show(); goto DONE; } SETTING_CHECK_ERROR: printMsg(Msg); DONE: andy_config_close(andy_config); return 0; } static void andy_web_page_show(void) { printf("Content-Type: text/html\n\n"); printf("<html>\n"); printf("<head>\n"); printf("<meta http-equiv=\"Content-Type\" content=\"text/html;" />\n"); printf("<title>andy Setting</title>\n"); printf("</head>\n"); printf("<body>\n"); printf("<fieldset>\n"); printf("<legend>andy Setting</legend>\n"); printf("<form name=\"andyPage\" method=\"post\" action=\"/cgi-bin/andy_web.cgi\" >\n"); printf(" <table id=\"table1\" border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n"); uint16_t height = 0; if (andy_config_retrive_height(andy_config, &height) != -1) { printf(" <tr>\n"); printf(" <td height=\"20\" width=\"150\" style=\"padding-left:10px\">andy height</td>\n"); printf(" <td height=\"20\" ><input type=\"text\" name=\"height\" style=\"width: 100;\" value=\"%d\" />(%d~%d)</td>\n", height, MIN_HEIGHT, MAX_HEIGHT); printf(" </tr>\n"); } uint16_t weight = 0; if (andy_config_retrive_weight(andy_config, &weight) != -1) { printf(" <tr>\n"); printf(" <td height=\"20\" width=\"150\" style=\"padding-left:10px\">andy weight</td>\n"); printf(" <td height=\"20\" ><input type=\"text\" name=\"weight\" style=\"width: 100;\" value=\"%d\" />(%d~%d)</td>\n", weight, MIN_WEIGHT, MAX_WEIGHT); printf(" </tr>\n"); } printf(" </table>\n"); printf(" <p align=\"center\">\n"); printf(" <input type=\"submit\" value=\" Update \" />\n"); printf(" <input type=\"hidden\" name=\"post\" value=\"setting\" />\n"); printf(" </p>\n"); printf("</form>\n"); printf("</body>\n"); printf("</html>\n"); } static int andy_web_page_setting_check_then_save(char Msg[], uint16_t MsgSize) { int count = 0; for (; count < form.nv_ct-1; count++) { if (!strcmp(form.nameval[count].name, "height")) { uint16_t height = atoi(form.nameval[count].value); if ((height < MIN_HEIGHT) || (height >= MAX_HEIGHT)) { snprintf(Msg, msgSize, "height <%d> should be between %d and %d", height, MIN_HEIGHT, MAX_HEIGHT); return -1; } andy_config_set_height(andy_config, height); } else if (!strcmp(form.nameval[count].name, "weight")) { uint16_t weight = atoi(form.nameval[count].value); if ((weight < MIN_WEIGHT) || (weight >= MAX_WEIGHT)) { snprintf(Msg, msgSize, "wieght <%d> should be between %d and %d", weight, MIN_WEIGHT, MAX_WEIGHT); return -1; } andy_config_set_weight(andy_config, weight); } else { } } andy_config_save(andy_config); return 0; } `andy_cfg.c` manages things related to the config file in the form of functions that are called by `andy_web.c`. Function names beginning with `iniparser`, such as `iniparser_load`, are offered by [iniparser][1] which basically parses ini files and offer abilities to read/write text files in C level. /*andy_cfg.h*/ #ifndef ANDY_CFG_H #define ANDY_CFG_H typedef struct andy_config_t andy_config_t; #define ANDY_CFG_FAILED (andy_config_t*)NULL andy_config_t* andy_config_init(uint16_t height, uint16_t weight); void andy_config_close(andy_config_t* me); void andy_config_save(andy_config_t* me); void andy_config_set_height(andy_config_t* me, uint16_t height); void andy_config_set_weight(andy_config_t* me, uint16_t weight); int andy_config_retrive_height(andy_config_t* me, uint16_t* height); int andy_config_retrive_weight(andy_config_t* me, uint16_t* weight); #endif /*andy_cfg.c*/ #include "iniparser.h" #include "andy_cfg.h" #define ANDY_STARTUP_DIR "/mnt/jffs2/cfg/" #define ANDY_RUNNING_DIR "/var/" #define ANDY_CFG_FILE "andy.ini" andy_config_t* andy_config_init(uint16_t height, uint16_t weight) { if (!file_is_existing(ANDY_STARTUP_DIR""ANDY_CFG_FILE)) { FILE* andy_startup_file = fopen(ANDY_STARTUP_DIR""ANDY_CFG_FILE, "w"); if (NULL == andy_startup_file) { return ANDY_CFG_FAILED; } fprintf(andy_startup_file, "\n" "[Info]\n" "\n" "height = %d ; 150 ~ 250\n" "wieght = %d ; 50 ~ 150\n" "\n", height, weight); fclose(andy_startup_file); } dictionary* andy_running_config = iniparser_load(ANDY_STARTUP_DIR""ANDY_CFG_FILE); if (NULL == andy_running_config) { return ANDY_CFG_FAILED; } return (andy_config_t*)andy_running_config; } void andy_config_close(andy_config_t* me) { if (NULL != me) { andy_config_save(me); iniparser_freedict((dictionary*)me); } } void andy_config_save(andy_config_t* me) { FILE* andy_startup_file = fopen(ANDY_STARTUP_DIR""ANDY_CFG_FILE, "w"); iniparser_dump_ini((dictionary*)me, andy_startup_file); fclose(andy_startup_file); } void andy_config_set_height(andy_config_t* me, uint16_t height) { char str[10] = {0}; snprintf(str, sizeof(str), "%d", height); iniparser_set((dictionary*)me, "Info:height", str); } void andy_config_set_weight(andy_config_t* me, uint16_t weight) { char str[10] = {0}; snprintf(str, sizeof(str), "%d", weight); iniparser_set((dictionary*)me, "Info:weight", str); } int andy_config_retrive_height(andy_config_t* me, uint16_t* height) { int ret = iniparser_getint((dictionary*)me, "Info:height", -1); if (ret == -1) { return -1; } *height = (uint16_t)ret; return 0; } int andy_config_retrive_weight(andy_config_t* me, uint16_t* weight) { int ret = iniparser_getint((dictionary*)me, "Info:weight", -1); if (ret == -1) { return -1; } *weight = (uint16_t)ret; return 0; } Now that I've covered the code, I want to point out some of my perspective about the code. 1. `andy_web.cgi` should only deal with and aware of nothing else but Web matters. 1.1. Would it be a good choice for `andy_web.cgi` that creating a program `andy_cfg` that deals with everything about `andy.ini` and to retrieve/save data from/to it through IPC? If so, under what considerations you will take this approach? Or, one can create a program `cfg` that handles config files not only for andy but everyone. However, this way will not go well because `cfg` will include so many header files such as `andy_cfg.h`, `amy_cfg.h`, `benson_cfg.h`, and end up with maintenance issues. I've seen code somewhere that seems to be designed to solve this maintenance issues. Basically there's one callback function, and all of a sudden it's like the relationship is upside down between caller and callee. The code might look like /*cfg.c*/ typedef void(*register_function_t)(void); cfg_register_set(char* function_name, register_function_t callback_function) { /*Object which wants to use config feature will call this function to register this feature*/ } void cfg_init(void) { /*After registration, all cfg files will be handled in some way.*/ } 1.2 How the above code works? Is there other common practices to deal with this kind of maintenance problem? Please tell me in detail. 2. `andy_cfg.c` seems to violate DRY (don't repeat yourself). This way will end up with so many source files such like `andy_cfg.c`, `amy_cfg.c`, `benson_cfg.c`, etc. All of these codes have everything in common except for the function names that provide literally the same functionality. For example /*amy_cfg.h*/ #ifndef AMY_CFG_H #define AMY_CFG_H typedef struct amy_config_t amy_config_t; #define AMY_CFG_FAILED (amy_config_t*)NULL amy_config_t* amy_config_init(uint16_t height, uint16_t weight); void amy_config_close(amy_config_t* me); void amy_config_save(amy_config_t* me); void amy_config_set_height(amy_config_t* me, uint16_t height); void amy_config_set_weight(amy_config_t* me, uint16_t weight); int amy_config_retrive_height(amy_config_t* me, uint16_t* height); int amy_config_retrive_weight(amy_config_t* me, uint16_t* weight); #endif /*amy_cfg.c*/ #include "iniparser.h" #include "amy_cfg.h" #define AMY_STARTUP_DIR "/mnt/jffs2/cfg/" #define AMY_RUNNING_DIR "/var/" #define AMY_CFG_FILE "amy.ini" amy_config_t* amy_config_init(uint16_t height, uint16_t weight) { if (!file_is_existing(AMY_STARTUP_DIR""AMY_CFG_FILE)) { FILE* amy_startup_file = fopen(AMY_STARTUP_DIR""AMY_CFG_FILE, "w"); if (NULL == amy_startup_file) { return AMY_CFG_FAILED; } fprintf(amy_startup_file, "\n" "[Info]\n" "\n" "height = %d ; 150 ~ 250\n" "wieght = %d ; 50 ~ 150\n" "\n", height, weight); fclose(amy_startup_file); } dictionary* amy_running_config = iniparser_load(AMY_STARTUP_DIR""AMY_CFG_FILE); if (NULL == amy_running_config) { return AMY_CFG_FAILED; } return (amy_config_t*)amy_running_config; } void amy_config_close(amy_config_t* me) { if (NULL != me) { amy_config_save(me); iniparser_freedict((dictionary*)me); } } void amy_config_save(amy_config_t* me) { FILE* amy_startup_file = fopen(AMY_STARTUP_DIR""AMY_CFG_FILE, "w"); iniparser_dump_ini((dictionary*)me, amy_startup_file); fclose(amy_startup_file); } void amy_config_set_height(amy_config_t* me, uint16_t height) { char str[10] = {0}; snprintf(str, sizeof(str), "%d", height); iniparser_set((dictionary*)me, "Info:height", str); } void amy_config_set_weight(amy_config_t* me, uint16_t weight) { char str[10] = {0}; snprintf(str, sizeof(str), "%d", weight); iniparser_set((dictionary*)me, "Info:weight", str); } int amy_config_retrive_height(amy_config_t* me, uint16_t* height) { int ret = iniparser_getint((dictionary*)me, "Info:height", -1); if (ret == -1) { return -1; } *height = (uint16_t)ret; return 0; } int amy_config_retrive_weight(amy_config_t* me, uint16_t* weight) { int ret = iniparser_getint((dictionary*)me, "Info:weight", -1); if (ret == -1) { return -1; } *weight = (uint16_t)ret; return 0; } My instinct told me that I should write type-general functions for `iniparser`, so that I can reuse it. Unfortunately, easier said than done. 2.1 Is writing type-general functions for `iniparser`, in order to solve DRY problem, a good choice in C? Is there other common practices? There once was another way that came into my mind, but I was not sure that it would be practicable. What if I wrapped `iniparser` as a program and let other programmers to specify arguments on command line? In other words, one can create config file for everyone by typing `iniparser someone.ini height=180 weight=80` on the command line, without having to create `someone_cfg.c` repeatedly. Also, for the command line, adding new attribute is much easier than a function approach, because all `iniparser` does is to put arguments on the command line in a file. For example, what if age attribute came around? For program approach, just type `iniparser someone.ini height=180 weight=80 age=32` on the command line; for function approach, `void someone_cfg_set_age(someone_cfg_t* me, uint8_t age);` added, source file re-compile, and re-link. However, this approach shifts `iniparser` 's learning curve from function level to program level. Let alone there may be bugs in the program itself! Furthermore, this approach is only applicable to programmers who knows scripts. 2.2 Is this really a good approach? Is there other common practices? 3. `andy_web.c` should not know details about config file. What if config files were handled in binary files instead of text files? The code would have to re-compile and re-link. Fortunately, this problem can be solved by creating a program `andy_cfg` that is described in 1.1. I spent several hours for this post. Please share your comment in any way! Thank you. [1]: https://github.com/ndevilla/iniparser