github.com/khulnasoft-lab/khulnasoft@v26.0.1-0.20240328202558-330a6f959fe0+incompatible/libnetwork/etchosts/etchosts.go (about) 1 package etchosts 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "io" 8 "net/netip" 9 "os" 10 "regexp" 11 "sync" 12 ) 13 14 // Record Structure for a single host record 15 type Record struct { 16 Hosts string 17 IP string 18 } 19 20 // WriteTo writes record to file and returns bytes written or error 21 func (r Record) WriteTo(w io.Writer) (int64, error) { 22 n, err := fmt.Fprintf(w, "%s\t%s\n", r.IP, r.Hosts) 23 return int64(n), err 24 } 25 26 var ( 27 // Default hosts config records slice 28 defaultContentIPv4 = []Record{ 29 {Hosts: "localhost", IP: "127.0.0.1"}, 30 } 31 defaultContentIPv6 = []Record{ 32 {Hosts: "localhost ip6-localhost ip6-loopback", IP: "::1"}, 33 {Hosts: "ip6-localnet", IP: "fe00::0"}, 34 {Hosts: "ip6-mcastprefix", IP: "ff00::0"}, 35 {Hosts: "ip6-allnodes", IP: "ff02::1"}, 36 {Hosts: "ip6-allrouters", IP: "ff02::2"}, 37 } 38 39 // A cache of path level locks for synchronizing /etc/hosts 40 // updates on a file level 41 pathMap = make(map[string]*sync.Mutex) 42 43 // A package level mutex to synchronize the cache itself 44 pathMutex sync.Mutex 45 ) 46 47 func pathLock(path string) func() { 48 pathMutex.Lock() 49 defer pathMutex.Unlock() 50 51 pl, ok := pathMap[path] 52 if !ok { 53 pl = &sync.Mutex{} 54 pathMap[path] = pl 55 } 56 57 pl.Lock() 58 return func() { 59 pl.Unlock() 60 } 61 } 62 63 // Drop drops the path string from the path cache 64 func Drop(path string) { 65 pathMutex.Lock() 66 defer pathMutex.Unlock() 67 68 delete(pathMap, path) 69 } 70 71 // Build function 72 // path is path to host file string required 73 // extraContent is an array of extra host records. 74 func Build(path string, extraContent []Record) error { 75 return build(path, defaultContentIPv4, defaultContentIPv6, extraContent) 76 } 77 78 // BuildNoIPv6 is the same as Build, but will not include IPv6 entries. 79 func BuildNoIPv6(path string, extraContent []Record) error { 80 var ipv4ExtraContent []Record 81 for _, rec := range extraContent { 82 addr, err := netip.ParseAddr(rec.IP) 83 if err != nil || !addr.Is6() { 84 ipv4ExtraContent = append(ipv4ExtraContent, rec) 85 } 86 } 87 return build(path, defaultContentIPv4, ipv4ExtraContent) 88 } 89 90 func build(path string, contents ...[]Record) error { 91 defer pathLock(path)() 92 93 buf := bytes.NewBuffer(nil) 94 95 // Write content from function arguments 96 for _, content := range contents { 97 for _, c := range content { 98 if _, err := c.WriteTo(buf); err != nil { 99 return err 100 } 101 } 102 } 103 104 return os.WriteFile(path, buf.Bytes(), 0o644) 105 } 106 107 // Add adds an arbitrary number of Records to an already existing /etc/hosts file 108 func Add(path string, recs []Record) error { 109 defer pathLock(path)() 110 111 if len(recs) == 0 { 112 return nil 113 } 114 115 b, err := mergeRecords(path, recs) 116 if err != nil { 117 return err 118 } 119 120 return os.WriteFile(path, b, 0o644) 121 } 122 123 func mergeRecords(path string, recs []Record) ([]byte, error) { 124 f, err := os.Open(path) 125 if err != nil { 126 return nil, err 127 } 128 defer f.Close() 129 130 content := bytes.NewBuffer(nil) 131 132 if _, err := content.ReadFrom(f); err != nil { 133 return nil, err 134 } 135 136 for _, r := range recs { 137 if _, err := r.WriteTo(content); err != nil { 138 return nil, err 139 } 140 } 141 142 return content.Bytes(), nil 143 } 144 145 // Delete deletes an arbitrary number of Records already existing in /etc/hosts file 146 func Delete(path string, recs []Record) error { 147 defer pathLock(path)() 148 149 if len(recs) == 0 { 150 return nil 151 } 152 old, err := os.Open(path) 153 if err != nil { 154 return err 155 } 156 157 var buf bytes.Buffer 158 159 s := bufio.NewScanner(old) 160 eol := []byte{'\n'} 161 loop: 162 for s.Scan() { 163 b := s.Bytes() 164 if len(b) == 0 { 165 continue 166 } 167 168 if b[0] == '#' { 169 buf.Write(b) 170 buf.Write(eol) 171 continue 172 } 173 for _, r := range recs { 174 if bytes.HasSuffix(b, []byte("\t"+r.Hosts)) { 175 continue loop 176 } 177 } 178 buf.Write(b) 179 buf.Write(eol) 180 } 181 old.Close() 182 if err := s.Err(); err != nil { 183 return err 184 } 185 return os.WriteFile(path, buf.Bytes(), 0o644) 186 } 187 188 // Update all IP addresses where hostname matches. 189 // path is path to host file 190 // IP is new IP address 191 // hostname is hostname to search for to replace IP 192 func Update(path, IP, hostname string) error { 193 defer pathLock(path)() 194 195 old, err := os.ReadFile(path) 196 if err != nil { 197 return err 198 } 199 re := regexp.MustCompile(fmt.Sprintf("(\\S*)(\\t%s)(\\s|\\.)", regexp.QuoteMeta(hostname))) 200 return os.WriteFile(path, re.ReplaceAll(old, []byte(IP+"$2"+"$3")), 0o644) 201 }