Pull request 2149: 6711 watch hosts

Updates #6711.

Squashed commit of the following:

commit 3ddfe809f76325c2d4cda0715a7bcc15e76a2388
Merge: 185957cd0 d338451fa
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Feb 13 13:01:30 2024 +0300

    Merge branch 'master' into 6711-watch-hosts

commit 185957cd01516e5955e388108615e6f131d6ad71
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Feb 9 18:11:41 2024 +0300

    aghos: imp docs

commit 3afbbcbb7ab6cc60c7c40ef8acd5b3ddf52cb3d1
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Feb 9 15:40:02 2024 +0300

    all: upd golibs, imp fswatcher
This commit is contained in:
Eugene Burkov 2024-02-13 13:19:22 +03:00
parent d338451faf
commit 2a546aa609
18 changed files with 102 additions and 87 deletions

View File

@ -57,12 +57,17 @@ NOTE: Add new changes BELOW THIS COMMENT.
- Go 1.21 support. Future versions will require at least Go 1.22 to build. - Go 1.21 support. Future versions will require at least Go 1.22 to build.
### Fixed
- Incorrect tracking of the system hosts file's changes ([#6711]).
### Removed ### Removed
- Go 1.20 support, as it has reached end of life. - Go 1.20 support, as it has reached end of life.
[#5992]: https://github.com/AdguardTeam/AdGuardHome/issues/5992 [#5992]: https://github.com/AdguardTeam/AdGuardHome/issues/5992
[#6679]: https://github.com/AdguardTeam/AdGuardHome/issues/6679 [#6679]: https://github.com/AdguardTeam/AdGuardHome/issues/6679
[#6711]: https://github.com/AdguardTeam/AdGuardHome/issues/6711
[go-toolchain]: https://go.dev/blog/toolchain [go-toolchain]: https://go.dev/blog/toolchain

2
go.mod
View File

@ -4,7 +4,7 @@ go 1.21.7
require ( require (
github.com/AdguardTeam/dnsproxy v0.65.0 github.com/AdguardTeam/dnsproxy v0.65.0
github.com/AdguardTeam/golibs v0.20.0 github.com/AdguardTeam/golibs v0.20.1
github.com/AdguardTeam/urlfilter v0.17.3 github.com/AdguardTeam/urlfilter v0.17.3
github.com/NYTimes/gziphandler v1.1.1 github.com/NYTimes/gziphandler v1.1.1
github.com/ameshkov/dnscrypt/v2 v2.2.7 github.com/ameshkov/dnscrypt/v2 v2.2.7

4
go.sum
View File

@ -1,7 +1,7 @@
github.com/AdguardTeam/dnsproxy v0.65.0 h1:mqJjVSkqoqPwThY3tTvnLHQ/AYBYrfWmK2ER91fu4FE= github.com/AdguardTeam/dnsproxy v0.65.0 h1:mqJjVSkqoqPwThY3tTvnLHQ/AYBYrfWmK2ER91fu4FE=
github.com/AdguardTeam/dnsproxy v0.65.0/go.mod h1:AGYMLPk2zX+I3NIUYS12KUI296mkCyfoMF/luy2uqdk= github.com/AdguardTeam/dnsproxy v0.65.0/go.mod h1:AGYMLPk2zX+I3NIUYS12KUI296mkCyfoMF/luy2uqdk=
github.com/AdguardTeam/golibs v0.20.0 h1:A9FIdYq7wUKhFYy3z+YZ/Aw5oFUYgW+xgaVAJ0pnnPY= github.com/AdguardTeam/golibs v0.20.1 h1:ol8qLjWGZhU9paMMwN+OLWVTUigGsXa29iVTyd62VKY=
github.com/AdguardTeam/golibs v0.20.0/go.mod h1:3WunclLLfrVAq7fYQRhd6f168FHOEMssnipVXCxDL/w= github.com/AdguardTeam/golibs v0.20.1/go.mod h1:bgcMgRviCKyU6mkrX+RtT/OsKPFzyppelfRsksMG3KU=
github.com/AdguardTeam/urlfilter v0.17.3 h1:fg/ObbnO0Cv6aw0tW6N/ETDMhhNvmcUUOZ7HlmKC3rw= github.com/AdguardTeam/urlfilter v0.17.3 h1:fg/ObbnO0Cv6aw0tW6N/ETDMhhNvmcUUOZ7HlmKC3rw=
github.com/AdguardTeam/urlfilter v0.17.3/go.mod h1:Jru7jFfeH2CoDf150uDs+rRYcZBzHHBz05r9REyDKyE= github.com/AdguardTeam/urlfilter v0.17.3/go.mod h1:Jru7jFfeH2CoDf150uDs+rRYcZBzHHBz05r9REyDKyE=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=

View File

@ -67,6 +67,7 @@ func TestNewHostsContainer(t *testing.T) {
} }
hc, err := aghnet.NewHostsContainer(testFS, &aghtest.FSWatcher{ hc, err := aghnet.NewHostsContainer(testFS, &aghtest.FSWatcher{
OnStart: func() (_ error) { panic("not implemented") },
OnEvents: onEvents, OnEvents: onEvents,
OnAdd: onAdd, OnAdd: onAdd,
OnClose: func() (err error) { return nil }, OnClose: func() (err error) { return nil },
@ -93,6 +94,7 @@ func TestNewHostsContainer(t *testing.T) {
t.Run("nil_fs", func(t *testing.T) { t.Run("nil_fs", func(t *testing.T) {
require.Panics(t, func() { require.Panics(t, func() {
_, _ = aghnet.NewHostsContainer(nil, &aghtest.FSWatcher{ _, _ = aghnet.NewHostsContainer(nil, &aghtest.FSWatcher{
OnStart: func() (_ error) { panic("not implemented") },
// Those shouldn't panic. // Those shouldn't panic.
OnEvents: func() (e <-chan struct{}) { return nil }, OnEvents: func() (e <-chan struct{}) { return nil },
OnAdd: func(name string) (err error) { return nil }, OnAdd: func(name string) (err error) { return nil },
@ -111,6 +113,7 @@ func TestNewHostsContainer(t *testing.T) {
const errOnAdd errors.Error = "error" const errOnAdd errors.Error = "error"
errWatcher := &aghtest.FSWatcher{ errWatcher := &aghtest.FSWatcher{
OnStart: func() (_ error) { panic("not implemented") },
OnEvents: func() (e <-chan struct{}) { panic("not implemented") }, OnEvents: func() (e <-chan struct{}) { panic("not implemented") },
OnAdd: func(name string) (err error) { return errOnAdd }, OnAdd: func(name string) (err error) { return errOnAdd },
OnClose: func() (err error) { return nil }, OnClose: func() (err error) { return nil },
@ -155,6 +158,7 @@ func TestHostsContainer_refresh(t *testing.T) {
t.Cleanup(func() { close(eventsCh) }) t.Cleanup(func() { close(eventsCh) })
w := &aghtest.FSWatcher{ w := &aghtest.FSWatcher{
OnStart: func() (_ error) { panic("not implemented") },
OnEvents: func() (e <-chan event) { return eventsCh }, OnEvents: func() (e <-chan event) { return eventsCh },
OnAdd: func(name string) (err error) { OnAdd: func(name string) (err error) {
assert.Equal(t, "dir", name) assert.Equal(t, "dir", name)

View File

@ -17,6 +17,7 @@ import (
"github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/osutil"
) )
// DialContextFunc is the semantic alias for dialing functions, such as // DialContextFunc is the semantic alias for dialing functions, such as
@ -32,7 +33,7 @@ var (
netInterfaceAddrs = net.InterfaceAddrs netInterfaceAddrs = net.InterfaceAddrs
// rootDirFS is the filesystem pointing to the root directory. // rootDirFS is the filesystem pointing to the root directory.
rootDirFS = aghos.RootDirFS() rootDirFS = osutil.RootDirFS()
) )
// ErrNoStaticIPInfo is returned by IfaceHasStaticIP when no information about // ErrNoStaticIPInfo is returned by IfaceHasStaticIP when no information about

View File

@ -8,6 +8,8 @@ import (
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/osutil"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
) )
@ -18,31 +20,38 @@ type event = struct{}
// FSWatcher tracks all the fyle system events and notifies about those. // FSWatcher tracks all the fyle system events and notifies about those.
// //
// TODO(e.burkov, a.garipov): Move into another package like aghfs. // TODO(e.burkov, a.garipov): Move into another package like aghfs.
//
// TODO(e.burkov): Add tests.
type FSWatcher interface { type FSWatcher interface {
// Start starts watching the added files.
Start() (err error)
// Close stops watching the files and closes an update channel.
io.Closer io.Closer
// Events should return a read-only channel which notifies about events. // Events returns the channel to notify about the file system events.
Events() (e <-chan event) Events() (e <-chan event)
// Add should check if the file named name is accessible and starts tracking // Add starts tracking the file. It returns an error if the file can't be
// it. // tracked. It must not be called after Start.
Add(name string) (err error) Add(name string) (err error)
} }
// osWatcher tracks the file system provided by the OS. // osWatcher tracks the file system provided by the OS.
type osWatcher struct { type osWatcher struct {
// w is the actual notifier that is handled by osWatcher. // watcher is the actual notifier that is handled by osWatcher.
w *fsnotify.Watcher watcher *fsnotify.Watcher
// events is the channel to notify. // events is the channel to notify.
events chan event events chan event
// files is the set of tracked files.
files *stringutil.Set
} }
const (
// osWatcherPref is a prefix for logging and wrapping errors in osWathcer's // osWatcherPref is a prefix for logging and wrapping errors in osWathcer's
// methods. // methods.
osWatcherPref = "os watcher" const osWatcherPref = "os watcher"
)
// NewOSWritesWatcher creates FSWatcher that tracks the real file system of the // NewOSWritesWatcher creates FSWatcher that tracks the real file system of the
// OS and notifies only about writing events. // OS and notifies only about writing events.
@ -55,25 +64,27 @@ func NewOSWritesWatcher() (w FSWatcher, err error) {
return nil, fmt.Errorf("creating watcher: %w", err) return nil, fmt.Errorf("creating watcher: %w", err)
} }
fsw := &osWatcher{ return &osWatcher{
w: watcher, watcher: watcher,
events: make(chan event, 1), events: make(chan event, 1),
files: stringutil.NewSet(),
}, nil
} }
go fsw.handleErrors() // type check
go fsw.handleEvents() var _ FSWatcher = (*osWatcher)(nil)
return fsw, nil // Start implements the FSWatcher interface for *osWatcher.
func (w *osWatcher) Start() (err error) {
go w.handleErrors()
go w.handleEvents()
return nil
} }
// handleErrors handles accompanying errors. It is intended to be used as a // Close implements the FSWatcher interface for *osWatcher.
// goroutine. func (w *osWatcher) Close() (err error) {
func (w *osWatcher) handleErrors() { return w.watcher.Close()
defer log.OnPanic(fmt.Sprintf("%s: handling errors", osWatcherPref))
for err := range w.w.Errors {
log.Error("%s: %s", osWatcherPref, err)
}
} }
// Events implements the FSWatcher interface for *osWatcher. // Events implements the FSWatcher interface for *osWatcher.
@ -81,22 +92,30 @@ func (w *osWatcher) Events() (e <-chan event) {
return w.events return w.events
} }
// Add implements the FSWatcher interface for *osWatcher. // Add implements the [FSWatcher] interface for *osWatcher.
// //
// TODO(e.burkov): Make it accept non-existing files to detect it's creating. // TODO(e.burkov): Make it accept non-existing files to detect it's creating.
func (w *osWatcher) Add(name string) (err error) { func (w *osWatcher) Add(name string) (err error) {
defer func() { err = errors.Annotate(err, "%s: %w", osWatcherPref) }() defer func() { err = errors.Annotate(err, "%s: %w", osWatcherPref) }()
if _, err = fs.Stat(RootDirFS(), name); err != nil { fi, err := fs.Stat(osutil.RootDirFS(), name)
if err != nil {
return fmt.Errorf("checking file %q: %w", name, err) return fmt.Errorf("checking file %q: %w", name, err)
} }
return w.w.Add(filepath.Join("/", name)) name = filepath.Join("/", name)
w.files.Add(name)
// Watch the directory and filter the events by the file name, since the
// common recomendation to the fsnotify package is to watch the directory
// instead of the file itself.
//
// See https://pkg.go.dev/github.com/fsnotify/fsnotify@v1.7.0#readme-watching-a-file-doesn-t-work-well.
if !fi.IsDir() {
name = filepath.Dir(name)
} }
// Close implements the FSWatcher interface for *osWatcher. return w.watcher.Add(name)
func (w *osWatcher) Close() (err error) {
return w.w.Close()
} }
// handleEvents notifies about the received file system's event if needed. It // handleEvents notifies about the received file system's event if needed. It
@ -106,9 +125,9 @@ func (w *osWatcher) handleEvents() {
defer close(w.events) defer close(w.events)
ch := w.w.Events ch := w.watcher.Events
for e := range ch { for e := range ch {
if e.Op&fsnotify.Write == 0 { if e.Op&fsnotify.Write == 0 || !w.files.Has(e.Name) {
continue continue
} }
@ -131,3 +150,13 @@ func (w *osWatcher) handleEvents() {
} }
} }
} }
// handleErrors handles accompanying errors. It used to be called in a separate
// goroutine.
func (w *osWatcher) handleErrors() {
defer log.OnPanic(fmt.Sprintf("%s: handling errors", osWatcherPref))
for err := range w.watcher.Errors {
log.Error("%s: %s", osWatcherPref, err)
}
}

View File

@ -7,7 +7,6 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"io" "io"
"io/fs"
"os" "os"
"os/exec" "os/exec"
"path" "path"
@ -18,7 +17,6 @@ import (
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/mathutil"
) )
// UnsupportedError is returned by functions and methods when a particular // UnsupportedError is returned by functions and methods when a particular
@ -63,7 +61,7 @@ func RunCommand(command string, arguments ...string) (code int, output []byte, e
cmd := exec.Command(command, arguments...) cmd := exec.Command(command, arguments...)
out, err := cmd.Output() out, err := cmd.Output()
out = out[:mathutil.Min(len(out), MaxCmdOutputSize)] out = out[:min(len(out), MaxCmdOutputSize)]
if err != nil { if err != nil {
if eerr := new(exec.ExitError); errors.As(err, &eerr) { if eerr := new(exec.ExitError); errors.As(err, &eerr) {
@ -142,7 +140,7 @@ func parsePSOutput(r io.Reader, cmdName string, ignore []int) (largest, instNum
} }
instNum++ instNum++
largest = mathutil.Max(largest, cur) largest = max(largest, cur)
} }
if err = s.Err(); err != nil { if err = s.Err(); err != nil {
return 0, 0, fmt.Errorf("scanning stdout: %w", err) return 0, 0, fmt.Errorf("scanning stdout: %w", err)
@ -156,13 +154,6 @@ func IsOpenWrt() (ok bool) {
return isOpenWrt() return isOpenWrt()
} }
// RootDirFS returns the [fs.FS] rooted at the operating system's root. On
// Windows it returns the fs.FS rooted at the volume of the system directory
// (usually, C:).
func RootDirFS() (fsys fs.FS) {
return rootDirFS()
}
// NotifyReconfigureSignal notifies c on receiving reconfigure signals. // NotifyReconfigureSignal notifies c on receiving reconfigure signals.
func NotifyReconfigureSignal(c chan<- os.Signal) { func NotifyReconfigureSignal(c chan<- os.Signal) {
notifyReconfigureSignal(c) notifyReconfigureSignal(c)

View File

@ -7,6 +7,7 @@ import (
"os" "os"
"syscall" "syscall"
"github.com/AdguardTeam/golibs/osutil"
"github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/golibs/stringutil"
) )
@ -40,7 +41,7 @@ func isOpenWrt() (ok bool) {
} }
return nil, !stringutil.ContainsFold(string(data), osNameData), nil return nil, !stringutil.ContainsFold(string(data), osNameData), nil
}).Walk(RootDirFS(), etcReleasePattern) }).Walk(osutil.RootDirFS(), etcReleasePattern)
return err == nil && ok return err == nil && ok
} }

View File

@ -3,17 +3,12 @@
package aghos package aghos
import ( import (
"io/fs"
"os" "os"
"os/signal" "os/signal"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
func rootDirFS() (fsys fs.FS) {
return os.DirFS("/")
}
func notifyReconfigureSignal(c chan<- os.Signal) { func notifyReconfigureSignal(c chan<- os.Signal) {
signal.Notify(c, unix.SIGHUP) signal.Notify(c, unix.SIGHUP)
} }

View File

@ -3,29 +3,13 @@
package aghos package aghos
import ( import (
"io/fs"
"os" "os"
"os/signal" "os/signal"
"path/filepath"
"syscall" "syscall"
"github.com/AdguardTeam/golibs/log"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
func rootDirFS() (fsys fs.FS) {
// TODO(a.garipov): Use a better way if golang/go#44279 is ever resolved.
sysDir, err := windows.GetSystemDirectory()
if err != nil {
log.Error("aghos: getting root filesystem: %s; using C:", err)
// Assume that C: is the safe default.
return os.DirFS("C:")
}
return os.DirFS(filepath.VolumeName(sysDir))
}
func setRlimit(val uint64) (err error) { func setRlimit(val uint64) (err error) {
return Unsupported("setrlimit") return Unsupported("setrlimit")
} }

View File

@ -26,14 +26,25 @@ import (
// FSWatcher is a fake [aghos.FSWatcher] implementation for tests. // FSWatcher is a fake [aghos.FSWatcher] implementation for tests.
type FSWatcher struct { type FSWatcher struct {
OnStart func() (err error)
OnClose func() (err error)
OnEvents func() (e <-chan struct{}) OnEvents func() (e <-chan struct{})
OnAdd func(name string) (err error) OnAdd func(name string) (err error)
OnClose func() (err error)
} }
// type check // type check
var _ aghos.FSWatcher = (*FSWatcher)(nil) var _ aghos.FSWatcher = (*FSWatcher)(nil)
// Start implements the [aghos.FSWatcher] interface for *FSWatcher.
func (w *FSWatcher) Start() (err error) {
return w.OnStart()
}
// Close implements the [aghos.FSWatcher] interface for *FSWatcher.
func (w *FSWatcher) Close() (err error) {
return w.OnClose()
}
// Events implements the [aghos.FSWatcher] interface for *FSWatcher. // Events implements the [aghos.FSWatcher] interface for *FSWatcher.
func (w *FSWatcher) Events() (e <-chan struct{}) { func (w *FSWatcher) Events() (e <-chan struct{}) {
return w.OnEvents() return w.OnEvents()
@ -44,11 +55,6 @@ func (w *FSWatcher) Add(name string) (err error) {
return w.OnAdd(name) return w.OnAdd(name)
} }
// Close implements the [aghos.FSWatcher] interface for *FSWatcher.
func (w *FSWatcher) Close() (err error) {
return w.OnClose()
}
// Package agh // Package agh
// ServiceWithConfig is a fake [agh.ServiceWithConfig] implementation for tests. // ServiceWithConfig is a fake [agh.ServiceWithConfig] implementation for tests.

View File

@ -14,6 +14,7 @@ import (
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/osutil"
) )
// Variables and functions to substitute in tests. // Variables and functions to substitute in tests.
@ -22,7 +23,7 @@ var (
aghosRunCommand = aghos.RunCommand aghosRunCommand = aghos.RunCommand
// rootDirFS is the filesystem pointing to the root directory. // rootDirFS is the filesystem pointing to the root directory.
rootDirFS = aghos.RootDirFS() rootDirFS = osutil.RootDirFS()
) )
// Interface stores and refreshes the network neighborhood reported by ARP // Interface stores and refreshes the network neighborhood reported by ARP

View File

@ -1330,6 +1330,7 @@ func TestPTRResponseFromHosts(t *testing.T) {
var eventsCalledCounter uint32 var eventsCalledCounter uint32
hc, err := aghnet.NewHostsContainer(testFS, &aghtest.FSWatcher{ hc, err := aghnet.NewHostsContainer(testFS, &aghtest.FSWatcher{
OnStart: func() (_ error) { panic("not implemented") },
OnEvents: func() (e <-chan struct{}) { OnEvents: func() (e <-chan struct{}) {
assert.Equal(t, uint32(1), atomic.AddUint32(&eventsCalledCounter, 1)) assert.Equal(t, uint32(1), atomic.AddUint32(&eventsCalledCounter, 1))

View File

@ -418,6 +418,7 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) {
}, },
}, },
&aghtest.FSWatcher{ &aghtest.FSWatcher{
OnStart: func() (_ error) { panic("not implemented") },
OnEvents: func() (e <-chan struct{}) { return nil }, OnEvents: func() (e <-chan struct{}) { return nil },
OnAdd: func(_ string) (err error) { return nil }, OnAdd: func(_ string) (err error) { return nil },
OnClose: func() (err error) { return nil }, OnClose: func() (err error) { return nil },

View File

@ -40,6 +40,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
}, },
} }
watcher := &aghtest.FSWatcher{ watcher := &aghtest.FSWatcher{
OnStart: func() (_ error) { panic("not implemented") },
OnEvents: func() (e <-chan struct{}) { return nil }, OnEvents: func() (e <-chan struct{}) { return nil },
OnAdd: func(name string) (err error) { return nil }, OnAdd: func(name string) (err error) { return nil },
OnClose: func() (err error) { return nil }, OnClose: func() (err error) { return nil },

View File

@ -8,7 +8,6 @@ import (
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/mathutil"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
@ -181,7 +180,7 @@ func findRewrites(
if isWildcard(r.Domain) { if isWildcard(r.Domain) {
// Don't use rewrites[:0], because we need to return at least one // Don't use rewrites[:0], because we need to return at least one
// item here. // item here.
rewrites = rewrites[:mathutil.Max(1, i)] rewrites = rewrites[:max(1, i)]
break break
} }

View File

@ -15,7 +15,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/version" "github.com/AdguardTeam/AdGuardHome/internal/version"
"github.com/AdguardTeam/golibs/httphdr" "github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/mathutil"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/NYTimes/gziphandler" "github.com/NYTimes/gziphandler"
) )
@ -145,10 +144,7 @@ func handleStatus(w http.ResponseWriter, r *http.Request) {
// Make sure that we don't send negative numbers to the frontend, // Make sure that we don't send negative numbers to the frontend,
// since enough time might have passed to make the difference less // since enough time might have passed to make the difference less
// than zero. // than zero.
protectionDisabledDuration = mathutil.Max( protectionDisabledDuration = max(0, time.Until(*protectionDisabledUntil).Milliseconds())
0,
time.Until(*protectionDisabledUntil).Milliseconds(),
)
} }
resp = statusResponse{ resp = statusResponse{

View File

@ -250,7 +250,7 @@ func setupHostsContainer() (err error) {
return errors.Join(fmt.Errorf("initializing hosts container: %w", err), closeErr) return errors.Join(fmt.Errorf("initializing hosts container: %w", err), closeErr)
} }
return nil return hostsWatcher.Start()
} }
// setupOpts sets up command-line options. // setupOpts sets up command-line options.