uhttpd main函数分析
int main(int argc, char **argv) { struct alias *alias; /* 设置命令的别名 struct alias { struct list_head list;//命令列表 char *alias;//命令别名 char *path;//命令路径 }; struct list_head { struct list_head *next;//列表下一个对象 struct list_head *prev;//列表前一个对象 }; */ bool nofork = false; /* 进程还没有开始克隆自己 */ char *port; /* 端口 */ int opt; /* 一个在for里面使用的普通变量 */ int ch; /* 一个在while里面使用的普通变量 */ int cur_fd; /* 保存了"/dev/null"句柄 */ int bound = 0; /* 绑定的监听端口个数 */ #ifdef HAVE_TLS /* 当安装了tls插件 */ int n_tls = 0; /* 如果安装了tls,则不为0 */ const char *tls_key = NULL; /* ASN.1 server private key file */ const char *tls_crt = NULL; /* ASN.1 server certificate file */ #endif BUILD_BUG_ON(sizeof(uh_buf) < PATH_MAX); /* 条件为真,编译报错,uh_buf为4096,path_max一般为260 */ /* #define __BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) */ uh_dispatch_add(&cgi_dispatch); /* 添加cgi调度程序到双向链表 */ init_defaults_pre(); /* 初始化配置变量 */ signal(SIGPIPE, SIG_IGN); /* 函数原型:sig_t signal(int signum,sig_t handler); 功能:设置某一信号的对应动作 第一个参数signum指明了所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号。 第二个参数handler描述了与信号关联的动作。 SIGPIPE:在reader中止之后写Pipe的时候发送 SIG_IGN:表示忽略该信号 函数说明: signal()会依参数signum 指定的信号编号来设置该信号的处理函数。 当指定的信号到达时就会跳转到参数handler指定的函数执行。 当一个信号的信号处理函数执行时,如果进程又接收到了该信号, 该信号会自动被储存而不会中断信号处理函数的执行,直到信号处理函数执行完毕再重新调用相应的处理函数。 但是如果在信号处理函数执行时进程收到了其它类型的信号,该函数的执行就会被中断。 */ /* 原型:int getopt(int argc,char * const argv[ ],const char * optstring); 功能:用来分析命令行参数。 参数:argc和argv是由main()传递的参数个数和内容。参数optstring则代表欲处理的选项字符串。 函数说明:此函数会返回在argv 中下一个的选项字母,此字母会对应参数optstring 中的字母。 如果选项字符串里的字母后接着冒号“:”,则表示还有相关的参数,全域变量optarg 即会指向此额外参数。 如果getopt()找不到符合的参数则会印出错信息,并将全域变量optopt设为“?”字符, 如果不希望getopt()印出错信息,则只要将全域变量opterr设为0即可。 */ while (ch = getopt(argc, argv, "A:aC:c:Dd:E:fh:H:I:i:K:k:L:l:m:N:n:p:qRr:Ss:T:t:U:u:Xx:y:") != -1) { /* 每次解释一个命令行传递过来的参数 */ switch(ch) { #ifdef HAVE_TLS /* 如果安装了tls插件,命令行又带相关的参数,执行相应的配置 */ case 'C': tls_crt = optarg; break; case 'K': tls_key = optarg; break; case 'q': conf.tls_redirect = 1; break; case 's': n_tls++; /* fall through */ #else /* 如果没有安装tls插件,命令行又带相关的参数,显示错误 ,但继续执行*/ case 'C': case 'K': case 'q': case 's': fprintf(stderr, "uhttpd: TLS support not compiled, " "ignoring -%c\n", ch); break; #endif case 'p': optarg = strdup(optarg); bound += add_listener_arg(optarg, (ch == 's')); break; case 'h': if (!realpath(optarg, uh_buf)) { fprintf(stderr, "Error: Invalid directory %s: %s\n", optarg, strerror(errno)); exit(1); } conf.docroot = strdup(uh_buf); break; case 'H': if (uh_handler_add(optarg)) { fprintf(stderr, "Error: Failed to load handler script %s\n", optarg); exit(1); } break; case 'E': if (optarg[0] != '/') { fprintf(stderr, "Error: Invalid error handler: %s\n", optarg); exit(1); } conf.error_handler = optarg; break; case 'I': if (optarg[0] == '/') { fprintf(stderr, "Error: Invalid index page: %s\n", optarg); exit(1); } uh_index_add(optarg); break; case 'S': conf.no_symlinks = 1; break; case 'D': conf.no_dirlists = 1; break; case 'R': conf.rfc1918_filter = 1; break; case 'n': conf.max_script_requests = atoi(optarg); break; case 'N': conf.max_connections = atoi(optarg); break; case 'x': fixup_prefix(optarg); conf.cgi_prefix = optarg; break; case 'y': alias = calloc(1, sizeof(*alias)); if (!alias) { fprintf(stderr, "Error: failed to allocate alias\n"); exit(1); } alias->alias = strdup(optarg); alias->path = strchr(alias->alias, '='); if (alias->path) *alias->path++ = 0; list_add(&alias->list, &conf.cgi_alias); break; case 'i': optarg = strdup(optarg); port = strchr(optarg, '='); if (optarg[0] != '.' || !port) { fprintf(stderr, "Error: Invalid interpreter: %s\n", optarg); exit(1); } *port++ = 0; uh_interpreter_add(optarg, port); break; case 't': conf.script_timeout = atoi(optarg); break; case 'T': conf.network_timeout = atoi(optarg); break; case 'k': conf.http_keepalive = atoi(optarg); break; case 'A': conf.tcp_keepalive = atoi(optarg); break; case 'f': nofork = 1; break; case 'd': optarg = strdup(optarg); port = alloca(strlen(optarg) + 1); if (!port) return -1; /* "decode" plus to space to retain compat */ for (opt = 0; optarg[opt]; opt++) if (optarg[opt] == '+') optarg[opt] = ' '; /* opt now contains strlen(optarg) -- no need to re-scan */ if (uh_urldecode(port, opt, optarg, opt) < 0) { fprintf(stderr, "uhttpd: invalid encoding\n"); return -1; } printf("%s", port); return 0; break; /* basic auth realm */ case 'r': conf.realm = optarg; break; /* md5 crypt */ case 'm': printf("%s\n", crypt(optarg, "$1$")); return 0; break; /* config file */ case 'c': conf.file = optarg; break; #ifdef HAVE_LUA /* 如果安装了lua插件,命令行又带相关的参数,执行相应的配置 */ case 'l': conf.lua_prefix = optarg; break; case 'L': conf.lua_handler = optarg; break; #else /* 如果没有安装lua插件,命令行又带相关的参数,显示错误 ,但继续执行*/ case 'l': case 'L': fprintf(stderr, "uhttpd: Lua support not compiled, " "ignoring -%c\n", ch); break; #endif #ifdef HAVE_UBUS /* 如果安装了ubus插件,命令行又带相关的参数,执行相应的配置 */ case 'a': conf.ubus_noauth = 1; break; case 'u': conf.ubus_prefix = optarg; break; case 'U': conf.ubus_socket = optarg; break; case 'X': conf.ubus_cors = 1; break; #else /* 如果没有安装ubus插件,命令行又带相关的参数,显示错误 ,但继续执行*/ case 'a': case 'u': case 'U': case 'X': fprintf(stderr, "uhttpd: UBUS support not compiled, " "ignoring -%c\n", ch); break; #endif default: return usage(argv[0]); /* 没有参数或者有不明参数,显示帮助并退出 */ } } uh_config_parse(); /* 原型:static void uh_config_parse(void) 功能:解析config文件 */ if (!conf.docroot) /* 如果conf结构体里的docroot还没有赋值,就执行 */ { if (!realpath(".", uh_buf)) /* uh_buf为当前工作目录的绝对路径指针 */ { /* 原型:char *realpath(const char *path, char *resolved_path); 功能:用来将参数path所指的相对路径转换成绝对路径 返回值:成功则返回指向resolved_path的指针,失败返回NULL */ fprintf(stderr, "Error: Unable to determine work dir\n"); return 1; } conf.docroot = strdup(uh_buf); /* 结构体conf里的docroot字段保存了当前工作路径 */ /* 原型:extern char *strdup(char *s); 功能: 将字符串拷贝到新建的位置处 返回值:返回一个指针,指向为复制字符串分配的空间;如果分配空间失败,则返回NULL值 */ } init_defaults_post(); /* 初始化主页名和cgi的绝对路径 */ if (!bound) /* 如果没有绑定监听端口,则报错退出 */ { fprintf(stderr, "Error: No sockets bound, unable to continue\n"); return 1; } #ifdef HAVE_TLS /* 如果安装了tls,则执行 */ if (n_tls) /* 安装了tls,n_tls就不为0 */ { if (!tls_crt || !tls_key) /* 没有公匙或者没有私匙,就报错并退出 */ { fprintf(stderr, "Please specify a certificate and " "a key file to enable SSL support\n"); return 1; } if (uh_tls_init(tls_key, tls_crt)) /* 初始化tls,成功返回0 */ return 1; } #endif #ifdef HAVE_LUA /* 如果安装了lua程序,则执行 */ if (conf.lua_handler || conf.lua_prefix) /* 两个变量其中一个有值就执行 */ { /* conf.lua_handler = optarg; //file lua脚本文件 conf.lua_prefix = optarg; //string 默认为'/lua' */ if (!conf.lua_handler || !conf.lua_prefix) /* 两个变量其中一个没值就报错 */ { fprintf(stderr, "Need handler and prefix to enable Lua support\n"); return 1; } if (uh_plugin_init("uhttpd_lua.so")) /* 加载lua动态链接库 */ return 1; } #endif #ifdef HAVE_UBUS /* 如果安装了ubus程序,则执行 */ /* 不是很理解这里,conf.ubus_prefix是否有值对结果不是很重要 */ if (conf.ubus_prefix && uh_plugin_init("uhttpd_ubus.so")) /* 插件初始化成功则会继续执行 */ return 1; #endif /* 程序如果还没有克隆自己,那现在开始克隆 */ if (!nofork) { switch (fork()) { /* 在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。 在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。 我们可以通过fork返回的值来判断当前进程是子进程还是父进程。 */ case -1: /* 如果出现错误,fork返回一个负值 */ perror("fork()"); /* 显示错误 */ /* perror(s) 用来将上一个函数发生错误的原因输出到标准设备(stderr)。 参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。 */ exit(1); /* 1是错误退出 */ case 0: /* 子进程执行这里,守护进程设置 */ /* chdir 系统调用函数(同cd) 原型: int chdir(const char *path ); 功 能:更改当前工作目录。 参 数:Path 目标目录,可以是绝对目录或相对目录。 返回值:成功返回0 ,失败返回-1 */ if (chdir("/")) /* 跳转成功不执行 */ perror("chdir()"); /* 显示错误 */ /* open 系统调用函数 原型:int open(constchar*pathname,intflags); 参数1:pathname 是待打开/创建文件的POSIX路径名 参数2:flags 用于指定文件的打开/创建模式 O_RDONLY只读模式 O_WRONLY只写模式 O_RDWR读写模式 返回值:成功则返回文件描述符,否则返回-1 */ cur_fd = open("/dev/null", O_WRONLY); /* 由open返回的文件描述符一定是最小的未用描述符数值,打开的应该至少是文件描述符3 */ if (cur_fd > 0) { /* 函数名: dup2 功能: 复制文件句柄 用法: int dup2(int oldfd,int newfd); */ dup2(cur_fd, 0); /* 功能是令文件描述符0 指向fd 所指向的文件,即“输入重定向”的功能 */ dup2(cur_fd, 1); /* 功能是令文件描述符1 指向fd 所指向的文件,即“输出重定向”的功能 */ dup2(cur_fd, 2); /* 功能是令文件描述符2 指向fd 所指向的文件,即“错误重定向”的功能 */ } break; default: /* 父进程执行这里 */ exit(0); /* 0是正常退出 */ } } return run_server(); }
主函数用到的函数:
uh_config_parse
/* uhttpd配置文件解析 */ static void uh_config_parse(void) { const char *path = conf.file; /* config文件地址赋值给变量path */ FILE *c; /* 文件流对象c */ char line[512]; /* 大小为512字节的数组 */ char *col1; /* 指向特定字符的指针,可以进行指针运算 */ char *col2; /* 指向特定字符的指针,可以进行指针运算 */ char *eol; /* 代表一个字符数组存储空间的首地址,可以进行指针运算 */ if (!path) /* 如果config文件地址没有定义,则赋默认值 */ path = "/etc/httpd.conf"; c = fopen(path, "r"); /* 打开config文件,只读模式,并将句柄赋给文件流对象c */ if (!c) /* 如果句柄为空,则返回 */ return; memset(line, 0, sizeof(line)); /* 用0填充line数组 */ /* 原型:void *memset(void *s, int ch, size_t n); 解释:将s中当前位置后面的n个字节用 ch 替换并返回 s */ while (fgets(line, sizeof(line) - 1, c)) { /* 从文件结构体指针stream中读取数据,每次读取一行。 原型:char *fgets(char *buf, int bufsize, FILE *stream); 参数1:*buf 字符型指针,指向用来存储所得数据的地址。 参数2:bufsize 整型数据,指明存储数据的大小。 参数3:*stream 文件结构体指针,将要读取的文件流。 返回值:成功,则返回第一个参数buf;失败NULL */ /* 查看了配置文件,好像下面判断里的代码都不会被执行 */ if ((line[0] == '/') && (strchr(line, ':') != NULL)) /* 行开头为'/',并且行里存在字符':',就执行下面 */ { /* 原型:char *strchr(char* _Str,char _Ch) 功能:查找字符串_Str中首次出现字符_Ch的位置 返回值:成功则返回要查找字符第一次出现的位置,失败返回NUL */ /* 下面条件中只要有一个成立,就退出对line数组的操作 */ if (!(col1 = strchr(line, ':')) || /* ':'在line的首地址赋值给col1,然后取反,意思是存在就是变成0,不存在为1 */ (*col1++ = 0) || /* 给col1地址赋0,然后col1指针加1 */ !(col2 = strchr(col1, ':')) || /* ':'在line的第二地址赋值给col2,然后取反,意思是存在就是变成0,不存在为1 */ (*col2++ = 0) || /* 给col2地址赋0,然后col2指针加1 */ !(eol = strchr(col2, '\n')) || /* '\n'在line的地址赋值给eol,然后取反,意思是存在就是变成0,不存在为1 */ (*eol++ = 0)) /* 给eol地址赋0,然后eol指针加1 */ continue; uh_auth_add(line, col1, col2); } else if (!strncmp(line, "I:", 2)) /* 行开头的两个字符为"I:",则执行下面 */ { /* 原型:int strncmp ( const char * str1, const char * str2, size_t num ); 功能:比较str1和str2字符串的前num个字符 返回值:若str1与str2的前n个字符相同,则返回0; 若s1大于s2,则返回大于0的值; 若s1 若小于s2,则返回小于0的值 */ if (!(col1 = strchr(line, ':')) || (*col1++ = 0) || !(eol = strchr(col1, '\n')) || (*eol++ = 0)) continue; uh_index_add(strdup(col1)); } else if (!strncmp(line, "E404:", 5)) /* 行开头的5个字符为"E404:",则执行下面 */ { if (!(col1 = strchr(line, ':')) || (*col1++ = 0) || !(eol = strchr(col1, '\n')) || (*eol++ = 0)) continue; conf.error_handler = strdup(col1); } else if ((line[0] == '*') && (strchr(line, ':') != NULL)) /* 行开头为'*',并且行里存在字符':',就执行下面 */ { if (!(col1 = strchr(line, '*')) || (*col1++ = 0) || !(col2 = strchr(col1, ':')) || (*col2++ = 0) || !(eol = strchr(col2, '\n')) || (*eol++ = 0)) continue; uh_interpreter_add(col1, col2); } } fclose(c); /* 关闭文件流对象 */ }
init_defaults_post
/* 初始化主页名和cgi的绝对路径 */ static void init_defaults_post(void) { /* 把默认主页名列表保存到链接表index_files */ uh_index_add("index.html"); uh_index_add("index.htm"); uh_index_add("default.html"); uh_index_add("default.htm"); /* 配置cgi的一些全局参数 */ if (conf.cgi_prefix) /* option 'cgi_prefix' '/cgi-bin' */ { char *str = malloc(strlen(conf.docroot) + strlen(conf.cgi_prefix) + 1); /* *str = malloc(strlen("/www/cgi_bin")+1) */ strcpy(str, conf.docroot); strcat(str, conf.cgi_prefix); /* 原型:extern char *strcat(char *dest, const char *src); 功能:把src所指字符串添加到dest结尾处(覆盖dest结尾处的'\0')。 */ conf.cgi_docroot_path = str; /* 配置文件的cgi_docroot_path指向cgi目录的绝对路径 */ conf.cgi_prefix_len = strlen(conf.cgi_prefix); /* '/cgi_bin'的长度为8 */ } }
uh_index_add
/* 本函数在file文件下 */ void uh_index_add(const char *filename) { struct index_file *idx; /* index_file struct list_head list; const char *name; */ idx = calloc(1, sizeof(*idx)); /* 原型:void *calloc(size_t n, size_t size); 功能:在内存的动态存储区中分配n个长度为size的连续空间 返回值:函数返回一个指向分配起始地址的指针;如果分配不成功,返回NULL。 */ idx->name = filename; list_add_tail(&idx->list, &index_files); /* index_files是一个全局结构体,双链接结构 */ /* 功能:把传进来的文件名保存到链接表 */ } /* 为了代码逻辑清晰做的调整,一个待加入的链表值,一个前链表,一个链表头 */ static inline void list_add_tail(struct list_head *_new, struct list_head *head) { _list_add(_new, head->prev, head); } /* 向双向链表插入新的值 */ static inline void _list_add(struct list_head *_new, struct list_head *prev, struct list_head *next) { next->prev = _new; _new->next = next; _new->prev = prev; prev->next = _new; /* ------ ------ ------ | next | | next | | next | |------| |------| |------| | prev | | prev | | prev | ------ ------ ------ NEXT NEW PREV NEXT->prev -------> NEW NEXT <------ NEW->next NEW->prev ------> PREV NEW <-------- PREV->next */ }
uh_plugin_init
/* plugin.c 插件初始化 */ int uh_plugin_init(const char *name) /* name为插件文件名 */ { struct uhttpd_plugin *p; /* 动态链接库地址 */ const char *sym; /* 动态链接库符号 */ void *dlh; /* 动态链接库名柄 */ dlh = dlopen(name, RTLD_LAZY | RTLD_GLOBAL); /* 打开动态链接库 */ if (!dlh) /* 无法打开动态链接库,报错退出 */ { fprintf(stderr, "Could not open plugin %s: %s\n", name, dlerror()); return -ENOENT; } sym = "uhttpd_plugin"; p = dlsym(dlh, sym); /* 保存动态链接库地址 */ if (!p) /* 无法获得动态链接库地址,报错退出 */ { fprintf(stderr, "Could not find symbol '%s' in plugin '%s'\n", sym, name); return -ENOENT; } list_add(&p->list, &plugins); /* 把动态链接库保存到双链接表plugins */ return p->init(&ops, &conf); /* 初始化插件的配置内容 */ }
usage
static int usage(const char *name) { fprintf(stderr, "Usage: %s -p [addr:]port -h docroot\n" " -f Do not fork to background\n" " -c file Configuration file, default is '/etc/httpd.conf'\n" " -p [addr:]port Bind to specified address and port, multiple allowed\n" #ifdef HAVE_TLS " -s [addr:]port Like -p but provide HTTPS on this port\n" " -C file ASN.1 server certificate file\n" " -K file ASN.1 server private key file\n" " -q Redirect all HTTP requests to HTTPS\n" #endif " -h directory Specify the document root, default is '.'\n" " -E string Use given virtual URL as 404 error handler\n" " -I string Use given filename as index for directories, multiple allowed\n" " -S Do not follow symbolic links outside of the docroot\n" " -D Do not allow directory listings, send 403 instead\n" " -R Enable RFC1918 filter\n" " -n count Maximum allowed number of concurrent script requests\n" " -N count Maximum allowed number of concurrent connections\n" #ifdef HAVE_LUA " -l string URL prefix for Lua handler, default is '/lua'\n" " -L file Lua handler script, omit to disable Lua\n" #endif #ifdef HAVE_UBUS " -u string URL prefix for UBUS via JSON-RPC handler\n" " -U file Override ubus socket path\n" " -a Do not authenticate JSON-RPC requests against UBUS session api\n" " -X Enable CORS HTTP headers on JSON-RPC api\n" #endif " -x string URL prefix for CGI handler, default is '/cgi-bin'\n" " -y alias[=path] URL alias handle\n" " -i .ext=path Use interpreter at path for files with the given extension\n" " -t seconds CGI, Lua and UBUS script timeout in seconds, default is 60\n" " -T seconds Network timeout in seconds, default is 30\n" " -k seconds HTTP keepalive timeout\n" " -d string URL decode given string\n" " -r string Specify basic auth realm\n" " -m string MD5 crypt given string\n" "\n", name ); return 1; }
/*
TLS: TLS(安全传输层协议)用于在两个通信应用程序之间提供保密性和数据完整性。
LUA: LUA是一个小巧的脚本语言
UBUS: UBUS是新openwrt引入的一个消息总线,主要作用是实现不同应用程序之间的信息交互
*/
本站的文章和资源来自互联网或者站长的原创,按照 CC BY -NC -SA 3.0 CN协议发布和共享,转载或引用本站文章应遵循相同协议。如果有侵犯版权的资 源请尽快联系站长,我们会在24h内删除有争议的资源。欢迎大家多多交流,期待共同学习进步。