-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdsfree_service.go
More file actions
106 lines (91 loc) · 3.11 KB
/
dsfree_service.go
File metadata and controls
106 lines (91 loc) · 3.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package main
import (
"log"
"os"
"sync"
"time"
"github.com/fsnotify/fsnotify"
)
// DSFreeService is the core orchestrator. It contains the business logic
// of the application, completely decoupled from the external world.
type DSFreeService struct {
// These fields are not a concrete type, but an interface.
// These dependencies are injected to allow for clean testing and decoupling.
hostsManager HostsManager
watcher FileWatcher
scheduler Scheduler
domains []string
shutdownChan chan os.Signal
// mu protect the following fields from concurrent access.
mu sync.Mutex
isCoolingDown bool
}
// It creates a new service instance with its required dependencies.
func NewDSFreeService(manager HostsManager, watcher FileWatcher, shutdownChan chan os.Signal, scheduler Scheduler, domains []string) *DSFreeService {
return &DSFreeService{
hostsManager: manager,
watcher: watcher,
shutdownChan: shutdownChan,
scheduler: scheduler,
domains: domains,
}
}
// Run starts the service logic. It applies the sabotage and then waits
// for a shutdown signal to restore everything. This is a blocking method.
func (s *DSFreeService) Run() {
// Ensure the watcher is closed gracefully when the service stops.
defer s.watcher.Close()
log.Println("DSFreeService: Applying initial sabotage...")
if err := s.hostsManager.Apply(s.domains); err != nil {
// Note: A non-fatal error is logged. Run() is not responsible for stopping the entire program
// this initial failure is best handled by the main function or process manager.
log.Printf("ERROR: Could not apply initial hosts configuration: %v", err)
return
}
log.Println("DSFreeService: Sabotage applied. Starting continuous watch.")
// Start the main event loop.
for {
select {
case event, ok := <-s.watcher.Events():
if !ok {
return
}
s.mu.Lock()
if s.isCoolingDown {
s.mu.Unlock()
continue
}
s.isCoolingDown = true
s.mu.Unlock()
if event.Has(fsnotify.Write) || event.Has(fsnotify.Rename) || event.Has(fsnotify.Remove) {
log.Println("DSFreeService: Tampering or file replacement detected! Re-enforcing state...")
if err := s.hostsManager.Apply(s.domains); err != nil {
log.Printf("ERROR: Failed to re-apply sabotage: %v", err)
}
// CRUCIAL STEP: We must re-attach the watcher to the new file
// created by Apply().
if err := s.watcher.Add(hostsFilePath); err != nil {
log.Printf("CRITICAL: Failed to re-establish watch on hosts file: %v", err)
}
}
s.scheduler.AfterFunc(2*time.Second, func() {
s.mu.Lock()
s.isCoolingDown = false
s.mu.Unlock()
log.Println("DSFreeService: Cooldown finished. Resuming normal watch.")
})
case err, ok := <-s.watcher.Errors():
if !ok {
return
}
log.Printf("ERROR: File watcher error: %v", err)
case sig := <-s.shutdownChan:
log.Printf("DSFreeService: Shutdown signal received: %v. Cleaning up.", sig)
if err := s.hostsManager.Restore(); err != nil {
log.Printf("ERROR: Could not restore hosts file on shutdown: %v", err)
}
log.Println("DSFreeService: Cleanup complete.")
return
}
}
}