- 重构 sysconf 为文档模型 INI Parser 与 Config Framework - 强化 hosts 解析、插入校验、写回与异常输入处理 - 完善 StarCmd 生命周期、等待 API、流式输出与 IO 重定向 - 扩展跨平台文件时间、文件锁、内存、进程与网络能力 - 将 Windows 进程适配更新到 b612.me/wincmd v0.1.0 - 移除本地 wincmd/win32api replace,改用发布版依赖 - 将最低 Go 版本提升到 1.18 - 补充 hosts、sysconf、FileLock、StarCmd 与平台适配回归测试
1358 lines
28 KiB
Go
1358 lines
28 KiB
Go
package hosts
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
var lineBreaker string
|
|
|
|
func init() {
|
|
if runtime.GOOS == "windows" {
|
|
lineBreaker = "\r\n"
|
|
} else {
|
|
lineBreaker = "\n"
|
|
}
|
|
}
|
|
func SystemHostpath() string {
|
|
if runtime.GOOS == "windows" {
|
|
return `C:\Windows\System32\drivers\etc\hosts`
|
|
}
|
|
return `/etc/hosts`
|
|
}
|
|
|
|
var defaultParser = NewHosts()
|
|
|
|
func ParseHosts(hostPath string) error {
|
|
return defaultParser.Parse(hostPath)
|
|
}
|
|
|
|
func Parse() error {
|
|
return defaultParser.Parse(SystemHostpath())
|
|
}
|
|
|
|
func List() []*HostNode {
|
|
return defaultParser.List()
|
|
}
|
|
|
|
func ListHosts() map[string][]string {
|
|
return defaultParser.ListHosts()
|
|
}
|
|
|
|
func ListIPs() map[string][]string {
|
|
return defaultParser.ListIPs()
|
|
}
|
|
|
|
func ListByHost(host string) []*HostNode {
|
|
return defaultParser.ListByHost(host)
|
|
}
|
|
|
|
func ListIPsByHost(host string) []string {
|
|
return defaultParser.ListIPsByHost(host)
|
|
}
|
|
|
|
func ListFirstIPByHost(host string) string {
|
|
return defaultParser.ListFirstIPByHost(host)
|
|
}
|
|
|
|
func ListByIP(ip string) []*HostNode {
|
|
return defaultParser.ListByIP(ip)
|
|
}
|
|
|
|
func ListHostsByIP(ip string) []string {
|
|
return defaultParser.ListHostsByIP(ip)
|
|
}
|
|
|
|
func ListFirstHostByIP(ip string) string {
|
|
return defaultParser.ListFirstHostByIP(ip)
|
|
}
|
|
|
|
func AddHosts(ip string, hosts ...string) error {
|
|
return defaultParser.AddHosts(ip, hosts...)
|
|
}
|
|
|
|
func AddHostsComment(comment string, ip string, hosts ...string) error {
|
|
return defaultParser.AddHostsComment(comment, ip, hosts...)
|
|
}
|
|
|
|
func RemoveIPHosts(ip string, hosts ...string) error {
|
|
return defaultParser.RemoveIPHosts(ip, hosts...)
|
|
}
|
|
|
|
func RemoveIPs(ips ...string) error {
|
|
return defaultParser.RemoveIPs(ips...)
|
|
}
|
|
|
|
func RemoveHosts(hosts ...string) error {
|
|
return defaultParser.RemoveHosts(hosts...)
|
|
}
|
|
|
|
func SetIPHosts(ip string, hosts ...string) error {
|
|
return defaultParser.SetIPHosts(ip, hosts...)
|
|
}
|
|
|
|
func SetHostIPs(host string, ips ...string) error {
|
|
return defaultParser.SetHostIPs(host, ips...)
|
|
}
|
|
|
|
func InsertNodes(node *HostNode, before bool, comment string, ip string, hosts ...string) error {
|
|
return defaultParser.InsertNodeByData(node, before, comment, ip, hosts...)
|
|
}
|
|
|
|
func SaveAs(path string) error {
|
|
return defaultParser.SaveAs(path)
|
|
}
|
|
|
|
func Save() error {
|
|
return defaultParser.Save()
|
|
}
|
|
|
|
func Build() ([]byte, error) {
|
|
return defaultParser.Build()
|
|
}
|
|
|
|
func GetFirstNode() (*HostNode, error) {
|
|
return defaultParser.GetFirstNode()
|
|
}
|
|
|
|
func GetNode(nodeID uint64) (*HostNode, error) {
|
|
return defaultParser.GetNode(nodeID)
|
|
}
|
|
|
|
func GetNextNode(nodeID uint64) (*HostNode, error) {
|
|
return defaultParser.GetNextNode(nodeID)
|
|
}
|
|
|
|
func GetLastNode(nodeID uint64) (*HostNode, error) {
|
|
return defaultParser.GetLastNode(nodeID)
|
|
}
|
|
|
|
func GetLatestNode() (*HostNode, error) {
|
|
return defaultParser.GetLatestNode()
|
|
}
|
|
|
|
func DeleteNode(node *HostNode) error {
|
|
return defaultParser.DeleteNode(node)
|
|
}
|
|
|
|
func DeleteNodeByUID(uid uint64) error {
|
|
return defaultParser.DeleteNodeByUID(uid)
|
|
}
|
|
|
|
func AddNode(node *HostNode) error {
|
|
return defaultParser.AddNode(node)
|
|
}
|
|
|
|
func InsertNode(node *HostNode) error {
|
|
return defaultParser.InsertNode(node)
|
|
}
|
|
|
|
func UpdateNode(node *HostNode) error {
|
|
return defaultParser.UpdateNode(node)
|
|
}
|
|
|
|
type HostNode struct {
|
|
uid uint64
|
|
nextuid uint64
|
|
lastuid uint64
|
|
ip string
|
|
host []string
|
|
comment string
|
|
original string
|
|
onlyComment bool
|
|
valid bool
|
|
}
|
|
type Host struct {
|
|
idx uint64
|
|
hostPath string
|
|
firstUid uint64
|
|
lastUid uint64
|
|
fulldata map[uint64]*HostNode
|
|
hostData map[string][]*HostNode
|
|
ipData map[string][]*HostNode
|
|
sync.RWMutex
|
|
}
|
|
|
|
func NewHosts() *Host {
|
|
return &Host{
|
|
hostData: make(map[string][]*HostNode),
|
|
ipData: make(map[string][]*HostNode),
|
|
fulldata: make(map[uint64]*HostNode),
|
|
}
|
|
}
|
|
|
|
func cloneHostNode(node *HostNode) *HostNode {
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
cloned := *node
|
|
cloned.host = append([]string(nil), node.host...)
|
|
return &cloned
|
|
}
|
|
|
|
func cloneHostNodes(nodes []*HostNode) []*HostNode {
|
|
if len(nodes) == 0 {
|
|
return nil
|
|
}
|
|
cloned := make([]*HostNode, 0, len(nodes))
|
|
for _, node := range nodes {
|
|
cloned = append(cloned, cloneHostNode(node))
|
|
}
|
|
return cloned
|
|
}
|
|
|
|
func copyHostNodeState(dst, src *HostNode) {
|
|
if dst == nil || src == nil {
|
|
return
|
|
}
|
|
dst.uid = src.uid
|
|
dst.nextuid = src.nextuid
|
|
dst.lastuid = src.lastuid
|
|
dst.ip = src.ip
|
|
dst.host = append(dst.host[:0], src.host...)
|
|
dst.comment = src.comment
|
|
dst.original = src.original
|
|
dst.onlyComment = src.onlyComment
|
|
dst.valid = src.valid
|
|
}
|
|
|
|
func normalizeEditableNode(node *HostNode) (*HostNode, error) {
|
|
if node == nil {
|
|
return nil, fmt.Errorf("node not exists")
|
|
}
|
|
candidate := cloneHostNode(node)
|
|
if candidate.comment != "" && !strings.HasPrefix(strings.TrimSpace(candidate.comment), "#") {
|
|
candidate.comment = "#" + candidate.comment
|
|
}
|
|
if len(candidate.host) > 0 {
|
|
hosts, err := normalizeHostTokens(candidate.host...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
candidate.host = hosts
|
|
}
|
|
if candidate.ip != "" || len(candidate.host) > 0 {
|
|
ip, err := normalizeIPToken(candidate.ip)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
candidate.ip = ip
|
|
if len(candidate.host) == 0 {
|
|
return nil, fmt.Errorf("empty host")
|
|
}
|
|
} else {
|
|
candidate.ip = strings.TrimSpace(candidate.ip)
|
|
}
|
|
candidate.onlyComment = candidate.ip == "" && len(candidate.host) == 0 && candidate.comment != ""
|
|
if !candidate.onlyComment && candidate.ip == "" && len(candidate.host) == 0 {
|
|
return nil, fmt.Errorf("empty node")
|
|
}
|
|
candidate.valid = candidate.CheckValid()
|
|
if !candidate.valid {
|
|
return nil, fmt.Errorf("invalid hosts node")
|
|
}
|
|
return candidate, nil
|
|
}
|
|
|
|
func (h *Host) Parse(hostPath string) error {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
h.idx = 0
|
|
h.firstUid = 0
|
|
h.lastUid = 0
|
|
h.hostPath = hostPath
|
|
h.fulldata = make(map[uint64]*HostNode)
|
|
h.hostData = make(map[string][]*HostNode)
|
|
h.ipData = make(map[string][]*HostNode)
|
|
return h.parse()
|
|
}
|
|
|
|
func (h *Host) parse() error {
|
|
f, err := os.OpenFile(h.hostPath, os.O_RDONLY, 0666)
|
|
if err != nil {
|
|
return fmt.Errorf("open hosts file %s error: %s", h.hostPath, err)
|
|
}
|
|
defer f.Close()
|
|
buf := bufio.NewReader(f)
|
|
for {
|
|
line, err := buf.ReadString('\n')
|
|
if err != nil && err != io.EOF {
|
|
return fmt.Errorf("read hosts file error: %s", err)
|
|
}
|
|
raw := strings.TrimRight(line, "\r\n")
|
|
if err == nil || (err == io.EOF && raw != "") {
|
|
data, _ := h.parseLine(raw)
|
|
h.appendNodeLocked(&data)
|
|
}
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *Host) parseLine(raw string) (HostNode, error) {
|
|
var res = HostNode{
|
|
original: raw,
|
|
}
|
|
data := strings.TrimSpace(raw)
|
|
if len(data) == 0 {
|
|
return res, fmt.Errorf("empty line")
|
|
}
|
|
if data[0] == '#' {
|
|
res.comment = data
|
|
res.onlyComment = true
|
|
res.valid = true
|
|
return res, nil
|
|
}
|
|
var dataArr []string
|
|
cache := ""
|
|
for k, v := range data {
|
|
if v == '#' {
|
|
if len(cache) > 0 {
|
|
dataArr = append(dataArr, cache)
|
|
cache = ""
|
|
}
|
|
dataArr = append(dataArr, data[k:])
|
|
break
|
|
}
|
|
if v == ' ' || v == '\t' {
|
|
if len(cache) > 0 {
|
|
dataArr = append(dataArr, cache)
|
|
cache = ""
|
|
}
|
|
continue
|
|
}
|
|
cache += string(v)
|
|
}
|
|
if len(cache) > 0 {
|
|
dataArr = append(dataArr, cache)
|
|
}
|
|
if len(dataArr) < 2 {
|
|
return res, fmt.Errorf("invalid line")
|
|
}
|
|
if strings.HasPrefix(dataArr[1], "#") {
|
|
return res, fmt.Errorf("invalid line")
|
|
}
|
|
if net.ParseIP(dataArr[0]) == nil {
|
|
return res, fmt.Errorf("invalid ip address")
|
|
}
|
|
for k, v := range dataArr {
|
|
switch k {
|
|
case 0:
|
|
res.ip = v
|
|
default:
|
|
if strings.HasPrefix(v, "#") {
|
|
res.comment = v
|
|
} else {
|
|
res.host = append(res.host, v)
|
|
}
|
|
}
|
|
}
|
|
res.valid = true
|
|
return res, nil
|
|
}
|
|
|
|
func (h *Host) List() []*HostNode {
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
var res []*HostNode
|
|
nextUid := h.firstUid
|
|
for {
|
|
if nextUid == 0 {
|
|
break
|
|
}
|
|
res = append(res, cloneHostNode(h.fulldata[nextUid]))
|
|
nextUid = h.fulldata[nextUid].nextuid
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (h *Host) ListHosts() map[string][]string {
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
if h.hostData == nil {
|
|
return nil
|
|
}
|
|
var res = make(map[string][]string)
|
|
for k, v := range h.hostData {
|
|
for _, vv := range v {
|
|
res[k] = append(res[k], vv.ip)
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (h *Host) ListIPs() map[string][]string {
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
if h.ipData == nil {
|
|
return nil
|
|
}
|
|
var res = make(map[string][]string)
|
|
for k, v := range h.ipData {
|
|
for _, vv := range v {
|
|
res[k] = append(res[k], vv.host...)
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (h *Host) ListByHost(host string) []*HostNode {
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
if h.hostData == nil {
|
|
return nil
|
|
}
|
|
return cloneHostNodes(h.hostData[host])
|
|
}
|
|
|
|
func (h *Host) ListIPsByHost(host string) []string {
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
if h.hostData == nil {
|
|
return nil
|
|
}
|
|
var res []string
|
|
for _, v := range h.hostData[host] {
|
|
res = append(res, v.ip)
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (h *Host) ListFirstIPByHost(host string) string {
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
if h.hostData == nil {
|
|
return ""
|
|
}
|
|
for _, v := range h.hostData[host] {
|
|
return v.ip
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (h *Host) ListByIP(ip string) []*HostNode {
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
if h.ipData == nil {
|
|
return nil
|
|
}
|
|
return cloneHostNodes(h.ipData[ip])
|
|
}
|
|
|
|
func (h *Host) ListHostsByIP(ip string) []string {
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
return h.listHostsByIP(ip)
|
|
}
|
|
|
|
func (h *Host) listHostsByIP(ip string) []string {
|
|
if h.ipData == nil {
|
|
return nil
|
|
}
|
|
var res []string
|
|
for _, v := range h.ipData[ip] {
|
|
res = append(res, v.host...)
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (h *Host) ListFirstHostByIP(ip string) string {
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
if h.ipData == nil {
|
|
return ""
|
|
}
|
|
for _, v := range h.ipData[ip] {
|
|
if len(v.host) > 0 {
|
|
return v.host[0]
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (h *Host) AddHosts(ip string, hosts ...string) error {
|
|
return h.addHosts("", ip, hosts...)
|
|
}
|
|
|
|
func (h *Host) AddHostsComment(comment string, ip string, hosts ...string) error {
|
|
return h.addHosts(comment, ip, hosts...)
|
|
}
|
|
|
|
func (h *Host) RemoveIPHosts(ip string, hosts ...string) error {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
if h.ipData == nil {
|
|
return fmt.Errorf("hosts data not initialized")
|
|
}
|
|
ipInfo := h.ipData[ip]
|
|
if len(ipInfo) == 0 {
|
|
return nil
|
|
}
|
|
cntfor:
|
|
for _, v := range ipInfo {
|
|
for _, host := range hosts {
|
|
if len(v.host) == 1 && v.host[0] == host {
|
|
h.removeNodeFromIPDataLocked(ip, v.uid)
|
|
h.removeNodeFromHostDataLocked(host, v.uid)
|
|
h.unlinkNodeLocked(v)
|
|
v = nil
|
|
continue cntfor
|
|
}
|
|
if len(v.host) > 1 {
|
|
var newHosts []string
|
|
removed := false
|
|
for _, vv := range v.host {
|
|
if vv != host {
|
|
newHosts = append(newHosts, vv)
|
|
} else {
|
|
removed = true
|
|
}
|
|
}
|
|
if !removed {
|
|
continue
|
|
}
|
|
v.host = newHosts
|
|
h.removeNodeFromHostDataLocked(host, v.uid)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *Host) RemoveIPs(ips ...string) error {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
if h.ipData == nil {
|
|
return fmt.Errorf("hosts data not initialized")
|
|
}
|
|
for _, ip := range ips {
|
|
h.removeIPLocked(ip)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *Host) RemoveHosts(hosts ...string) error {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
if h.hostData == nil {
|
|
return fmt.Errorf("hosts data not initialized")
|
|
}
|
|
for _, host := range hosts {
|
|
h.removeHostLocked(host)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *Host) SetIPHosts(ip string, hosts ...string) error {
|
|
var err error
|
|
ip, err = normalizeIPToken(ip)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
hosts, err = normalizeHostTokens(hosts...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(hosts) == 0 {
|
|
return fmt.Errorf("empty host")
|
|
}
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
if h.ipData == nil {
|
|
return fmt.Errorf("hosts data not initialized")
|
|
}
|
|
info := h.ipData[ip]
|
|
if len(info) == 0 {
|
|
return h.addHostsLocked("", ip, hosts...)
|
|
} else if len(info) == 1 {
|
|
node := *info[0]
|
|
node.host = append([]string(nil), hosts...)
|
|
node.comment = ""
|
|
return h.updateNodeLocked(&node)
|
|
}
|
|
h.removeIPLocked(ip)
|
|
return h.addHostsLocked("", ip, hosts...)
|
|
}
|
|
|
|
func (h *Host) SetHostIPs(host string, ips ...string) error {
|
|
if len(ips) == 0 {
|
|
return fmt.Errorf("no ip address")
|
|
}
|
|
var err error
|
|
host, err = normalizeHostToken(host)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
normalizedIPs := make([]string, 0, len(ips))
|
|
for _, ip := range ips {
|
|
normalizedIP, err := normalizeIPToken(ip)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
normalizedIPs = append(normalizedIPs, normalizedIP)
|
|
}
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
if h.hostData == nil {
|
|
return fmt.Errorf("hosts data not initialized")
|
|
}
|
|
h.removeHostLocked(host)
|
|
for _, ip := range normalizedIPs {
|
|
err := h.addHostsLocked("", ip, host)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *Host) addHosts(comment string, ip string, hosts ...string) error {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
return h.addHostsLocked(comment, ip, hosts...)
|
|
}
|
|
|
|
func (h *Host) addHostsLocked(comment string, ip string, hosts ...string) error {
|
|
if h.hostData == nil {
|
|
return fmt.Errorf("hosts data not initialized")
|
|
}
|
|
var err error
|
|
ip, err = normalizeIPToken(ip)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ipInfo := h.listHostsByIP(ip)
|
|
var needAddHosts []string
|
|
for _, v := range hosts {
|
|
host, err := normalizeHostToken(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !inArray(ipInfo, host) && !inArray(needAddHosts, host) {
|
|
needAddHosts = append(needAddHosts, host)
|
|
}
|
|
}
|
|
if len(needAddHosts) == 0 {
|
|
return nil
|
|
}
|
|
if comment != "" && !strings.HasPrefix(strings.TrimSpace(comment), "#") {
|
|
comment = "#" + comment
|
|
}
|
|
hostNode := HostNode{
|
|
ip: ip,
|
|
host: needAddHosts,
|
|
valid: true,
|
|
comment: comment,
|
|
}
|
|
if !hostNode.CheckValid() {
|
|
return fmt.Errorf("invalid hosts node")
|
|
}
|
|
h.appendNodeLocked(&hostNode)
|
|
return nil
|
|
}
|
|
|
|
func (h *Host) removeHostLocked(host string) {
|
|
hostInfo := h.hostData[host]
|
|
if len(hostInfo) == 0 {
|
|
return
|
|
}
|
|
delete(h.hostData, host)
|
|
for _, v := range hostInfo {
|
|
var newHosts []string
|
|
for _, vv := range v.host {
|
|
if vv != host {
|
|
newHosts = append(newHosts, vv)
|
|
}
|
|
}
|
|
v.host = newHosts
|
|
if len(v.host) == 0 {
|
|
h.removeNodeFromIPDataLocked(v.ip, v.uid)
|
|
h.unlinkNodeLocked(v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *Host) removeIPLocked(ip string) {
|
|
ipInfo := h.ipData[ip]
|
|
if len(ipInfo) == 0 {
|
|
return
|
|
}
|
|
delete(h.ipData, ip)
|
|
for _, v := range ipInfo {
|
|
for _, host := range v.host {
|
|
h.removeNodeFromHostDataLocked(host, v.uid)
|
|
}
|
|
h.unlinkNodeLocked(v)
|
|
}
|
|
}
|
|
|
|
func (h *Host) InsertNodeByData(node *HostNode, before bool, comment string, ip string, hosts ...string) error {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
if h.hostData == nil {
|
|
return fmt.Errorf("hosts data not initialized")
|
|
}
|
|
if node == nil {
|
|
return fmt.Errorf("node not exists")
|
|
}
|
|
anchor, ok := h.fulldata[node.uid]
|
|
if !ok {
|
|
return fmt.Errorf("node not exists")
|
|
}
|
|
if comment != "" && !strings.HasPrefix(strings.TrimSpace(comment), "#") {
|
|
comment = "#" + comment
|
|
}
|
|
ip = strings.TrimSpace(ip)
|
|
var err error
|
|
hosts, err = normalizeHostTokens(hosts...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ip != "" || len(hosts) > 0 {
|
|
ip, err = normalizeIPToken(ip)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(hosts) == 0 {
|
|
return fmt.Errorf("empty host")
|
|
}
|
|
}
|
|
if ip == "" && len(hosts) == 0 && comment == "" {
|
|
return fmt.Errorf("empty node")
|
|
}
|
|
hostNode := HostNode{
|
|
ip: ip,
|
|
host: hosts,
|
|
valid: true,
|
|
comment: comment,
|
|
}
|
|
if ip == "" && len(hosts) == 0 && comment != "" {
|
|
hostNode.onlyComment = true
|
|
}
|
|
return h.insertNodeRelativeLocked(anchor, before, &hostNode)
|
|
}
|
|
|
|
func normalizeIPToken(ip string) (string, error) {
|
|
ip = strings.TrimSpace(ip)
|
|
if net.ParseIP(ip) == nil {
|
|
return "", fmt.Errorf("invalid ip address")
|
|
}
|
|
return ip, nil
|
|
}
|
|
|
|
func normalizeHostToken(host string) (string, error) {
|
|
host = strings.TrimSpace(host)
|
|
if host == "" {
|
|
return "", fmt.Errorf("empty host")
|
|
}
|
|
if strings.ContainsAny(host, " \t\r\n") || strings.HasPrefix(host, "#") {
|
|
return "", fmt.Errorf("invalid host")
|
|
}
|
|
return host, nil
|
|
}
|
|
|
|
func normalizeHostTokens(hosts ...string) ([]string, error) {
|
|
out := make([]string, 0, len(hosts))
|
|
seen := make(map[string]struct{}, len(hosts))
|
|
for _, host := range hosts {
|
|
normalized, err := normalizeHostToken(host)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, ok := seen[normalized]; ok {
|
|
continue
|
|
}
|
|
seen[normalized] = struct{}{}
|
|
out = append(out, normalized)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (h *Host) DeleteNode(node *HostNode) error {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
if h.hostData == nil {
|
|
return fmt.Errorf("hosts data not initialized")
|
|
}
|
|
if node == nil {
|
|
return fmt.Errorf("node not exists")
|
|
}
|
|
current, ok := h.fulldata[node.uid]
|
|
if !ok {
|
|
return fmt.Errorf("node not exists")
|
|
}
|
|
for _, v := range current.host {
|
|
h.removeNodeFromHostDataLocked(v, current.uid)
|
|
}
|
|
if current.ip != "" {
|
|
h.removeNodeFromIPDataLocked(current.ip, current.uid)
|
|
}
|
|
h.unlinkNodeLocked(current)
|
|
return nil
|
|
}
|
|
|
|
func (h *Host) DeleteNodeByUID(uid uint64) error {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
node, ok := h.fulldata[uid]
|
|
if !ok {
|
|
return fmt.Errorf("node not exists")
|
|
}
|
|
for _, v := range node.host {
|
|
h.removeNodeFromHostDataLocked(v, node.uid)
|
|
}
|
|
if node.ip != "" {
|
|
h.removeNodeFromIPDataLocked(node.ip, node.uid)
|
|
}
|
|
h.unlinkNodeLocked(node)
|
|
return nil
|
|
}
|
|
|
|
func (h *Host) AddNode(node *HostNode) error {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
if h.hostData == nil {
|
|
return fmt.Errorf("hosts data not initialized")
|
|
}
|
|
prepared, err := normalizeEditableNode(node)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
h.appendNodeLocked(prepared)
|
|
copyHostNodeState(node, prepared)
|
|
return nil
|
|
}
|
|
|
|
func (h *Host) InsertNode(node *HostNode) error {
|
|
if node.lastuid == 0 && node.nextuid == 0 {
|
|
return h.AddNode(node)
|
|
}
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
if h.hostData == nil {
|
|
return fmt.Errorf("hosts data not initialized")
|
|
}
|
|
prepared, err := normalizeEditableNode(node)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
prepared.uid = h.idx + 1
|
|
lastNode := h.fulldata[prepared.lastuid]
|
|
nextNode := h.fulldata[prepared.nextuid]
|
|
if lastNode == nil || nextNode == nil {
|
|
return fmt.Errorf("node link not exists")
|
|
}
|
|
if lastNode.nextuid != prepared.nextuid || nextNode.lastuid != prepared.lastuid {
|
|
return fmt.Errorf("node lastuid nextuid not match")
|
|
}
|
|
h.idx++
|
|
lastNode.nextuid = prepared.uid
|
|
nextNode.lastuid = prepared.uid
|
|
h.storeNodeLocked(prepared)
|
|
copyHostNodeState(node, prepared)
|
|
return nil
|
|
}
|
|
|
|
func (h *Host) appendNodeLocked(node *HostNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
h.idx++
|
|
node.uid = h.idx
|
|
node.lastuid = h.lastUid
|
|
node.nextuid = 0
|
|
if h.lastUid != 0 {
|
|
if last := h.fulldata[h.lastUid]; last != nil {
|
|
last.nextuid = node.uid
|
|
}
|
|
} else {
|
|
h.firstUid = node.uid
|
|
}
|
|
if h.firstUid == 0 {
|
|
h.firstUid = node.uid
|
|
}
|
|
h.lastUid = node.uid
|
|
h.storeNodeLocked(node)
|
|
}
|
|
|
|
func (h *Host) insertNodeRelativeLocked(anchor *HostNode, before bool, node *HostNode) error {
|
|
if anchor == nil || node == nil {
|
|
return fmt.Errorf("node not exists")
|
|
}
|
|
h.idx++
|
|
node.uid = h.idx
|
|
if before {
|
|
node.nextuid = anchor.uid
|
|
node.lastuid = anchor.lastuid
|
|
if anchor.lastuid != 0 {
|
|
prev := h.fulldata[anchor.lastuid]
|
|
if prev == nil {
|
|
return fmt.Errorf("node lastuid not exists")
|
|
}
|
|
prev.nextuid = node.uid
|
|
} else {
|
|
h.firstUid = node.uid
|
|
}
|
|
anchor.lastuid = node.uid
|
|
} else {
|
|
node.lastuid = anchor.uid
|
|
node.nextuid = anchor.nextuid
|
|
if anchor.nextuid != 0 {
|
|
next := h.fulldata[anchor.nextuid]
|
|
if next == nil {
|
|
return fmt.Errorf("node nextuid not exists")
|
|
}
|
|
next.lastuid = node.uid
|
|
} else {
|
|
h.lastUid = node.uid
|
|
}
|
|
anchor.nextuid = node.uid
|
|
}
|
|
if h.firstUid == 0 {
|
|
h.firstUid = node.uid
|
|
}
|
|
if node.nextuid == 0 {
|
|
h.lastUid = node.uid
|
|
}
|
|
node.valid = node.CheckValid()
|
|
h.storeNodeLocked(node)
|
|
return nil
|
|
}
|
|
|
|
func (h *Host) storeNodeLocked(node *HostNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
stored := cloneHostNode(node)
|
|
h.fulldata[stored.uid] = stored
|
|
if !stored.valid {
|
|
return
|
|
}
|
|
if stored.ip != "" {
|
|
h.ipData[stored.ip] = append(h.ipData[stored.ip], stored)
|
|
}
|
|
for _, v := range stored.host {
|
|
h.hostData[v] = append(h.hostData[v], stored)
|
|
}
|
|
}
|
|
|
|
func (h *Host) removeNodeFromIPDataLocked(ip string, uid uint64) {
|
|
var newIPData []*HostNode
|
|
for _, node := range h.ipData[ip] {
|
|
if node.uid != uid {
|
|
newIPData = append(newIPData, node)
|
|
}
|
|
}
|
|
if len(newIPData) == 0 {
|
|
delete(h.ipData, ip)
|
|
return
|
|
}
|
|
h.ipData[ip] = newIPData
|
|
}
|
|
|
|
func (h *Host) removeNodeFromHostDataLocked(host string, uid uint64) {
|
|
var newHostData []*HostNode
|
|
for _, node := range h.hostData[host] {
|
|
if node.uid != uid {
|
|
newHostData = append(newHostData, node)
|
|
}
|
|
}
|
|
if len(newHostData) == 0 {
|
|
delete(h.hostData, host)
|
|
return
|
|
}
|
|
h.hostData[host] = newHostData
|
|
}
|
|
|
|
func (h *Host) unlinkNodeLocked(node *HostNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
if node.lastuid != 0 {
|
|
if last := h.fulldata[node.lastuid]; last != nil {
|
|
last.nextuid = node.nextuid
|
|
}
|
|
} else {
|
|
h.firstUid = node.nextuid
|
|
}
|
|
if node.nextuid != 0 {
|
|
if next := h.fulldata[node.nextuid]; next != nil {
|
|
next.lastuid = node.lastuid
|
|
}
|
|
} else {
|
|
h.lastUid = node.lastuid
|
|
}
|
|
delete(h.fulldata, node.uid)
|
|
}
|
|
|
|
func inArray(arr []string, v string) bool {
|
|
for _, vv := range arr {
|
|
if v == vv {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (h *Host) SaveAs(path string) error {
|
|
return h.save(path)
|
|
}
|
|
|
|
func (h *Host) Save() error {
|
|
return h.save(h.hostPath)
|
|
}
|
|
|
|
func (h *Host) save(path string) error {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
if h.fulldata == nil {
|
|
return fmt.Errorf("no data")
|
|
}
|
|
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
|
if err != nil {
|
|
return fmt.Errorf("open hosts file %s error: %s", h.hostPath, err)
|
|
}
|
|
defer f.Close()
|
|
if h.firstUid == 0 {
|
|
return nil
|
|
}
|
|
node := h.fulldata[h.firstUid]
|
|
if node == nil {
|
|
return fmt.Errorf("no data")
|
|
}
|
|
for {
|
|
if node.onlyComment {
|
|
if node.original != "" && strings.TrimSpace(node.original) == node.comment {
|
|
if _, err := f.WriteString(node.original + lineBreaker); err != nil {
|
|
return fmt.Errorf("write hosts file error: %s", err)
|
|
}
|
|
} else if _, err := f.WriteString(node.comment + lineBreaker); err != nil {
|
|
return fmt.Errorf("write hosts file error: %s", err)
|
|
}
|
|
if node.nextuid == 0 {
|
|
break
|
|
}
|
|
node = h.fulldata[node.nextuid]
|
|
continue
|
|
}
|
|
if !node.valid {
|
|
if _, err := f.WriteString(node.original + lineBreaker); err != nil {
|
|
return fmt.Errorf("write hosts file error: %s", err)
|
|
}
|
|
if node.nextuid == 0 {
|
|
break
|
|
}
|
|
node = h.fulldata[node.nextuid]
|
|
continue
|
|
}
|
|
if _, err := f.WriteString(node.ip + " "); err != nil {
|
|
return fmt.Errorf("write hosts file error: %s", err)
|
|
}
|
|
if _, err := f.WriteString(strings.Join(node.host, " ")); err != nil {
|
|
return fmt.Errorf("write hosts file error: %s", err)
|
|
}
|
|
if len(node.comment) > 0 {
|
|
if _, err := f.WriteString(" " + node.comment); err != nil {
|
|
return fmt.Errorf("write hosts file error: %s", err)
|
|
}
|
|
}
|
|
if _, err := f.WriteString(lineBreaker); err != nil {
|
|
return fmt.Errorf("write hosts file error: %s", err)
|
|
}
|
|
if node.nextuid == 0 {
|
|
break
|
|
}
|
|
node = h.fulldata[node.nextuid]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *Host) Build() ([]byte, error) {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
if h.fulldata == nil {
|
|
return nil, fmt.Errorf("no data")
|
|
}
|
|
if h.firstUid == 0 {
|
|
return []byte{}, nil
|
|
}
|
|
var f bytes.Buffer
|
|
node := h.fulldata[h.firstUid]
|
|
if node == nil {
|
|
return nil, fmt.Errorf("no data")
|
|
}
|
|
for {
|
|
if node.onlyComment {
|
|
if node.original != "" && strings.TrimSpace(node.original) == node.comment {
|
|
if _, err := f.WriteString(node.original + lineBreaker); err != nil {
|
|
return nil, fmt.Errorf("write hosts file error: %s", err)
|
|
}
|
|
} else if _, err := f.WriteString(node.comment + lineBreaker); err != nil {
|
|
return nil, fmt.Errorf("write hosts file error: %s", err)
|
|
}
|
|
if node.nextuid == 0 {
|
|
break
|
|
}
|
|
node = h.fulldata[node.nextuid]
|
|
continue
|
|
}
|
|
if !node.valid {
|
|
if _, err := f.WriteString(node.original + lineBreaker); err != nil {
|
|
return nil, fmt.Errorf("write hosts file error: %s", err)
|
|
}
|
|
if node.nextuid == 0 {
|
|
break
|
|
}
|
|
node = h.fulldata[node.nextuid]
|
|
continue
|
|
}
|
|
if _, err := f.WriteString(node.ip + " "); err != nil {
|
|
return nil, fmt.Errorf("write hosts file error: %s", err)
|
|
}
|
|
if _, err := f.WriteString(strings.Join(node.host, " ")); err != nil {
|
|
return nil, fmt.Errorf("write hosts file error: %s", err)
|
|
}
|
|
if len(node.comment) > 0 {
|
|
if _, err := f.WriteString(" " + node.comment); err != nil {
|
|
return nil, fmt.Errorf("write hosts file error: %s", err)
|
|
}
|
|
}
|
|
if _, err := f.WriteString(lineBreaker); err != nil {
|
|
return nil, fmt.Errorf("write hosts file error: %s", err)
|
|
}
|
|
if node.nextuid == 0 {
|
|
break
|
|
}
|
|
node = h.fulldata[node.nextuid]
|
|
}
|
|
return f.Bytes(), nil
|
|
}
|
|
|
|
func (h *Host) GetFirstNode() (*HostNode, error) {
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
if h.firstUid == 0 {
|
|
return nil, fmt.Errorf("no data")
|
|
}
|
|
if h.fulldata == nil {
|
|
return nil, fmt.Errorf("no data")
|
|
}
|
|
return cloneHostNode(h.fulldata[h.firstUid]), nil
|
|
}
|
|
|
|
func (h *Host) GetNode(nodeID uint64) (*HostNode, error) {
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
if h.fulldata == nil {
|
|
return nil, fmt.Errorf("no data")
|
|
}
|
|
if node, ok := h.fulldata[nodeID]; ok {
|
|
return cloneHostNode(node), nil
|
|
}
|
|
return nil, fmt.Errorf("node not exists")
|
|
}
|
|
|
|
func (h *Host) GetNextNode(nodeID uint64) (*HostNode, error) {
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
if h.fulldata == nil {
|
|
return nil, fmt.Errorf("no data")
|
|
}
|
|
if node, ok := h.fulldata[nodeID]; ok {
|
|
if node.nextuid == 0 {
|
|
return nil, fmt.Errorf("no next node")
|
|
}
|
|
return cloneHostNode(h.fulldata[node.nextuid]), nil
|
|
}
|
|
return nil, fmt.Errorf("node not exists")
|
|
}
|
|
|
|
func (h *Host) GetLastNode(nodeID uint64) (*HostNode, error) {
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
if h.fulldata == nil {
|
|
return nil, fmt.Errorf("no data")
|
|
}
|
|
if node, ok := h.fulldata[nodeID]; ok {
|
|
if node.lastuid == 0 {
|
|
return nil, fmt.Errorf("no last node")
|
|
}
|
|
return cloneHostNode(h.fulldata[node.lastuid]), nil
|
|
}
|
|
return nil, fmt.Errorf("node not exists")
|
|
}
|
|
|
|
func (h *Host) GetLatestNode() (*HostNode, error) {
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
if h.lastUid == 0 {
|
|
return nil, fmt.Errorf("no data")
|
|
}
|
|
if h.fulldata == nil {
|
|
return nil, fmt.Errorf("no data")
|
|
}
|
|
return cloneHostNode(h.fulldata[h.lastUid]), nil
|
|
}
|
|
|
|
func (h *Host) UpdateNode(node *HostNode) error {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
return h.updateNodeLocked(node)
|
|
}
|
|
|
|
func (h *Host) updateNodeLocked(node *HostNode) error {
|
|
if node == nil {
|
|
return fmt.Errorf("node not exists")
|
|
}
|
|
if h.fulldata == nil {
|
|
return fmt.Errorf("no data")
|
|
}
|
|
current, ok := h.fulldata[node.uid]
|
|
if !ok {
|
|
return fmt.Errorf("node not exists")
|
|
}
|
|
candidate, err := normalizeEditableNode(node)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
candidate.uid = current.uid
|
|
candidate.lastuid = current.lastuid
|
|
candidate.nextuid = current.nextuid
|
|
if candidate.original == "" {
|
|
candidate.original = current.original
|
|
}
|
|
for _, host := range current.host {
|
|
h.removeNodeFromHostDataLocked(host, current.uid)
|
|
}
|
|
if current.ip != "" {
|
|
h.removeNodeFromIPDataLocked(current.ip, current.uid)
|
|
}
|
|
h.storeNodeLocked(candidate)
|
|
copyHostNodeState(node, candidate)
|
|
return nil
|
|
}
|
|
|
|
func (n *HostNode) String() string {
|
|
if n.onlyComment {
|
|
return n.comment
|
|
}
|
|
return fmt.Sprintf("%s %s %s", n.ip, strings.Join(n.host, " "), n.comment)
|
|
}
|
|
|
|
func (n *HostNode) Bytes() []byte {
|
|
return []byte(n.String())
|
|
}
|
|
|
|
func (n *HostNode) Hosts() []string {
|
|
return n.host
|
|
}
|
|
|
|
func (n *HostNode) IP() string {
|
|
return n.ip
|
|
}
|
|
|
|
func (n *HostNode) Comment() string {
|
|
return n.comment
|
|
}
|
|
|
|
func (n *HostNode) UID() uint64 {
|
|
return n.uid
|
|
}
|
|
|
|
func (n *HostNode) NextUID() uint64 {
|
|
return n.nextuid
|
|
}
|
|
|
|
func (n *HostNode) LastUID() uint64 {
|
|
return n.lastuid
|
|
}
|
|
|
|
func (n *HostNode) Valid() bool {
|
|
return n.valid
|
|
}
|
|
|
|
func (n *HostNode) OnlyComment() bool {
|
|
return n.onlyComment
|
|
}
|
|
|
|
func (n *HostNode) Original() string {
|
|
return n.original
|
|
}
|
|
|
|
func (n *HostNode) SetHosts(hosts ...string) {
|
|
n.host = hosts
|
|
}
|
|
|
|
func (n *HostNode) SetIP(ip string) {
|
|
n.ip = ip
|
|
}
|
|
|
|
func (n *HostNode) SetComment(comment string) {
|
|
if comment == "" {
|
|
return
|
|
}
|
|
if !strings.HasPrefix(strings.TrimSpace(comment), "#") {
|
|
n.comment = "#" + comment
|
|
return
|
|
}
|
|
n.comment = comment
|
|
}
|
|
|
|
func (n *HostNode) SetValid(valid bool) {
|
|
n.valid = valid
|
|
}
|
|
|
|
func (n *HostNode) SetOnlyComment(onlyComment bool) {
|
|
n.onlyComment = onlyComment
|
|
}
|
|
|
|
func (n *HostNode) SetOriginal(original string) {
|
|
n.original = original
|
|
}
|
|
|
|
func (n *HostNode) AddHosts(hosts ...string) {
|
|
n.host = append(n.host, hosts...)
|
|
}
|
|
|
|
func (n *HostNode) SetLastUID(uid uint64) error {
|
|
if n.uid != 0 {
|
|
return fmt.Errorf("uid already set,you cannot change the existing node")
|
|
}
|
|
n.lastuid = uid
|
|
return nil
|
|
}
|
|
|
|
func (n *HostNode) SetNextUID(uid uint64) error {
|
|
if n.uid != 0 {
|
|
return fmt.Errorf("uid already set,you cannot change the existing node")
|
|
}
|
|
n.nextuid = uid
|
|
return nil
|
|
}
|
|
|
|
func (n *HostNode) CheckValid() bool {
|
|
if n.ip == "" && len(n.host) == 0 && n.comment == "" {
|
|
return true
|
|
}
|
|
if n.ip == "" && len(n.host) == 0 && n.comment != "" {
|
|
return true
|
|
}
|
|
if n.ip == "" && len(n.host) > 0 {
|
|
return false
|
|
}
|
|
if net.ParseIP(n.ip) == nil {
|
|
return false
|
|
}
|
|
return true
|
|
}
|