1.说明
lighttpd
源码: https://github.com/lighttpd/lighttpd1.4.gitlighttpd wiki
: https://redmine.lighttpd.net/projects/lighttpd/wiki/libfcgi
: https://github.com/toshic/libfcgi/tree/master
注意:
2.编译
2.1 lighttpd
编译与web
访问
lighttpd
编译方法,可以参考文档:https://github.com/lighttpd/lighttpd1.4/blob/master/INSTALL,命令如下:
$ cd lighttpd-1.4.xx
$ ./autogen.sh
$ ./configure -C
$ make check
$ /usr/bin/sudo make install
安装后文件路径:
$ ls /usr/local/sbin/lighttpd* -al
-rwxr-xr-x 1 root root 2023608 8月 30 16:45 /usr/local/sbin/lighttpd
-rwxr-xr-x 1 root root 23080 8月 30 16:45 /usr/local/sbin/lighttpd-angel
配置文件lighttpd.conf
,使用:https://github.com/lighttpd/lighttpd1.4/blob/master/doc/config/lighttpd.conf。配置文件:modules.conf
,使用https://github.com/lighttpd/lighttpd1.4/blob/master/doc/config/modules.conf。/etc/lighttpd/conf.d/access_log.conf
使用https://github.com/lighttpd/lighttpd1.4/tree/master/doc/config/conf.d。
注释掉/etc/lighttpd/lighttpd.conf
:
#server.username = "lighttpd"
#server.groupname = "lighttpd"
修改/etc/lighttpd/lighttpd.conf
内容:
server.document-root = "/home/wityuan/Desktop/lighttpd/lighttpd1.4-lighttpd-1.4.76/www
server.port = 8080
server.bind = "localhost"
在目录:/home/wityuan/Desktop/lighttpd/lighttpd1.4-lighttpd-1.4.76/www
中添加test.html
,内容:
<!DOCTYPE html>
<html>
<head>
<title> first website </title></head><body>
<h1> welcome
</h1>
<p>this is a param.
</p>
</body>
</html>
执行命令:
$ sudo mkdir /var/log/lighttpd
$ sudo touch /var/log/lighttpd/error.log
启动lighttpd
:
# sudo /usr/local/sbin/lighttpd -f /etc/lighttpd/lighttpd.conf
启动成功:
网络访问:
http://127.0.0.1:8080/test.html
- 备注:
这一篇的文件配置可以参考文档: https://redmine.lighttpd.net/projects/lighttpd/wiki/InstallFromSource
2.2 libfcgi
下载与编译测试
从网站https://github.com/toshic/libfcgi/tree/master下载代码。
编译,使用命令:
$ ./configure
$ make
$ sudo make install
如果编译不过去,修改文件examples/Makefile.in
中的内容:
echo_cpp_LDADD = $(LIBDIR)/libfcgi++.la
改为:
echo_cpp_LDADD = $(LIBDIR)/libfcgi++.la $(LIBDIR)/libfcgi.la
最后,编译生成的目录信息如下:
编写程序文件cgitest1.c
:
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <alloca.h>
#include <fcgiapp.h>
#define LISTENSOCK_FILENO 0
#define LISTENSOCK_FLAGS 0
int main(int argc, char** argv) {
openlog("testfastcgi", LOG_CONS|LOG_NDELAY, LOG_USER);
int err = FCGX_Init(); /* call before Accept in multithreaded apps */
if (err) { syslog (LOG_INFO, "FCGX_Init failed: %d", err); return 1; }
FCGX_Request cgi;
err = FCGX_InitRequest(&cgi, LISTENSOCK_FILENO, LISTENSOCK_FLAGS);
if (err) { syslog(LOG_INFO, "FCGX_InitRequest failed: %d", err); return 2; }
while (1) {
err = FCGX_Accept_r(&cgi);
if (err) { syslog(LOG_INFO, "FCGX_Accept_r stopped: %d", err); break; }
char** envp;
int size = 200;
for (envp = cgi.envp; *envp; ++envp) size += strlen(*envp) + 11;
char* result = (char*) alloca(size);
strcpy(result, "Status: 200 OK\r\nContent-Type: text/html\r\n\r\n");
strcat(result, "<html><head><title>testcgi</title></head><body><ul>\r\n");
for (envp = cgi.envp; *envp; ++envp) {
strcat(result, "<li>");
strcat(result, *envp);
strcat(result, "</li>\r\n");
}
strcat(result, "</ul></body></html>\r\n");
FCGX_PutStr(result, strlen(result), cgi.out);
}
return 0;
}
编译命令:
# gcc cgitest1.c -o cgitest1 -lfcgi
修改文件/etc/lighttpd/modules.conf
,增加内容:
...
server.modules += ("mod_fastcgi")
fastcgi.debug = 1
fastcgi.server += ("/cgi" =>
((
"bin-path" => "/home/wityuan/Desktop/lighttpd/cgicode/cgitest1",
"max-procs" => 1,
"socket" => "/tmp/fcgi_test.socket",
"check-local" => "disable",
"allow-x-send-file" => "enable"
)))
...
注意:
- 1.如果运行报错:
$ /usr/local/bin/cgi-fcgi
/usr/local/bin/cgi-fcgi: error while loading shared libraries: libfcgi.so.0: cannot open shared object file: No such file or directory
可以参考:https://serverfault.com/questions/120233/how-to-configure-fastcgi-to-work-with-ligttpd-in-ubuntu
解决办法:
export LD_LIBRARY_PATH=/usr/local/lib
- 2.如果运行报错:
2024-08-30 23:59:14: (gw_backend.c.676) gw-backend failed to start: /xx/cgitest1
2024-08-30 23:59:14: (gw_backend.c.678) If you're trying to run your app as a FastCGI backend, make sure you're using the FastCGI-enabled version. If this is PHP on Gentoo, add 'fastcgi' to the USE flags. If this is PHP, try removing the bytecode caches for now and try again.
可以执行命令:
$ sudo ldconfig
重启lighttd
服务器,可以看到有2个进程:
访问资源,截图如下:
2.3 websocket
2.3.1 资源
websocket
的资源参考如下:
js
创建websocket
:https://websockets.spec.whatwg.org/#the-websocket-interfacewebsocket api
:https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_APIwebsocket-6455
官方文档:https://datatracker.ietf.org/doc/html/rfc6455
实际上,如上内容均在lighttpd
中可以看到:
2.3.2 websocket js
与后台程序通信
这里会用到网站所说的mod_wstunnel
的功能:
mod_wstunnel is a WebSocket tunnel endpoint, terminating the websocket tunnel from a client. mod_wstunnel decodes websocket frames and then passes data (without websocket frames) to a backend, and in the opposite direction encodes responses from backend into websocket frames before sending responses to client.
1.配置stunnel
在文件/etc/lighttpd/modules.conf
中新增mod_wstunnel
的配置:
..,
server.modules += ("mod_wstunnel")
$HTTP["url"] =~ "^/websocket" {
wstunnel.server = (
"" => ((
"host" => "127.0.0.1",
"port" => "8081"
))
)
wstunnel.frame-type = "text"
}
...
然后编写一个后台的server
程序,内容如下:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
// Should be same with the one in lihttpd.conf and index.html.
#define DEFAULT_PORT 8081
// Should be same with the one in lihttpd.conf.
#define DEFAULT_IP "127.0.0.1"
int main(int argc, char **argv)
{
int server_socket = -1;
int client_socket = -1;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
char received_buffer[1024]; // Buffer for received.
int received_len = -1;
int sended_len = -1;
int res = -1;
socklen_t addr_len = sizeof(struct sockaddr);
int index;
// Create a socket.
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket < 0)
{
printf("Create socket failed: %s\n", strerror(errno));
return -1;
}
// Bind the created socket on special IP and port.
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(DEFAULT_PORT);
server_addr.sin_addr.s_addr = inet_addr(DEFAULT_IP);
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
printf("Bind server failed: %s\n", strerror(errno));
return -2;
}
printf("Socket[%d] has bond on port[%d] for IP address[%s]!\n",
server_socket, DEFAULT_PORT, DEFAULT_IP);
// Listen on the created socket.
listen(server_socket, 10);
while (1)
{
printf("Waiting and accept new client connect...\n");
client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_len);
if (client_socket < 0)
{
printf("Accept client socket failed: %s\n", strerror(errno));
return -3;
}
printf("Accept new client[%d] socket[%s:%d]\n", client_socket,
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
while (1)
{
memset(received_buffer, 0, sizeof(received_buffer));
received_len = read(client_socket, received_buffer, sizeof(received_buffer));
if (received_len < 0)
{
printf("Read data from client [%d] failed: %s\n", client_socket, strerror(errno));
close(client_socket);
break;
}
else if (0 == received_len)
{
printf("Client [%d] disconnected!\n", client_socket);
close(client_socket);
break;
}
else
{
printf("Read %d bytes from client[%d] and the data is : %s\n",
received_len, client_socket, received_buffer);
// Send back the received buffer to client.
sended_len = write(client_socket, received_buffer, received_len);
if (sended_len < 0)
{
printf("wWite data back to client[%d] failed: %s \n", client_socket,
strerror(errno));
close(client_socket);
break;
}
}
}
sleep(1);
}
if (client_socket)
{
close(client_socket);
}
close(server_socket);
return 1;
}
编译,执行程序:
# gcc server1.c -o server1
# ./server1
web
端js
程序命名lighttpd1.4-lighttpd-1.4.76/www/websocket.js
,内容如下:
// 获取按钮和文本框元素
const sendBtn = document.getElementById('sendBtn');
const messageBox = document.getElementById('messageBox');
// 创建 WebSocket 对象
const socket = new WebSocket('ws://127.0.0.1:8080/websocket'); // 使用一个 WebSocket 服务器进行测试
// 设置 WebSocket 连接打开时的回调函数
socket.onopen = function() {
console.log('WebSocket 连接已打开');
};
// 设置 WebSocket 接收到消息时的回调函数
socket.onmessage = function(event) {
console.log('WebSocket 接收到消息:', event.data);
messageBox.value += event.data + '\n';
};
// 设置 WebSocket 发生错误时的回调函数
socket.onerror = function() {
console.log('WebSocket 发生错误');
};
// 设置 WebSocket 连接关闭时的回调函数
socket.onclose = function() {
console.log('WebSocket 连接已关闭');
};
// 点击按钮时发送消息
sendBtn.onclick = function() {
const message = 'Hello, WebSocket!';
socket.send(message);
messageBox.value += '发送消息: ' + message + '\n';
};
之后,可以看到端口被占用,服务器在监听:
html
资源命名为lighttpd1.4-lighttpd-1.4.76/www/websocket.html
,内容:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket 示例</title>
</head>
<body>
<button id="sendBtn">发送消息</button>
<textarea id="messageBox" readonly></textarea>
<script src="websocket.js"></script>
</body>
</html>
在浏览器中访问资源:
服务端收到数据:
注意:
- 在网站: https://github.com/nori0428/mod_websocket上,有描述:
DEAD.use lighttpd v1.4.46 or after w/ mod_proxy and mod_wstunnnel.
,该项目已经停止维护,建议使用mod_wstunnnel
了。服务器上AMI BMC
的实现kvm,sol, cd-server
还是在用mod_websocket
。
从https://github.com/nori0428/mod_websocket/tree/master摘录一下数据流:
client <--- ssl ---> lighttpd - mod_websocket <--- tcp ---> your websocket server
3.nbd
的实现
3.1 应用层的实现
在网站:https://github.com/NetworkBlockDevice/nbd上实现了应用层的nbd-server
和nbd-client
.
至于使用方法,可以参考链接: https://blog.csdn.net/kenera/article/details/16839499,从这个应用来讲,可以这样来讲:
客户端(linux nbd设备驱动+应用层):需求端 ----> 服务器(windows/linux) + 需要获取的文件。
即想要使用的文件(server服务),被挂接到客户端/dev/nbdx
上,从而客户端能获取需要的文件信息。
3.2 一种openbmc
上的实现
在网站:https://github.com/openbmc/jsnbd/上可以看到openbmc
上提供的一个简陋的实现。
另外,还要搭配: https://github.com/joewalnes/websocketd/tree/master 一起才能工作,可以直接使用安装包安装: https://github.com/joewalnes/websocketd/releases,从介绍上看,其实websocketd
类似lighttpd
,提供了服务器web server
的功能。
下面让其工作起来,看效果如何? 该程序先使用linux
主机而不是BMC
运行起来。
- 1.在
linux
主机上安装nbd-client
,命令如下:
# sudo apt install nbd-client
- 2.查看内核是否有加载
/dev/nbdx
设备:
$ ls /dev/nbd*
zsh: no matches found: /dev/nbd*
如果没有,需要执行命令:
# sudo modprobe nbd
再次查看:
$ ls /dev/nbd*
/dev/nbd0 /dev/nbd12 /dev/nbd2 /dev/nbd6
/dev/nbd1 /dev/nbd13 /dev/nbd3 /dev/nbd7
/dev/nbd10 /dev/nbd14 /dev/nbd4 /dev/nbd8
/dev/nbd11 /dev/nbd15 /dev/nbd5 /dev/nbd9
- 3.安装
websocketd
:
# sudo apt install websocketd
或者源码编译与运行:
# git clone https://github.com/joewalnes/websocketd
# cd websocketd && make
# sudo websocketd/websocketd --port=8000 --staticdir=web --binary ./nbd-proxy <config>
- 4.编译
https://github.com/openbmc/jsnbd
仓库中的nbd-proxy.c
先安装工具包:
$ sudo apt install libjson-c-dev libudev-dev
然后编译:
$ autoscan
$ aclocal
$ ./configure
$ make
$ ls nbd-proxy -al
-rwxrwxr-x 1 wityuan wityuan 64208 9月 1 23:20 nbd-proxy
或者简单的使用命令:
$ meson build && ninja -C build
$ ls build -al | grep nbd
-rwxrwxr-x 1 wityuan wityuan 46976 9月 1 23:26 nbd-proxy
drwxrwxr-x 2 wityuan wityuan 4096 9月 1 23:26 nbd-proxy.p
最终生成程序nbd-proxy
.
抄录jsnbd-master/config.sample.json
中的内容,复制,修改文件名为:config.json
,内容如下:
{
"timeout": 30,
"configurations": {
"0": {
"nbd-device": "/dev/nbd0",
"metadata": {
"description": "Virtual media device"
}
}
}
}
最终目录下有2个文件:
$ ls -al config*json
-rw-rw-r-- 1 wityuan wityuan 211 9月 1 23:40 config.json
-rw-rw-r-- 1 wityuan wityuan 362 7月 28 2023 config.sample.json
另外,修改jsnbd-master/nbd-proxy.c
代码:
static const char* conf_path = "/home/wityuan/Desktop/lighttpd/jsnbd-master/config.json";
static const char* sockpath_tmpl = "/home/wityuan/Desktop/lighttpd/jsnbd-master/nbd.%d.sock";
执行命令:
$ sudo websocketd --port=8000 --staticdir=/home/wityuan/Desktop/lighttpd/jsnbd-master/web --binary /home/wityuan/Desktop/lighttpd/jsnbd-master/build/nbd-proxy
可以看到效果:
3.3 lighttpd + nbd
代码实现
根据前面的调用图,修改jsnbd-master/nbd-proxy.c
代码实现,首要的是修改copy_fd()
,添加内容:
static int copy_fd(struct ctx* ctx, int fd_in, int fd_out)
{
#undef HAVE_SPLICE
...
}
然后贴上整个的代码文件jsnbd-master/nbd-proxy.c
:
/* Copyright 2018 IBM Corp.
*
* Author: Jeremy Kerr <jk@ozlabs.org>
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
// NOLINTBEGIN
#define _GNU_SOURCE
#include "config.h"
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <json.h>
#include <libudev.h>
#include <limits.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#define LIGHTTPD_PORT 8081
#define LIGHTTPD_IP "127.0.0.1"
struct config
{
char* name;
bool is_default;
char* nbd_device;
struct json_object* metadata;
};
struct ctx
{
int sock;
int sock_client;
int signal_pipe[2];
char* sock_path;
pid_t nbd_client_pid;
pid_t state_hook_pid;
int nbd_timeout;
dev_t nbd_devno;
uint8_t* buf;
size_t bufsize;
struct config* configs;
int n_configs;
struct config* default_config;
struct config* config;
struct udev* udev;
struct udev_monitor* monitor;
unsigned int lighttpd_server_socketfd;
unsigned int lighttpd_client_socketfd;
};
//static const char* conf_path = SYSCONFDIR "/nbd-proxy/config.json";
static const char* state_hook_path = SYSCONFDIR "/nbd-proxy/state";
//static const char* sockpath_tmpl = RUNSTATEDIR "/nbd.%d.sock";
static const char* conf_path = "/home/wityuan/Desktop/lighttpd/jsnbd-master/config.json";
static const char* sockpath_tmpl = "/home/wityuan/Desktop/lighttpd/jsnbd-master/nbd.%d.sock";
static const size_t bufsize = 0x20000;
static const int nbd_timeout_default = 30;
static int open_nbd_socket(struct ctx* ctx)
{
struct sockaddr_un addr;
char* path;
int sd, rc;
rc = asprintf(&path, sockpath_tmpl, getpid());
if (rc < 0)
return -1;
sd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (sd < 0)
{
warn("can't create socket");
goto err_free;
}
rc = fchmod(sd, S_IRUSR | S_IWUSR);
if (rc)
{
warn("can't set permissions on socket");
goto err_close;
}
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
rc = bind(sd, (struct sockaddr*)&addr, sizeof(addr));
if (rc)
{
warn("can't bind to path %s", path);
goto err_close;
}
rc = listen(sd, 1);
if (rc)
{
warn("can't listen on socket %s", path);
goto err_unlink;
}
ctx->sock = sd;
ctx->sock_path = path;
return 0;
err_unlink:
unlink(path);
err_close:
close(sd);
err_free:
free(path);
return -1;
}
static int open_lighttpd_socket(struct ctx* ctx)
{
int sd, rc;
struct sockaddr_in server_addr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if (sd < 0)
{
warn("can't create socket");
return -1;
}
rc = fchmod(sd, S_IRUSR | S_IWUSR);
if (rc)
{
warn("can't set permissions on socket");
return -1;
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(LIGHTTPD_PORT);
server_addr.sin_addr.s_addr = inet_addr(LIGHTTPD_IP);
rc = bind(sd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (rc)
{
warn("can't bind to server ip: %s , port: %d ", LIGHTTPD_IP, LIGHTTPD_PORT);
return -1;
}
rc = listen(sd, 1);
if (rc)
{
warn("can't listen to server ip: %s , port: %d ", LIGHTTPD_IP, LIGHTTPD_PORT);
return -1;
}
ctx->lighttpd_server_socketfd = sd;
return 0;
}
static int wait_for_lighttpd_socket(struct ctx* ctx)
{
struct pollfd pollfds[1];
pollfds[0].fd = ctx->lighttpd_server_socketfd;
pollfds[0].events = POLLIN;
for (;;)
{
int rc;
rc = poll(pollfds, 1, -1);
if (rc < 0)
{
if (errno == EINTR)
continue;
printf("poll failed\n");
return -1;
}
printf("wait_for_lighttpd_socket : %d \n", pollfds[0].revents);
if (pollfds[0].revents)
{
rc = accept4(ctx->lighttpd_server_socketfd, NULL, NULL, SOCK_CLOEXEC);
ctx->lighttpd_client_socketfd = rc;
warn("ctx->lighttpd_client_socketfd: %d\n", ctx->lighttpd_client_socketfd);
break;
}
}
return 0;
}
static int start_nbd_client(struct ctx* ctx)
{
pid_t pid;
pid = fork();
if (pid < 0)
{
warn("can't create client process");
return -1;
}
/* child process: run nbd-client in non-fork mode */
if (pid == 0)
{
char timeout_str[10];
//int fd;
snprintf(timeout_str, sizeof(timeout_str), "%d", ctx->nbd_timeout);
// fd = open("/dev/null", O_RDWR | O_CLOEXEC);
// if (fd < 0)
// err(EXIT_FAILURE, "can't open /dev/null");
// dup2(fd, STDIN_FILENO);
// dup2(fd, STDOUT_FILENO);
// close(fd);
warn("run nbd-client application.\n");
warn("nbd-client -u %s -n -L -t %s %s", ctx->sock_path, timeout_str, ctx->config->nbd_device);
execlp("nbd-client", "nbd-client", "-u", ctx->sock_path, "-n", "-L",
"-t", timeout_str, ctx->config->nbd_device, NULL);
warn("error happen...\n");
err(EXIT_FAILURE, "can't start ndb client");
}
ctx->nbd_client_pid = pid;
return 0;
}
static void stop_nbd_client(struct ctx* ctx)
{
int rc;
if (!ctx->nbd_client_pid)
return;
rc = kill(ctx->nbd_client_pid, SIGTERM);
if (rc)
return;
waitpid(ctx->nbd_client_pid, NULL, 0);
ctx->nbd_client_pid = 0;
}
static int copy_fd(struct ctx* ctx, int fd_in, int fd_out)
{
#undef HAVE_SPLICE
#ifdef HAVE_SPLICE
int rc;
rc = splice(fd_in, NULL, fd_out, NULL, ctx->bufsize, 0);
if (rc < 0)
warn("splice");
return rc;
#else
size_t len, pos;
ssize_t rc;
for (;;)
{
errno = 0;
rc = read(fd_in, ctx->buf, ctx->bufsize);
if (rc < 0)
{
if (errno == EINTR)
continue;
warn("read failure");
return -1;
}
if (rc == 0)
return 0;
break;
}
len = rc;
for (pos = 0; pos < len;)
{
errno = 0;
rc = write(fd_out, ctx->buf + pos, len - pos);
if (rc < 0)
{
if (errno == EINTR)
continue;
warn("write failure");
return -1;
}
if (rc == 0)
break;
pos += rc;
}
return pos;
#endif
}
static int signal_pipe_fd = -1;
static void signal_handler(int signal)
{
int rc;
rc = write(signal_pipe_fd, &signal, sizeof(signal));
/* not a lot we can do here but exit... */
if (rc != sizeof(signal))
{
warn("signal_handler invoked.\n");
exit(EXIT_FAILURE);
}
}
static int setup_signals(struct ctx* ctx)
{
struct sigaction sa;
int rc;
rc = pipe2(ctx->signal_pipe, O_CLOEXEC);
if (rc)
{
warn("cant setup signal pipe");
return -1;
}
signal_pipe_fd = ctx->signal_pipe[1];
memset(&sa, 0, sizeof(sa));
sa.sa_handler = signal_handler;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGCHLD, &sa, NULL);
return 0;
}
static void cleanup_signals(struct ctx* ctx)
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_DFL;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGCHLD, &sa, NULL);
close(ctx->signal_pipe[0]);
close(ctx->signal_pipe[1]);
}
static void process_sigchld(struct ctx* ctx, bool* exit)
{
int status;
pid_t pid;
for (;;)
{
pid = waitpid(-1, &status, WNOHANG);
if (pid == 0)
break;
if (pid == ctx->nbd_client_pid)
{
warnx("nbd client stopped (%s: %d); exiting",
WIFEXITED(status) ? "rc" : "sig",
WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status));
ctx->nbd_client_pid = 0;
*exit = true;
}
else if (pid == ctx->state_hook_pid)
{
if (!WIFEXITED(status) || WEXITSTATUS(status))
{
warnx("state hook failed (%s: %d); exiting",
WIFEXITED(status) ? "rc" : "sig",
WIFEXITED(status) ? WEXITSTATUS(status)
: WTERMSIG(status));
*exit = true;
}
ctx->state_hook_pid = 0;
}
}
}
static int process_signal_pipe(struct ctx* ctx, bool* exit)
{
int buf, rc;
rc = read(ctx->signal_pipe[0], &buf, sizeof(buf));
if (rc != sizeof(buf))
return -1;
*exit = false;
switch (buf)
{
case SIGCHLD:
process_sigchld(ctx, exit);
break;
case SIGINT:
case SIGTERM:
*exit = true;
break;
}
return 0;
}
static int wait_for_nbd_client(struct ctx* ctx)
{
struct pollfd pollfds[2];
pollfds[0].fd = ctx->sock;
pollfds[0].events = POLLIN;
pollfds[1].fd = ctx->signal_pipe[0];
pollfds[1].events = POLLIN;
for (;;)
{
errno = 0;
int rc;
rc = poll(pollfds, 2, -1);
if (rc < 0)
{
if (errno == EINTR)
continue;
warn("poll failed");
return -1;
}
if (pollfds[0].revents)
{
rc = accept4(ctx->sock, NULL, NULL, SOCK_CLOEXEC);
if (rc < 0)
{
warn("can't create connection");
return -1;
}
ctx->sock_client = rc;
break;
}
if (pollfds[1].revents)
{
bool exit;
rc = process_signal_pipe(ctx, &exit);
if (rc || exit)
return -1;
}
}
return 0;
}
static int run_state_hook(struct ctx* ctx, const char* action, bool wait)
{
int status, rc;
pid_t pid;
/* if the hook isn't present or executable, that's not necessarily
* an error condition */
if (access(state_hook_path, X_OK))
return 0;
pid = fork();
if (pid < 0)
{
warn("can't fork to execute hook %s", state_hook_path);
return -1;
}
if (!pid)
{
const char* argv0;
argv0 = strchr(state_hook_path, '/');
if (!argv0)
argv0 = state_hook_path;
int fd;
fd = open("/dev/null", O_RDWR | O_CLOEXEC);
if (fd < 0)
exit(EXIT_FAILURE);
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
execl(state_hook_path, argv0, action, ctx->config->name, NULL);
exit(EXIT_FAILURE);
}
if (!wait)
{
ctx->state_hook_pid = pid;
return 0;
}
rc = waitpid(pid, &status, 0);
if (rc < 0)
{
warn("wait");
return -1;
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
{
warnx("hook %s failed", state_hook_path);
return -1;
}
return 0;
}
static int udev_init(struct ctx* ctx)
{
int rc;
ctx->udev = udev_new();
if (!ctx->udev)
{
warn("can't create udev object");
return -1;
}
ctx->monitor = udev_monitor_new_from_netlink(ctx->udev, "kernel");
if (!ctx->monitor)
{
warn("can't create udev monitor");
goto out_unref_udev;
}
rc = udev_monitor_filter_add_match_subsystem_devtype(ctx->monitor, "block",
"disk");
if (rc)
{
warn("can't create udev monitor filter");
goto out_unref_monitor;
}
rc = udev_monitor_enable_receiving(ctx->monitor);
if (rc)
{
warn("can't start udev monitor");
goto out_unref_monitor;
}
return 0;
out_unref_monitor:
udev_monitor_unref(ctx->monitor);
out_unref_udev:
udev_unref(ctx->udev);
return -1;
}
static void udev_free(struct ctx* ctx)
{
udev_monitor_unref(ctx->monitor);
udev_unref(ctx->udev);
}
/* Check for the change event on our nbd device, signifying that the kernel
* has finished initialising the block device. Once we see the event, we run
* the "start" state hook, and close the udev monitor.
*
* Returns:
* 0 if no processing was performed
* -1 on state hook error (and the nbd session should be closed)
*/
static int udev_process(struct ctx* ctx)
{
struct udev_device* dev;
bool action_is_change;
dev_t devno;
int rc;
dev = udev_monitor_receive_device(ctx->monitor);
if (!dev)
return 0;
devno = udev_device_get_devnum(dev);
action_is_change = !strcmp(udev_device_get_action(dev), "change");
udev_device_unref(dev);
if (devno != ctx->nbd_devno)
return 0;
if (!action_is_change)
return 0;
udev_monitor_unref(ctx->monitor);
udev_unref(ctx->udev);
ctx->monitor = NULL;
ctx->udev = NULL;
rc = run_state_hook(ctx, "start", false);
return rc;
}
static int run_proxy_for_lighttpd(struct ctx* ctx)
{
struct pollfd pollfds[4];
bool exit = false;
int rc, n_fd;
/* main proxy: forward data between stdio & socket */
pollfds[0].fd = ctx->sock_client;
pollfds[0].events = POLLIN;
pollfds[1].fd = ctx->lighttpd_client_socketfd;
pollfds[1].events = POLLIN;
pollfds[2].fd = ctx->signal_pipe[0];
pollfds[2].events = POLLIN;
pollfds[3].fd = udev_monitor_get_fd(ctx->monitor);
pollfds[3].events = POLLIN;
n_fd = 4;
for (;;)
{
errno = 0;
rc = poll(pollfds, n_fd, -1);
if (rc < 0)
{
if (errno == EINTR)
continue;
warn("poll failed\n");
break;
}
if (pollfds[0].revents)
{
rc = copy_fd(ctx, ctx->sock_client, ctx->lighttpd_client_socketfd);
if (rc <= 0)
break;
}
if (pollfds[1].revents)
{
rc = copy_fd(ctx, ctx->lighttpd_client_socketfd, ctx->sock_client);
if (rc <= 0)
break;
}
if (pollfds[2].revents)
{
warn("run_proxy_for_lighttpd: %d, line: %d\n", pollfds[2].revents, __LINE__);
rc = process_signal_pipe(ctx, &exit);
if (rc || exit)
break;
}
if (pollfds[3].revents)
{
rc = udev_process(ctx);
if (rc)
break;
/* udev_process may have closed the udev connection,
* in which case we can stop polling on its fd */
if (!ctx->udev)
{
pollfds[2].fd = 0;
pollfds[2].revents = 0;
n_fd = 2;
}
}
}
return rc ? -1 : 0;
}
static void print_metadata(struct ctx* ctx)
{
struct json_object* md;
int i;
md = json_object_new_object();
for (i = 0; i < ctx->n_configs; i++)
{
struct config* config = &ctx->configs[i];
json_object_object_add(md, config->name,
json_object_get(config->metadata));
}
puts(json_object_get_string(md));
json_object_put(md);
}
static void config_free_one(struct config* config)
{
if (config->metadata)
json_object_put(config->metadata);
free(config->nbd_device);
free(config->name);
}
static int config_parse_one(struct config* config, const char* name,
json_object* obj)
{
struct json_object *tmp, *meta;
json_bool jrc;
jrc = json_object_object_get_ex(obj, "nbd-device", &tmp);
if (!jrc)
{
warnx("config %s doesn't specify a nbd-device", name);
return -1;
}
if (!json_object_is_type(tmp, json_type_string))
{
warnx("config %s has invalid nbd-device", name);
return -1;
}
config->nbd_device = strdup(json_object_get_string(tmp));
config->name = strdup(name);
jrc = json_object_object_get_ex(obj, "default", &tmp);
config->is_default = jrc && json_object_get_boolean(tmp);
jrc = json_object_object_get_ex(obj, "metadata", &meta);
if (jrc && json_object_is_type(meta, json_type_object))
config->metadata = json_object_get(meta);
else
config->metadata = NULL;
return 0;
}
static void config_free(struct ctx* ctx)
{
int i;
for (i = 0; i < ctx->n_configs; i++)
config_free_one(&ctx->configs[i]);
free(ctx->configs);
ctx->n_configs = 0;
}
static int config_init(struct ctx* ctx)
{
struct json_object *obj, *tmp;
json_bool jrc;
int i, rc;
/* apply defaults */
ctx->nbd_timeout = nbd_timeout_default;
obj = json_object_from_file(conf_path);
if (!obj)
{
warnx("can't read configuration from %s\n", conf_path);
return -1;
}
/* global configuration */
jrc = json_object_object_get_ex(obj, "timeout", &tmp);
if (jrc)
{
errno = 0;
ctx->nbd_timeout = json_object_get_int(tmp);
if (ctx->nbd_timeout == 0 && errno)
{
warnx("can't parse timeout value");
goto err_free;
}
}
/* per-config configuration */
jrc = json_object_object_get_ex(obj, "configurations", &tmp);
if (!jrc)
{
warnx("no configurations specified");
goto err_free;
}
if (!json_object_is_type(tmp, json_type_object))
{
warnx("invalid configurations format");
goto err_free;
}
ctx->n_configs = json_object_object_length(tmp);
ctx->configs = calloc(ctx->n_configs, sizeof(*ctx->configs));
i = 0;
json_object_object_foreach(tmp, name, config_json)
{
struct config* config = &ctx->configs[i];
rc = config_parse_one(config, name, config_json);
if (rc)
goto err_free;
if (config->is_default)
{
if (ctx->default_config)
{
warn("multiple configs flagged as default");
goto err_free;
}
ctx->default_config = config;
}
i++;
}
json_object_put(obj);
if (ctx->n_configs == 1)
ctx->default_config = &ctx->configs[0];
return 0;
err_free:
warnx("failed to load config from %s", conf_path);
json_object_put(obj);
return -1;
}
static int config_select(struct ctx* ctx, const char* name)
{
struct config* config;
struct stat statbuf;
int rc;
config = NULL;
if (!name)
{
/* no config specified: use the default */
if (!ctx->default_config)
{
warnx("no config specified, and no default");
return -1;
}
config = ctx->default_config;
}
else
{
/* find a matching config... */
for (int i = 0; i < ctx->n_configs; i++)
{
if (!strcmp(ctx->configs[i].name, name))
{
config = &ctx->configs[i];
break;
}
}
if (!config)
{
warnx("no such configuration '%s'", name);
return -1;
}
}
/* check that the device exists */
rc = stat(config->nbd_device, &statbuf);
if (rc)
{
warn("can't stat nbd device %s", config->nbd_device);
return -1;
}
if (!S_ISBLK(statbuf.st_mode))
{
warn("specified nbd path %s isn't a block device", config->nbd_device);
return -1;
}
/* ... and apply it */
ctx->config = config;
ctx->nbd_devno = statbuf.st_rdev;
return 0;
}
static const struct option options[] = {
{.name = "help", .val = 'h'},
{.name = "metadata", .val = 'm'},
{0},
};
enum action
{
ACTION_PROXY,
ACTION_METADATA,
};
static void print_usage(const char* progname)
{
fprintf(stderr, "usage:\n");
fprintf(stderr, "\t%s [configuration]\n", progname);
fprintf(stderr, "\t%s --metadata\n", progname);
}
int main(int argc, char** argv)
{
enum action action = ACTION_PROXY;
const char* config_name;
struct ctx _ctx, *ctx;
int rc;
config_name = NULL;
for (;;)
{
int c = getopt_long(argc, argv, "h", options, NULL);
if (c == -1)
break;
switch (c)
{
case 'm':
action = ACTION_METADATA;
break;
case 'h':
case '?':
print_usage(argv[0]);
return c == 'h' ? EXIT_SUCCESS : EXIT_FAILURE;
}
}
if (optind < argc)
config_name = argv[optind];
ctx = &_ctx;
memset(ctx, 0, sizeof(*ctx));
ctx->bufsize = bufsize;
ctx->buf = malloc(ctx->bufsize);
rc = config_init(ctx);
if (rc)
goto out_free;
if (action == ACTION_METADATA)
{
print_metadata(ctx);
goto out_free;
}
rc = config_select(ctx, config_name);
if (rc)
goto out_free;
rc = open_nbd_socket(ctx);
if (rc)
goto out_free;
rc = setup_signals(ctx);
if (rc)
goto out_close;
rc = start_nbd_client(ctx);
if (rc)
goto out_stop_client;
//open lighttpd socket
rc = open_lighttpd_socket(ctx);
if (rc)
goto out_stop_client;
rc = wait_for_nbd_client(ctx);
if (rc)
goto out_stop_client;
rc = wait_for_lighttpd_socket(ctx);
if (rc)
goto out_stop_client;
rc = udev_init(ctx);
if (rc)
goto out_stop_client;
// rc = run_proxy(ctx);
run_proxy_for_lighttpd(ctx);
if (ctx->udev)
udev_free(ctx);
run_state_hook(ctx, "stop", true);
out_stop_client:
/* we cleanup signals before stopping the client, because we
* no longer care about SIGCHLD from the stopping nbd-client
* process. stop_nbd_client will be a no-op if the client hasn't
* been started. */
cleanup_signals(ctx);
stop_nbd_client(ctx);
close(ctx->sock_client);
out_close:
if (ctx->sock_path)
{
unlink(ctx->sock_path);
free(ctx->sock_path);
}
close(ctx->sock);
out_free:
config_free(ctx);
free(ctx->buf);
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}
// NOLINTEND
说明一下,删除函数:
run_proxy()
新增函数:
open_lighttpd_socket()
wait_for_lighttpd_socket()
然后编译文件:
# meson build && ninja -C build
运行文件:
# sudo ./build/nbd-proxy
注意加sudo
.
web
访问资源: