提交 2a6fb41b 编写于 作者: C coolsnowwolf

luci app haproxy: package add

上级 228c4785
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-haproxy-tcp
PKG_VERSION=1.4
PKG_RELEASE:=1
PKG_MAINTAINER:=Alex Zhuo <1886090@gmail.com>
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
CATEGORY:=Utilities
SUBMENU:=Luci
TITLE:=luci for haproxy and shadowsocks
PKGARCH:=all
DEPENDS:=+haproxy
endef
define Package/$(PKG_NAME)/description
A luci app for haproxy with shadowsocks
endef
define Package/$(PKG_NAME)/postinst
#!/bin/sh
rm -rf /tmp/luci*
echo stopping haproxy
/etc/init.d/haproxy stop
/etc/init.d/haproxy disable
echo haproxy disabled
endef
define Build/Prepare
$(foreach po,$(wildcard ${CURDIR}/i18n/zh-cn/*.po), \
po2lmo $(po) $(PKG_BUILD_DIR)/$(patsubst %.po,%.lmo,$(notdir $(po)));)
endef
define Build/Configure
endef
define Build/Compile
endef
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/lib/lua/luci/i18n
$(INSTALL_DATA) $(PKG_BUILD_DIR)/*.*.lmo $(1)/usr/lib/lua/luci/i18n/
$(CP) ./files/* $(1)/
endef
$(eval $(call BuildPackage,$(PKG_NAME)))
# luci-app-haproxy-tcp
OpenWrt HAProxy的Luci配置页面,已在[该固件][A]中使用
简介
---
本软件包为OpenWRT HAPrxoy的 LuCI 控制界面,用于Shadowsocks在多服务器条件下实现负载均衡和高可用
可以设置多个主服务器或多个备用服务器. 默认监听端口127.0.0.1:2222 后台监控页面端口0.0.0.0:1111,后台监控页面地址192.168.1.1:1111/haproxy
多主服务器是将所有TCP流量分流,并可以设置每个服务器的分流比例;多备用服务器是在检测到主服务器A宕机之后切换至备用服务器B,B宕机之后切换到服务器C...依次类推,可以防止因为单个服务器或者线路故障导致的断网问题。
使用效果和更多使用方法请[点击这里][A]
依赖
---
显式依赖 `haproxy`, 安装完毕该luci包后会stop并disable当前op的haproxy,点击“保存&应用”后会修改HAProxy默认配置文件/etc/haproxy.cfg并自动重启,支持开机自启.
配置
---
如果有需要,可以修改/etc/haproxy_init.sh ,不要直接修改/etc/haproxy.cfg,否则会被覆盖
编译
---
从 OpenWrt 的 [SDK][openwrt-sdk] 编译
```bash
# 解压下载好的 SDK
tar xjf OpenWrt-SDK-ar71xx-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2.tar.bz2
cd OpenWrt-SDK-ar71xx-*
# Clone 项目
git clone https://github.com/AlexZhuo/luci-app-haproxy-tcp package/luci-app-haproxy-tcp
# 编译 po2lmo (如果有po2lmo可跳过)
pushd package/luci-app-haproxy-tcp/tools/po2lmo
make && sudo make install
popd
# 选择要编译的包 Utilities -> LuCI -> luci-app-haproxy-tcp
make menuconfig
# 开始编译
make package/luci-app-haproxy-tcp/compile V=99
```
截图
---
![](https://github.com/AlexZhuo/BreakwallOpenWrt/raw/master/screenshots/haproxy.png)
[A]: http://www.right.com.cn/forum/thread-198649-1-1.html
[openwrt-sdk]: http://wiki.openwrt.org/doc/howto/obtain.firmware.sdk
config arguments
option enabled '0'
config main_server
option server_weight '10'
option server_ip '1.2.3.4'
option server_port '443'
option server_name 'JP1'
option validate '1'
config backup_server
option server_name 'JP2'
option server_ip '2.2.2.2'
option server_port '8038'
option validate '1'
config backup_server
option server_name 'JP3'
option server_ip '3.3.3.3'
option server_port '443'
option validate '1'
config backup_server
option server_name 'JP4'
option server_ip '4.4.4.4'
option server_port '443'
option validate '1'
#!/bin/sh /etc/rc.common
CFG_FILE=/etc/haproxy.cfg
stop(){
logger -t alex stopping haproxy
echo "stopping haproxy"
/etc/init.d/haproxy disable
/etc/init.d/haproxy stop
[ -f /etc/haproxy_backup ] && {
cp /etc/haproxy_backup /etc/init.d/haproxy
}
iptables -t nat -D OUTPUT -j HAPROXY &> /dev/null
iptables -t nat -F HAPROXY &> /dev/null
sleep 1
iptables -t nat -X HAPROXY &> /dev/null
}
start(){
echo "starting haproxy"
logger -t restarting haproxy
echo global > $CFG_FILE
cat >> $CFG_FILE <<EOF
log 127.0.0.1 local0 #[日志输出配置,所有日志都记录在本机,通过local0输出]
log 127.0.0.1 local1 notice #定义haproxy 日志级别[error warringinfo debug]
daemon #以后台形式运行harpoxy
nbproc 1 #设置进程数量
pidfile /var/run/haproxy.pid
ulimit-n 1024 #ulimit 的数量限制
maxconn 1024 #默认最大连接数,需考虑ulimit-n限制
#chroot /usr/local/haproxy
defaults
log global
mode tcp #默认的模式mode { tcp|http|health },tcp是4层,http是7层,health只会返回OK
retries 3 #两次连接失败就认为是服务器不可用,也可以通过后面设置
option abortonclose #当服务器负载很高的时候,自动结束掉当前队列处理比较久的链接
option redispatch
maxconn 1024 #默认的最大连接数
timeout connect 5000ms #连接超时
timeout client 30000ms #客户端超时
timeout server 30000ms #服务器超时
balance roundrobin #设置默认负载均衡方式,轮询方式
#balance source #设置默认负载均衡方式,类似于nginx的ip_hash
#balnace leastconn #设置默认负载均衡方式,最小连接数
listen admin_stats
bind 0.0.0.0:1111 #节点统计页面的访问端口
mode http #http的7层模式
option httplog #采用http日志格式
maxconn 10 #节点统计页面默认的最大连接数
stats refresh 30s #节点统计页面自动刷新时间
stats uri /haproxy #节点统计页面url
stats realm Haproxy #统计页面密码框上提示文本
stats auth admin:root #设置监控页面的用户和密码:admin,可以设置多个用户名
stats admin if TRUE #设置手工启动/禁用,后端服务器(haproxy-1.4.9以后版本)
frontend ss-in
bind 127.0.0.1:2222
default_backend ss-out
backend ss-out
mode tcp
balance roundrobin
option tcplog
EOF
local COUNTER=0
#添加主服务器
iptables -t nat -X HAPROXY
iptables -t nat -N HAPROXY
iptables -t nat -F HAPROXY
while true
do
local server_ip=`uci get haproxy.@main_server[$COUNTER].server_ip 2>/dev/null`
local server_name=`uci get haproxy.@main_server[$COUNTER].server_name 2>/dev/null`
local server_port=`uci get haproxy.@main_server[$COUNTER].server_port 2>/dev/null`
local server_weight=`uci get haproxy.@main_server[$COUNTER].server_weight 2>/dev/null`
local validate=`uci get haproxy.@main_server[$COUNTER].validate 2>/dev/null`
if [ -z "$server_ip" ] || [ -z "$server_name" ] || [ -z "$server_port" ] || [ -z "$server_weight" ]; then
echo break
break
fi
echo the main server $COUNTER $server_ip $server_name $server_port $server_weight
[ "$validate" = 1 ] && {
echo server $server_name $server_ip:$server_port weight $server_weight maxconn 1024 check inter 1500 rise 3 fall 3 >> $CFG_FILE
}
iptables -t nat -A HAPROXY -p tcp -d $server_ip -j ACCEPT
COUNTER=$(($COUNTER+1))
done
COUNTER=0
#添加备用服务器
while true
do
local server_ip=`uci get haproxy.@backup_server[$COUNTER].server_ip 2>/dev/null`
local server_name=`uci get haproxy.@backup_server[$COUNTER].server_name 2>/dev/null`
local server_port=`uci get haproxy.@backup_server[$COUNTER].server_port 2>/dev/null`
local validate=`uci get haproxy.@backup_server[$COUNTER].validate 2>/dev/null`
if [ -z "$server_ip" ] || [ -z "$server_name" ] || [ -z "$server_port" ]; then
echo break
break
fi
echo the backup server $COUNTER $server_ip $server_name $server_port
[ "$validate" = 1 ] && {
echo server $server_name $server_ip:$server_port weight 10 check backup inter 1500 rise 3 fall 3 >> $CFG_FILE
}
iptables -t nat -A HAPROXY -p tcp -d $server_ip -j ACCEPT
COUNTER=$(($COUNTER+1))
done
iptables -t nat -I OUTPUT -j HAPROXY
/etc/init.d/haproxy enable
/etc/init.d/haproxy restart
cp /etc/init.d/haproxy /etc/haproxy_backup
cp /etc/haproxy_start /etc/init.d/haproxy
}
restart(){
echo luci for haproxy
sleep 1s
local vt_enabled=`uci get haproxy.@arguments[0].enabled 2>/dev/null`
logger -t haproxy is initializing enabled is $vt_enabled
echo $vt_enabled
if [ "$vt_enabled" = 1 ]; then
[ -f /etc/haproxy_backup ] && {
cp /etc/haproxy_backup /etc/init.d/haproxy
}
iptables -t nat -D OUTPUT -j HAPROXY &> /dev/null
iptables -t nat -F HAPROXY &> /dev/null
sleep 1
iptables -t nat -X HAPROXY &> /dev/null
start;
else
stop;
fi
}
\ No newline at end of file
#!/bin/sh /etc/rc.common
# Copyright (C) 2009-2010 OpenWrt.org
START=99
STOP=80
SERVICE_USE_PID=1
HAPROXY_BIN="/usr/sbin/haproxy"
HAPROXY_CONFIG="/etc/haproxy.cfg"
HAPROXY_PID="/var/run/haproxy.pid"
start() {
service_start $HAPROXY_BIN -q -D -f "$HAPROXY_CONFIG" -p "$HAPROXY_PID"
local COUNTER=0
#添加主服务器
iptables -t nat -D OUTPUT -j HAPROXY &> /dev/null
iptables -t nat -X HAPROXY
iptables -t nat -N HAPROXY
iptables -t nat -F HAPROXY
while true
do
local server_ip=`uci get haproxy.@main_server[$COUNTER].server_ip 2>/dev/null`
local server_name=`uci get haproxy.@main_server[$COUNTER].server_name 2>/dev/null`
local server_port=`uci get haproxy.@main_server[$COUNTER].server_port 2>/dev/null`
local server_weight=`uci get haproxy.@main_server[$COUNTER].server_weight 2>/dev/null`
local validate=`uci get haproxy.@main_server[$COUNTER].validate 2>/dev/null`
if [ -z "$server_ip" ] || [ -z "$server_name" ] || [ -z "$server_port" ] || [ -z "$server_weight" ]; then
echo break
break
fi
echo the main2 server $COUNTER $server_ip $server_name $server_port $server_weight
[ "$validate" = 1 ] && {
iptables -t nat -A HAPROXY -p tcp -d $server_ip -j ACCEPT
}
COUNTER=$(($COUNTER+1))
done
COUNTER=0
#添加备用服务器
while true
do
local server_ip=`uci get haproxy.@backup_server[$COUNTER].server_ip 2>/dev/null`
local server_name=`uci get haproxy.@backup_server[$COUNTER].server_name 2>/dev/null`
local server_port=`uci get haproxy.@backup_server[$COUNTER].server_port 2>/dev/null`
local validate=`uci get haproxy.@backup_server[$COUNTER].validate 2>/dev/null`
if [ -z "$server_ip" ] || [ -z "$server_name" ] || [ -z "$server_port" ]; then
echo break
break
fi
echo the backup2 server $COUNTER $server_ip $server_name $server_port
[ "$validate" = 1 ] && {
iptables -t nat -A HAPROXY -p tcp -d $server_ip -j ACCEPT
}
COUNTER=$(($COUNTER+1))
done
iptables -t nat -I OUTPUT -j HAPROXY
}
stop() {
kill -9 $(cat $HAPROXY_PID | tr "\n" " ")
service_stop $HAPROXY_BIN
iptables -t nat -D OUTPUT -j HAPROXY &> /dev/null
iptables -t nat -F HAPROXY &> /dev/null
sleep 1
iptables -t nat -X HAPROXY &> /dev/null
}
reload() {
$HAPROXY_BIN -D -q -f $HAPROXY_CONFIG -p $HAPROXY_PID -sf $(cat $HAPROXY_PID | tr "\n" " ")
#$HAPROXY_BIN -D -q -f $HAPROXY_CONFIG -p $HAPROXY_PID -sf $(cat $HAPROXY_PID)
}
#!/bin/sh
/etc/init.d/haproxy disable
/etc/init.d/haproxy stop
rm -f /tmp/luci-indexcache
exit 0
module("luci.controller.haproxy", package.seeall)
function index()
if not nixio.fs.access("/etc/config/haproxy") then
return
end
entry({"admin", "services", "haproxy"}, cbi("haproxy"), _("HAProxy")).dependent = true
end
\ No newline at end of file
--Alex<1886090@gmail.com>
local fs = require "nixio.fs"
function sync_value_to_file(value, file) --用来写入文件的函数,目前这种方式已经弃用
value = value:gsub("\r\n?", "\n")
local old_value = nixio.fs.readfile(file)
if value ~= old_value then
nixio.fs.writefile(file, value)
end
end
local state_msg = ""
local haproxy_on = (luci.sys.call("pidof haproxy > /dev/null") == 0)
local router_ip = luci.sys.exec("uci get network.lan.ipaddr")
if haproxy_on then
state_msg = "<b><font color=\"green\">" .. translate("Running") .. "</font></b>"
else
state_msg = "<b><font color=\"red\">" .. translate("Not running") .. "</font></b>"
end
m=Map("haproxy",translate("HAProxy"),translate("HAProxy能够检测Shadowsocks服务器的连通情况,从而实现负载均衡和高可用的功能,支持主备用服务器宕机自动切换,并且可以设置多个主服务器用于分流,规定每个分流节点的流量比例等。前提条件是你的所有Shadowsocks服务器的【加密方式】和【密码】一致。<br><br>使用方法:配置好你的Shadowsocks服务器ip地址和端口,然后开启Shadowsocks服务,将服务器地址填写为【127.0.0.1】,端口【2222】,其他参数和之前一样即可,你可以通过访问【路由器的IP:1111/haproxy】输入用户名admin,密码root来观察各节点健康状况,红色为宕机,绿色正常,使用说明请<a href='http://www.right.com.cn/forum/thread-198649-1-1.html'>点击这里</a>") .. "<br><br>后台监控页面:<a href='http://" .. router_ip .. ":1111/haproxy'>" .. router_ip .. ":1111/haproxy</a> 用户名admin,密码root" .. "<br><br>状态 - " .. state_msg)
s=m:section(TypedSection,"arguments","")
s.addremove=false
s.anonymous=true
view_enable = s:option(Flag,"enabled",translate("Enable"))
--通过读写配置文件控制HAProxy这种方式已经弃用
--view_cfg = s:option(TextValue, "1", nil)
--view_cfg.rmempty = false
--view_cfg.rows = 43
--function view_cfg.cfgvalue()
-- return nixio.fs.readfile("/etc/haproxy.cfg") or ""
--end
--function view_cfg.write(self, section, value)
-- sync_value_to_file(value, "/etc/haproxy.cfg")
--end
s=m:section(TypedSection,"main_server","<b>" .. translate("Main Server List") .. "<b>")
s.anonymous=true
s.addremove=true
o=s:option(Value,"server_name",translate("Display Name"),translate("Only English Characters,No spaces"))
o.rmempty = false
o=s:option(Flag,"validate",translate("validate"))
o=s:option(Value,"server_ip",translate("Proxy Server IP"))
o.datatype="ip4addr"
o=s:option(Value,"server_port",translate("Proxy Server Port"))
o.datatype="uinteger"
o=s:option(Value,"server_weight",translate("Weight"))
o.datatype="uinteger"
s=m:section(TypedSection,"backup_server","<b>" .. translate("Backup Server List") .. "<b>")
s.anonymous=true
s.addremove=true
o=s:option(Value,"server_name",translate("Display Name"),translate("Only English Characters,No spaces"))
o.rmempty = false
o=s:option(Flag,"validate",translate("validate"))
o=s:option(Value,"server_ip",translate("Proxy Server IP"))
o.datatype="ip4addr"
o=s:option(Value,"server_port",translate("Proxy Server Port"))
o.datatype="uinteger"
-- ---------------------------------------------------
local apply = luci.http.formvalue("cbi.apply")
if apply then
os.execute("/etc/haproxy_init.sh restart >/dev/null 2>&1 &")
end
return m
msgid "Running"
msgstr "运行中"
msgid "Not running"
msgstr "未运行"
msgid "Main Server List"
msgstr "主服务器列表"
msgid "Display Name"
msgstr "服务器名称"
msgid "Only English Characters,No spaces"
msgstr "仅限英文字母,不要有空格"
msgid "Proxy Server IP"
msgstr "代理服务器IP"
msgid "Proxy Server Port"
msgstr "代理服务器端口"
msgid "Weight"
msgstr "分流权重"
msgid "Backup Server List"
msgstr "备用服务器列表"
msgid "validate"
msgstr "生效"
INSTALL = install
PREFIX = /usr/bin
po2lmo: src/po2lmo.o src/template_lmo.o
$(CC) $(LDFLAGS) -o src/po2lmo src/po2lmo.o src/template_lmo.o
install:
$(INSTALL) -m 755 src/po2lmo $(PREFIX)
clean:
$(RM) src/po2lmo src/*.o
/*
* lmo - Lua Machine Objects - PO to LMO conversion tool
*
* Copyright (C) 2009-2012 Jo-Philipp Wich <xm@subsignal.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.
*/
#include "template_lmo.h"
static void die(const char *msg)
{
fprintf(stderr, "Error: %s\n", msg);
exit(1);
}
static void usage(const char *name)
{
fprintf(stderr, "Usage: %s input.po output.lmo\n", name);
exit(1);
}
static void print(const void *ptr, size_t size, size_t nmemb, FILE *stream)
{
if( fwrite(ptr, size, nmemb, stream) == 0 )
die("Failed to write stdout");
}
static int extract_string(const char *src, char *dest, int len)
{
int pos = 0;
int esc = 0;
int off = -1;
for( pos = 0; (pos < strlen(src)) && (pos < len); pos++ )
{
if( (off == -1) && (src[pos] == '"') )
{
off = pos + 1;
}
else if( off >= 0 )
{
if( esc == 1 )
{
switch (src[pos])
{
case '"':
case '\\':
off++;
break;
}
dest[pos-off] = src[pos];
esc = 0;
}
else if( src[pos] == '\\' )
{
dest[pos-off] = src[pos];
esc = 1;
}
else if( src[pos] != '"' )
{
dest[pos-off] = src[pos];
}
else
{
dest[pos-off] = '\0';
break;
}
}
}
return (off > -1) ? strlen(dest) : -1;
}
static int cmp_index(const void *a, const void *b)
{
uint32_t x = ((const lmo_entry_t *)a)->key_id;
uint32_t y = ((const lmo_entry_t *)b)->key_id;
if (x < y)
return -1;
else if (x > y)
return 1;
return 0;
}
static void print_uint32(uint32_t x, FILE *out)
{
uint32_t y = htonl(x);
print(&y, sizeof(uint32_t), 1, out);
}
static void print_index(void *array, int n, FILE *out)
{
lmo_entry_t *e;
qsort(array, n, sizeof(*e), cmp_index);
for (e = array; n > 0; n--, e++)
{
print_uint32(e->key_id, out);
print_uint32(e->val_id, out);
print_uint32(e->offset, out);
print_uint32(e->length, out);
}
}
int main(int argc, char *argv[])
{
char line[4096];
char key[4096];
char val[4096];
char tmp[4096];
int state = 0;
int offset = 0;
int length = 0;
int n_entries = 0;
void *array = NULL;
lmo_entry_t *entry = NULL;
uint32_t key_id, val_id;
FILE *in;
FILE *out;
if( (argc != 3) || ((in = fopen(argv[1], "r")) == NULL) || ((out = fopen(argv[2], "w")) == NULL) )
usage(argv[0]);
memset(line, 0, sizeof(key));
memset(key, 0, sizeof(val));
memset(val, 0, sizeof(val));
while( (NULL != fgets(line, sizeof(line), in)) || (state >= 2 && feof(in)) )
{
if( state == 0 && strstr(line, "msgid \"") == line )
{
switch(extract_string(line, key, sizeof(key)))
{
case -1:
die("Syntax error in msgid");
case 0:
state = 1;
break;
default:
state = 2;
}
}
else if( state == 1 || state == 2 )
{
if( strstr(line, "msgstr \"") == line || state == 2 )
{
switch(extract_string(line, val, sizeof(val)))
{
case -1:
state = 4;
break;
default:
state = 3;
}
}
else
{
switch(extract_string(line, tmp, sizeof(tmp)))
{
case -1:
state = 2;
break;
default:
strcat(key, tmp);
}
}
}
else if( state == 3 )
{
switch(extract_string(line, tmp, sizeof(tmp)))
{
case -1:
state = 4;
break;
default:
strcat(val, tmp);
}
}
if( state == 4 )
{
if( strlen(key) > 0 && strlen(val) > 0 )
{
key_id = sfh_hash(key, strlen(key));
val_id = sfh_hash(val, strlen(val));
if( key_id != val_id )
{
n_entries++;
array = realloc(array, n_entries * sizeof(lmo_entry_t));
entry = (lmo_entry_t *)array + n_entries - 1;
if (!array)
die("Out of memory");
entry->key_id = key_id;
entry->val_id = val_id;
entry->offset = offset;
entry->length = strlen(val);
length = strlen(val) + ((4 - (strlen(val) % 4)) % 4);
print(val, length, 1, out);
offset += length;
}
}
state = 0;
memset(key, 0, sizeof(key));
memset(val, 0, sizeof(val));
}
memset(line, 0, sizeof(line));
}
print_index(array, n_entries, out);
if( offset > 0 )
{
print_uint32(offset, out);
fsync(fileno(out));
fclose(out);
}
else
{
fclose(out);
unlink(argv[2]);
}
fclose(in);
return(0);
}
/*
* lmo - Lua Machine Objects - Base functions
*
* Copyright (C) 2009-2010 Jo-Philipp Wich <xm@subsignal.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.
*/
#include "template_lmo.h"
/*
* Hash function from http://www.azillionmonkeys.com/qed/hash.html
* Copyright (C) 2004-2008 by Paul Hsieh
*/
uint32_t sfh_hash(const char *data, int len)
{
uint32_t hash = len, tmp;
int rem;
if (len <= 0 || data == NULL) return 0;
rem = len & 3;
len >>= 2;
/* Main loop */
for (;len > 0; len--) {
hash += sfh_get16(data);
tmp = (sfh_get16(data+2) << 11) ^ hash;
hash = (hash << 16) ^ tmp;
data += 2*sizeof(uint16_t);
hash += hash >> 11;
}
/* Handle end cases */
switch (rem) {
case 3: hash += sfh_get16(data);
hash ^= hash << 16;
hash ^= data[sizeof(uint16_t)] << 18;
hash += hash >> 11;
break;
case 2: hash += sfh_get16(data);
hash ^= hash << 11;
hash += hash >> 17;
break;
case 1: hash += *data;
hash ^= hash << 10;
hash += hash >> 1;
}
/* Force "avalanching" of final 127 bits */
hash ^= hash << 3;
hash += hash >> 5;
hash ^= hash << 4;
hash += hash >> 17;
hash ^= hash << 25;
hash += hash >> 6;
return hash;
}
uint32_t lmo_canon_hash(const char *str, int len)
{
char res[4096];
char *ptr, prev;
int off;
if (!str || len >= sizeof(res))
return 0;
for (prev = ' ', ptr = res, off = 0; off < len; prev = *str, off++, str++)
{
if (isspace(*str))
{
if (!isspace(prev))
*ptr++ = ' ';
}
else
{
*ptr++ = *str;
}
}
if ((ptr > res) && isspace(*(ptr-1)))
ptr--;
return sfh_hash(res, ptr - res);
}
lmo_archive_t * lmo_open(const char *file)
{
int in = -1;
uint32_t idx_offset = 0;
struct stat s;
lmo_archive_t *ar = NULL;
if (stat(file, &s) == -1)
goto err;
if ((in = open(file, O_RDONLY)) == -1)
goto err;
if ((ar = (lmo_archive_t *)malloc(sizeof(*ar))) != NULL)
{
memset(ar, 0, sizeof(*ar));
ar->fd = in;
ar->size = s.st_size;
fcntl(ar->fd, F_SETFD, fcntl(ar->fd, F_GETFD) | FD_CLOEXEC);
if ((ar->mmap = mmap(NULL, ar->size, PROT_READ, MAP_SHARED, ar->fd, 0)) == MAP_FAILED)
goto err;
idx_offset = ntohl(*((const uint32_t *)
(ar->mmap + ar->size - sizeof(uint32_t))));
if (idx_offset >= ar->size)
goto err;
ar->index = (lmo_entry_t *)(ar->mmap + idx_offset);
ar->length = (ar->size - idx_offset - sizeof(uint32_t)) / sizeof(lmo_entry_t);
ar->end = ar->mmap + ar->size;
return ar;
}
err:
if (in > -1)
close(in);
if (ar != NULL)
{
if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED))
munmap(ar->mmap, ar->size);
free(ar);
}
return NULL;
}
void lmo_close(lmo_archive_t *ar)
{
if (ar != NULL)
{
if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED))
munmap(ar->mmap, ar->size);
close(ar->fd);
free(ar);
ar = NULL;
}
}
lmo_catalog_t *_lmo_catalogs = NULL;
lmo_catalog_t *_lmo_active_catalog = NULL;
int lmo_load_catalog(const char *lang, const char *dir)
{
DIR *dh = NULL;
char pattern[16];
char path[PATH_MAX];
struct dirent *de = NULL;
lmo_archive_t *ar = NULL;
lmo_catalog_t *cat = NULL;
if (!lmo_change_catalog(lang))
return 0;
if (!dir || !(dh = opendir(dir)))
goto err;
if (!(cat = malloc(sizeof(*cat))))
goto err;
memset(cat, 0, sizeof(*cat));
snprintf(cat->lang, sizeof(cat->lang), "%s", lang);
snprintf(pattern, sizeof(pattern), "*.%s.lmo", lang);
while ((de = readdir(dh)) != NULL)
{
if (!fnmatch(pattern, de->d_name, 0))
{
snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
ar = lmo_open(path);
if (ar)
{
ar->next = cat->archives;
cat->archives = ar;
}
}
}
closedir(dh);
cat->next = _lmo_catalogs;
_lmo_catalogs = cat;
if (!_lmo_active_catalog)
_lmo_active_catalog = cat;
return 0;
err:
if (dh) closedir(dh);
if (cat) free(cat);
return -1;
}
int lmo_change_catalog(const char *lang)
{
lmo_catalog_t *cat;
for (cat = _lmo_catalogs; cat; cat = cat->next)
{
if (!strncmp(cat->lang, lang, sizeof(cat->lang)))
{
_lmo_active_catalog = cat;
return 0;
}
}
return -1;
}
static lmo_entry_t * lmo_find_entry(lmo_archive_t *ar, uint32_t hash)
{
unsigned int m, l, r;
uint32_t k;
l = 0;
r = ar->length - 1;
while (1)
{
m = l + ((r - l) / 2);
if (r < l)
break;
k = ntohl(ar->index[m].key_id);
if (k == hash)
return &ar->index[m];
if (k > hash)
{
if (!m)
break;
r = m - 1;
}
else
{
l = m + 1;
}
}
return NULL;
}
int lmo_translate(const char *key, int keylen, char **out, int *outlen)
{
uint32_t hash;
lmo_entry_t *e;
lmo_archive_t *ar;
if (!key || !_lmo_active_catalog)
return -2;
hash = lmo_canon_hash(key, keylen);
for (ar = _lmo_active_catalog->archives; ar; ar = ar->next)
{
if ((e = lmo_find_entry(ar, hash)) != NULL)
{
*out = ar->mmap + ntohl(e->offset);
*outlen = ntohl(e->length);
return 0;
}
}
return -1;
}
void lmo_close_catalog(const char *lang)
{
lmo_archive_t *ar, *next;
lmo_catalog_t *cat, *prev;
for (prev = NULL, cat = _lmo_catalogs; cat; prev = cat, cat = cat->next)
{
if (!strncmp(cat->lang, lang, sizeof(cat->lang)))
{
if (prev)
prev->next = cat->next;
else
_lmo_catalogs = cat->next;
for (ar = cat->archives; ar; ar = next)
{
next = ar->next;
lmo_close(ar);
}
free(cat);
break;
}
}
}
/*
* lmo - Lua Machine Objects - General header
*
* Copyright (C) 2009-2012 Jo-Philipp Wich <xm@subsignal.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.
*/
#ifndef _TEMPLATE_LMO_H_
#define _TEMPLATE_LMO_H_
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <fnmatch.h>
#include <dirent.h>
#include <ctype.h>
#include <limits.h>
#if (defined(__GNUC__) && defined(__i386__))
#define sfh_get16(d) (*((const uint16_t *) (d)))
#else
#define sfh_get16(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\
+(uint32_t)(((const uint8_t *)(d))[0]) )
#endif
struct lmo_entry {
uint32_t key_id;
uint32_t val_id;
uint32_t offset;
uint32_t length;
} __attribute__((packed));
typedef struct lmo_entry lmo_entry_t;
struct lmo_archive {
int fd;
int length;
uint32_t size;
lmo_entry_t *index;
char *mmap;
char *end;
struct lmo_archive *next;
};
typedef struct lmo_archive lmo_archive_t;
struct lmo_catalog {
char lang[6];
struct lmo_archive *archives;
struct lmo_catalog *next;
};
typedef struct lmo_catalog lmo_catalog_t;
uint32_t sfh_hash(const char *data, int len);
uint32_t lmo_canon_hash(const char *data, int len);
lmo_archive_t * lmo_open(const char *file);
void lmo_close(lmo_archive_t *ar);
extern lmo_catalog_t *_lmo_catalogs;
extern lmo_catalog_t *_lmo_active_catalog;
int lmo_load_catalog(const char *lang, const char *dir);
int lmo_change_catalog(const char *lang);
int lmo_translate(const char *key, int keylen, char **out, int *outlen);
void lmo_close_catalog(const char *lang);
#endif
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册