mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 09:40:12 +00:00
150 lines
4.9 KiB
Go
150 lines
4.9 KiB
Go
//go:build agentcompat && linux
|
|
|
|
package singleton
|
|
|
|
import (
|
|
"errors"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func (connection *sqliteAttributionConnection) beforeWrite(classification sqliteAttributionClassification) error {
|
|
if !sqliteAttributionEnabled.Load() || classification.readonly {
|
|
return nil
|
|
}
|
|
if !classification.valid() {
|
|
return &SQLiteAttributionError{Cause: ErrSQLiteAttributionUnsupportedWrite}
|
|
}
|
|
connection.lifecycleMu.Lock()
|
|
state := connection.transaction
|
|
if state == nil {
|
|
connection.lifecycleMu.Unlock()
|
|
return &SQLiteAttributionError{Cause: ErrSQLiteAttributionUnboundWrite}
|
|
}
|
|
poison := state.poison
|
|
connection.lifecycleMu.Unlock()
|
|
if poison != nil {
|
|
return poison
|
|
}
|
|
connection.execution = &sqliteAttributionExecution{classification: classification, origin: sqliteAttributionOrigin()}
|
|
return nil
|
|
}
|
|
|
|
func (connection *sqliteAttributionConnection) discardExecution() { connection.execution = nil }
|
|
|
|
func (connection *sqliteAttributionConnection) publishExecution() error {
|
|
execution := connection.execution
|
|
connection.execution = nil
|
|
if execution == nil {
|
|
return nil
|
|
}
|
|
connection.lifecycleMu.Lock()
|
|
state := connection.transaction
|
|
if state == nil {
|
|
connection.lifecycleMu.Unlock()
|
|
return &SQLiteAttributionError{Cause: ErrSQLiteAttributionUnboundWrite}
|
|
}
|
|
if execution.hook.mismatch || !execution.hook.seen {
|
|
err := &SQLiteAttributionError{Cause: ErrSQLiteAttributionUnsupportedWrite}
|
|
state.poison = err
|
|
connection.lifecycleMu.Unlock()
|
|
return err
|
|
}
|
|
if state.journalFD < 0 {
|
|
descriptor, err := sqliteAttributionOpenJournalDescriptor(connection.journal)
|
|
if err != nil {
|
|
state.poison = err
|
|
connection.lifecycleMu.Unlock()
|
|
return err
|
|
}
|
|
state.journalFD = descriptor
|
|
}
|
|
journal, err := sqliteAttributionJournalIdentityFromDescriptor(state.journalFD)
|
|
if err != nil {
|
|
state.poison = err
|
|
connection.lifecycleMu.Unlock()
|
|
return err
|
|
}
|
|
transaction := state.transaction
|
|
tracker := state.tracker
|
|
connection.lifecycleMu.Unlock()
|
|
origin := execution.origin
|
|
origin.Operation = execution.classification.operation
|
|
origin.Table = execution.classification.table
|
|
err = tracker.RecordSQLiteWrite(transaction, SQLiteWriteObservation{
|
|
Origin: origin,
|
|
Update: SQLiteUpdateObservation{Operation: execution.classification.operation, Table: execution.classification.table, Journal: journal},
|
|
})
|
|
if err != nil {
|
|
return connection.poison(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (connection *sqliteAttributionConnection) poison(err error) error {
|
|
connection.lifecycleMu.Lock()
|
|
state := connection.transaction
|
|
if state != nil {
|
|
state.poison = err
|
|
}
|
|
connection.lifecycleMu.Unlock()
|
|
return err
|
|
}
|
|
|
|
// The adapter owns the explicit main transaction and DELETE journal; path-only stat is racy, so retain this O_PATH descriptor through finalization.
|
|
func sqliteAttributionOpenJournalDescriptor(path string) (int, error) {
|
|
descriptor, err := unix.Open(path, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
|
if err != nil {
|
|
return -1, &SQLiteAttributionError{Cause: errors.Join(ErrSQLiteAttributionJournalIdentity, err)}
|
|
}
|
|
return descriptor, nil
|
|
}
|
|
|
|
func sqliteAttributionCloseJournalDescriptor(descriptor int) error {
|
|
if descriptor < 0 {
|
|
return nil
|
|
}
|
|
if err := unix.Close(descriptor); err != nil {
|
|
return &SQLiteAttributionError{Cause: errors.Join(ErrSQLiteAttributionJournalIdentity, err)}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func sqliteAttributionJournalIdentityFromDescriptor(descriptor int) (SQLiteJournalIdentity, error) {
|
|
var status unix.Statx_t
|
|
mask := uint32(unix.STATX_BASIC_STATS | unix.STATX_BTIME | unix.STATX_MNT_ID)
|
|
if err := unix.Statx(descriptor, "", unix.AT_EMPTY_PATH|unix.AT_STATX_SYNC_AS_STAT, int(mask), &status); err != nil {
|
|
return SQLiteJournalIdentity{}, &SQLiteAttributionError{Cause: errors.Join(ErrSQLiteAttributionJournalIdentity, err)}
|
|
}
|
|
required := uint32(unix.STATX_MNT_ID | unix.STATX_BTIME)
|
|
if status.Mask&required != required {
|
|
return SQLiteJournalIdentity{}, &SQLiteAttributionError{Cause: ErrSQLiteAttributionJournalIdentity}
|
|
}
|
|
return SQLiteJournalIdentity{MountID: status.Mnt_id, DeviceMajor: status.Dev_major, DeviceMinor: status.Dev_minor, Inode: status.Ino, BirthSeconds: status.Btime.Sec, BirthNanoseconds: status.Btime.Nsec}, nil
|
|
}
|
|
|
|
func sqliteAttributionOrigin() SQLiteExecutionOrigin {
|
|
programCounters := make([]uintptr, 16)
|
|
count := runtime.Callers(3, programCounters)
|
|
programCounters = programCounters[:count]
|
|
frames := runtime.CallersFrames(programCounters)
|
|
frame, more := frames.Next()
|
|
for more && !strings.Contains(frame.Function, "github.com/nezhahq/nezha/") {
|
|
frame, more = frames.Next()
|
|
}
|
|
return SQLiteExecutionOrigin{StackHash: sqliteAttributionStackHash(programCounters), FirstNezhaFrame: frame.Function}
|
|
}
|
|
|
|
func sqliteAttributionStackHash(programCounters []uintptr) uint64 {
|
|
const offsetBasis uint64 = 14695981039346656037
|
|
const prime uint64 = 1099511628211
|
|
hash := offsetBasis
|
|
for _, programCounter := range programCounters {
|
|
hash ^= uint64(programCounter)
|
|
hash *= prime
|
|
}
|
|
return hash
|
|
}
|