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内删除有争议的资源。欢迎大家多多交流,期待共同学习进步。







